New APIs in Java 1.4 make logging a cinch

Before the release of Java 1.4, you had to rely on third-party logging packages. Now you can take advantage of the new built-in logging APIs. We'll get you started.

The 1.4 release of Sun’s JDK includes core APIs for application logging in the java.util.logging package. Earlier releases of Sun’s JDK did not have a built-in logging framework, so you had to either develop your own logging classes or use third-party logging APIs. The new Java logging package provides a straightforward, flexible way to build multiple levels of logging into Java applications. In this article, we'll run through some of the ways you can put the APIs to work. You can download the code examples for this article here.

To take advantage of the logging package, you simply need to obtain a Logger object and invoke one of the logging methods of the Logger. The Logger creates a LogRecord object and passes it to one or more Handlers to output the log record. Loggers and Handlers make use of logging Levels and Filters to determine which log records get recorded. The Handlers have associated Formatters that specify how the log records are output to the log. Figure A shows you how these objects work together to provide a logging system.

Figure A

Log levels
The Java logging package assigns a level to each log message. The levels are integer values, and higher logging levels are assigned higher integer values. The following levels are defined in the Level class (from lowest to highest):
  • ·        FINEST
  • ·        FINER
  • ·        FINE
  • ·        CONFIG
  • ·        INFO
  • ·        WARNING
  • ·        SEVERE

Loggers and Handlers can specify a minimum logging level, and only log messages at that level or higher will be recorded. In addition to these levels, the Level class defines a level of OFF that disables logging and a level of ALL that turns on logging for all levels.

Logger objects
You create Logger objects by calling one of the static getLogger methods in the Logger class and specifying a String for the name of the Logger. These methods return the Logger of the specified name, if one exists, or create and return a new Logger. Loggers use dot-separated names and typically follow the naming convention of the package for which they are performing logging. Logger names are hierarchical, and Loggers inherit some of their properties from their parent Loggers. If a Logger does not have a defined logging level, it will use the level of its parent Logger or the first non-Null level in its ancestor hierarchy.

By default, Loggers also pass log messages to the Handlers of their parents, in addition to their own Handlers. Several types of methods are provided for sending log messages to Logger objects. The simplest of these are the convenience methods, which are named after the standard log levels and take only a message string as an argument. The SimpleLogger class in Listing A demonstrates the use of the convenience methods to log messages at different log levels. Executing the main method of the SimpleLogger class creates the output in this sidebar on the system console.

In the output sidebar, notice that only the log messages at the INFO level or higher actually get logged to the system console. The reason for this is that the Logger is using the default Handler, which is a ConsoleHandler with a level of INFO. We'll look at this in more detail in the section on Handler objects. An important note about the Logger class is that the level comparison is the first action taken by any of the logging methods. If the level of the log message is below the Logger’s log level, the method simply returns without taking any further action. If the log level is one that the logger is interested in, the LogRecord object is created and passed to the Handler for publication to the log.

The expensive formatting and I/O operations are left to the Handler and Formatter objects. Since the log level evaluation is just an integer comparison, this is a relatively inexpensive operation. This allows log messages at various levels to be placed in the application code, and the Logger can determine which messages should be logged and which should be ignored without much additional overhead.

Handler objects
Handler objects are responsible for publishing log records to a specified output location. The Java Logging APIs provide two main categories of Handler objects. MemoryHandlers simply store log messages in a circular memory buffer and publish them to target handlers when a certain trigger event occurs. The trigger event is typically the receipt of a log message at a specified level known as the push level. If other push criteria are desired, the MemoryHandler class can be extended and the log method overwritten to push the memory buffer based on user-defined conditions of the log record.

StreamHandlers publish log records to a specified output stream. The Java Logging APIs specify three types of StreamHandler objects. ConsoleHandlers publish log records to a standard error. FileHandlers publish log records to a specified file, or they can be configured to write log records to a rotating set of files. SocketHandlers publish log records to a network stream for communication with another application.

Like Loggers, Handlers have a minimum log level that determines which log records they are interested in publishing. A Logger can have multiple Handlers associated with it, each with a different log level. The MultiHandlerLogger class in Listing B uses this functionality to log all messages to a log file while logging only SEVERE messages to the console. When the main method is executed, the output shown in this sidebar is displayed on the console.

As expected, only the SEVERE message is logged to the console. A log file named test.log is also created, as shown in the output sidebar. Although all the log messages are recorded in the test.log file, the format of the log file is somewhat unexpected. We'll look at the reason for this in the next section.

Each Handler has a Formatter object associated with it. The Formatter converts the LogRecord object into a String for output purposes. The Java Logging APIs provide two types of Formatters, the SimpleFormatter and the XMLFormatter. By default, ConsoleHandler objects use the SimpleFormatter, and FileHandler objects use the XMLFormatter. This explains the output in the test.log file. It was output in XML format because the FileHandler used the default XMLFormatter. The Handler object can override the default Formatter using the setFormatter method. You can extend the Formatter class to create custom log formats for your applications.

The Java Logging APIs provide the Filter interface to give developers more control over which log records get logged beyond just checking the log level. A class that implements the Filter interface must implement the isLoggable method. This method receives a LogRecord object and returns a Boolean indicating whether the log record should be published. Logger and Handler objects can add a filter using the setFilter method.

The LogManager is a global object that stores configuration and state information for all Loggers in the Java Virtual Machine. Items such as log Levels, Handlers, and Formatters are maintained by the LogManager. By default, the LogManager reads its configuration information at startup from a properties file in the lib subdirectory of the JRE named The LogManager can be configured to read its initialization properties from another file or to load a class whose constructor will set the initialization properties. For detailed information, see the Java API documentation on the LogManager class.

The Java Logging APIs provide a flexible framework for building both simple and complex application logging systems. Application developers can utilize multiple Loggers to create different logs for different parts of the system. Multiple Handlers and Formatters can be used to present different logging views to different audiences.

The encapsulation of the expensive formatting and I/O operations in the Formatter and Handler classes provides a simple way to control the amount of logging overhead incurred simply by changing the log level of the Logger objects. Since this can be done in a properties file, without requiring any change to the application code, the amount of logging enabled in the system can be adjusted up or down as needed with little additional development effort and a minimal impact to system performance.

Editor's Picks