Customize error logging with the Logging API

JDK 1.4's Logging API offers developers more control of when, where, and how your applications log crucial reporting data. Find out how to fine-tune your logging to maximize flexibility and efficiency.

Logging API, introduced in JDK 1.4, can capture information such as configuration errors, performance bottlenecks, security breaches, and application or platform bugs. The API produces log reports suitable for analysis by field service engineers, software developers, and system administrators engaged in customer site service and maintenance.

The definition of logging
Logging is a systematic method of recording information that should be:
  • Simple, requiring minimal effort to enter information into an original program.
  • Traceable, requiring minimal effort to understand.
  • Maintainable, requiring minimal effort to change information when a program is modified.

Many programmers are accustomed to using a function System.err.println to display debugging information. Simple? Yes, it is. Traceable? Not really, except that you can instruct the function to display and save detailed information. Maintainable? Definitely not. You may be confused by other OutputStream objects, such as System.out.println or fout.println.

The Logging API in JDK 1.4, which provides object-oriented interfaces for Java’s logging faculties, is designed to support simple, traceable, and maintainable problem diagnosis for a variety of clients, from end users to developers. Table A lists the key elements of the Logging API.

Table A
Class Name Description
Logger Main entity used to make logging calls.
LogRecord Used to pass logging requests between the logging framework
and individual log Handler objects.
Handler Used to export LogRecord objects to varied types of output,
such as OutputStream and files.
Level Defines a preset logging level, which can be used to control
logging output.
Filter Provides fine-grained control over what is logged or beyond the
control provided by log Level objects.
Formatter A Formatter object supports formatting LogRecord objects.

Now that we've considered the overall function of Logging API, let's look at some fundamental and practical applications of the tool. Before implementing your code, remember to add the following statement, which includes the package of Logging API:
import java.util.logging.*;

As I mentioned earlier, a Logger object is responsible for generating logging messages. You construct a Logger object with the following statement:
static Logger theLogger = Logger.getLogger(LoggingTest.class.getName());

The above statement uses a factory method, getLogger, to construct or find a Logger object, theLogger, with a name provided from a static method, LoggingTest.class.getName(). The Logger object is created with a factory method instead of a constructor because the factory method can decide whether it should construct a new object or choose an existing object for theLogger.

Each Logger object contains a name that identifies each unique Logger. If an existing Logger object has already used the name, the factory method will return the existing object; otherwise, the method will construct a new Logger object. The name will be used to register the Logger object in the LoggerManager global namespace.

In some situations, for example in an applet, it's not convenient to have a registered Logger, as it complicates possible security checking. You may need an untitled Logger object so that an untrusted applet is allowed to update the control state of the Logger. You can construct an untitled Logger object with the following statement:
static Logger theLogger = Logger. getAnonymousLogger();

The statement below will display a logging message:"Hello logging!");

Unlike System.err.println, this statement can record more information than just a text message. The statement has the following output:
2003/2/27 08:46:31 LoggingTest testLogger
Info: Hello logging!

The Logger object can record the logging’s date, time, involved class, involved method, the level of logging, and the text message. In the past, you might have to write several lines of code to produce such detailed messages, but now the Logger does it for you.

In my example above, the method INFO is used to output the logging message. A variety of methods are available to output a message but the INFO method is used specifically to output a logging message in a Level of INFO.

Unlike System.err.println, a Logger object does not immediately output all information; this setting avoids bogging down the common language runtime by outputting nonessential data. To determine which information should be output at which time, a class Level should be used to restrict logging. Table B offers a list of predefined levels for logging.

Table B
Level Importance Corresponding log
method in Logger
Level value
SEVERE Extremely important severe(String message); 1000
WARNING Intended for warning warning(String message); 900
INFO Informational runtime
info(String message); 800
CONFIG Message for static
config(String message); 700
FINE Provides tracing
fine(String message); 500
FINER Indicates a fairly
detailed tracing message
finer(String message); 400
FINEST Highly detailed tracing
finest(String message); 300
ALL Indicates that all
messages should be
Not applicable Integer.MIN_VALUE
OFF Turns off logging Not applicable Integer.MAX_VALUE

Notice that the first seven levels restrict logging, while the last two are special levels used to enable or disable logging.

Logging can work with a Level restriction in two ways. One approach is to use the predefined functions shown above. Another is to use the method log with a Level object. For instance, if you want to log a message in a CONFIG Level, you can use code such as:
theLogger.config(“Hello Logging!”);

theLogger.log(Level.CONFIG, “Hello Logging!”);

The outputs in the above examples are to the console, but the Logging API also provides other possible destinations for the outputs through a Handler object.

The following two statements construct a Handler for the file myLogger.txt and insert the Handler into the Logger object theLogger:
FileHandler h = new FileHandler("./myLogger.txt");

After these two statements run, a logging message will appear in the console as well as inside the file myLogger.txt.

Table C offers a list of available Handler classes.

Table C
Handler Usage
StreamHandler Writes formatted records to an OutputStream
ConsoleHandler Writes formatted records to System.err
FileHandler Writes formatted log records either to a single file or to a set of rotating log files
SocketHandler Writes formatted log records to remote TCP ports
MemoryHandler Buffers log records in memory

As you can see, destinations range from the console to files to the network. Handler controls not only logging messages' destination but also their output format, with help of Formatter.

Formatter is used to define the layout your logging output. JDK 1.4 offers two Formatters: SimpleFormatter and XMLFormatter. The former displays layout as shown earlier in this article, and the latter constructs a layout in an XML format. The above statements output logging messages to a text file in XML format like so:
FileHandler h = new FileHandler("./myLogger.txt");
h.setFormatter(new XMLFormatter());

Each Handler object usually can contain a Formatter object. In the above example, after calling the statement of“Hello Logging!”); the text file, myLogger.txt, will contain the content listed below:
<?xml version="1.0" encoding="windows-950" standalone="no"?>
<!DOCTYPE log SYSTEM "logger.dtd">
 <message>Hello logging!</message>

Since XML layout can be read and handled by common XML parsers, you can reconstruct a new format with the above XML file and your own XML parser application.

Keep ahead of an essential skill
In this article, I've provided a basic overview of Logging API in JDK 1.4, with emphasis on the Logger, Level, Handler, and Formatter objects. These objects are the basic tools you must understand before implementing logging functionality.

However, Logging API offers many levels of complexity beyond these basics. For example, you can develop your own Level, Handler, and Formatter objects with application-specific capabilities. Other advanced concepts—such as utilizing Filter to finely control logged items, employing dynamic configuration updates within a running program, and keeping track of global logging information with LogManager—build on these skills.

You should consider developing these skills now because even more advanced functionality is expected in later versions of Logging API.

Editor's Picks