Logging in J2SE 1.4

By Thomas Paul

The ability to log error, warning, or debugging information can be a critical part of any application. Logging is so critical that logging systems have been independently developed and implemented hundreds if not thousands of times. The Apache project developed a framework (log4j) which is widely used. With the release of J2SE 1.4, we finally have a logging API built into the language. The new API is found in the package, java.util.logging.

The functionality of the logging API has been divided into three different types of objects. Logger objects are the controllers. Handler objects determine where the logging information will be published. Formatter objects control what the published log information will look like.

Listing 1 contains a listing of a program using logging. Listing 2 contains the log output of the program.

Loggers

Logger objects are the main entry points into the logging API. Messages you wish to log will be passed into a logger object. The logger will determine if the message should be logged and send it to the appropriate Handler object.

Loggers are not directly instantiated but are created with the getLogger(String loggerName) static method of the Logger class. Logger objects are named using the package naming standard. Here is an example of creating a Logger object:

     Logger logger = Logger.getLogger("com.aag");

The Logger class has many methods that can be used to send log messages to the Handler objects. The basic version is:

     void log(Level level, String msg)

The Level class defines seven different levels as static constants. Ranging from highest to lowest, these are: SEVERE, WARNING, INFO, CONFIG, FINE , FINER, and FINEST. To send a warning level message to the log we created earlier we would use:

     logger.log(Level.WARNING, "A warning message");

You can also use:

     logger.warning("A warning message");

There is a version of the log method for each of the seven levels. There is also an overloaded version of the log method that you can use to log exceptions:

     void log(Level level, String msg, Throwable thrown)

To log an exception you could code this:

catch (Exception e) {
     logger.log(Level.SEVERE, "Exception caught!", e);
}

The Logger class has a setLevel method that can be used to filter logging messages:

     void setLevel(Level level)

The logger will ignore messages that are below the level specified in the setLevel method. For example, if you wanted to ignore all messages except SEVERE and WARNING level messages, you would use:

     logger.setLevel(Level.WARNING);

In addition to the seven static constant levels we mentioned earlier, the Level class also contains two other constants. Level.ALL is used to accept all messages. This ends up being the same as specifying Level.FINEST. Level.OFF is used to reject all messages sent to the Logger.

By default, the Logger object will send all messages to System.err in a format that is human readable. If some other functionality is required, the API provides Handler and Formatter classes.

Handlers

A Handler object controls how messages are published but not how they will appear. The API provides five Handler classes:

The Logger class has the addHandler(Handler handler) and removeHandler(Handler handler) methods to determine which Handlers will receive log messages. To change the Logger to send all log messages to a file named "out.log" instead of using the default ConsoleHandler, you would use:

     Handler handler = new FileHandler("out.log");
     logger.addHandler(handler);

Creating your own Handler class is fairly simple. The Handler abstract class contains only three methods:

I will leave it as an exercise for the reader to design a Handler that sends log messages to a JMS topic.

The level can be set for Handlers just as we can set it for Loggers. In this way a Logger can have multiple Handlers attached that to it with each Handler processing messages based on its own level criteria. For example, SEVERE messages could be sent to the console while INFO messages and above are logged in a file. To set the level for a Handler to report only SEVERE messages, you would use:

     handler.setLevel(Level.SEVERE);

Different Handlers have different default levels. ConsoleHandler uses Level.INFO as the default while FileHandler uses Level.ALL as its default.

Formatters

In addition to controlling where messages are sent, the logging API also provides two classes to control the appearance of the log messages. SimpleFormatter produces a log message in "human readable" form. This is the default Formatter used by ConsoleHandler. XMLFormatter writes the log messages in an XML format. This is the default used by FileHandler. Samples of both formats are shown blow. To change the FileHandler to use the SimpleFormatter you would code:

     Formatter formatter = new SimpleFormatter();
     handler.setFormatter(formatter);

Writing your own Formatter class is a simple exercise. The Formatter abstract class has three methods to use:

The getHead( ) and getTail( ) methods are invoked at the beginning and ending of logging. These can be used to specify information that should be written either before or after the actual logging data. The format( ) method is invoked once for each message to be logged. The method is given a LogRecord object that contains all the information about the log message. The LogRecord has several useful methods:

See Listing 3 for a sample Formatter class.

Filters

The logging API provides a Filter interface that can be used to filter messages either at the Logger or at the Handler. The Filter interface contains one method:

     boolean isLoggable(LogRecord log)

If the Filter returns false, the LogRecord is ignored and not sent or processed by the Handler. Filters can be used to reject log records based on any criteria. For example, log records could be rejected based on the class that generated them or the day of the week or by the message contents. Adding a Filter to a Handler or Logger is done using the setFilter( ) method. An example of using this:

     Filter filter = new DayOfWeekFilter("Monday");
     handler.setFilter(filter);

Conclusions

There are several areas where the logging API has some shortcomings. Security is an issue that may have potential problems. Another area of potential annoyance is with multi-threaded applications. The API does not synchronize any methods so there may be a problem of log messages not appearing in the order they were written to the log.

Overall, however, the logging API is a well written, if basic, logging system. I recommend that anyone doing development examine this API to see if it meets their needs. For those who are unsatisfied with the API, the Apache Project's log4j is available and continues in development.