Java Logging Overview

Last update Nov 26 2001

1.0 Java Logging Overview
    1.1 Overview of Control Flow
     1.2 Log Levels
     1.3 Loggers
     1.4 Logging Methods
     1.5 Handlers
     1.6 Formatters
     1.7 The LogManager
     1.8 Configuration File
     1.9 Default Configuration
     1.10 Dynamic Configuration Updates
     1.11 Native Methods
     1.12 XML DTD
     1.13 Unique Message IDs
     1.14 Security
     1.15 Configuration Management
     1.16 Packaging
     1.17 Localization
     1.18 Remote Access and Serialization
2.0 Examples
     2.1 Simple Use
     2.2 Changing the Configuration
     2.3 Simple Use, Ignoring Global Configuration
     2.4 Sample XML Output
3.0 Appendix A: DTD for XMLFormatter Output

1.0 Java Logging Overview

The logging APIs are described in detail in the Java SE API Specification. The goal of this document is to provide an overview of key elements.

1.1 Overview of Control Flow

Applications make logging calls on Logger objects. Loggers are organized in a hierarchical namespace and child Loggers may inherit some logging properties from their parents in the namespace.

Applications make logging calls on Logger objects. These Logger objects allocate LogRecord objects which are passed to Handler objects for publication. Both Loggers and Handlers may use logging Levels and (optionally) Filters to decide if they are interested in a particular LogRecord. When it is necessary to publish a LogRecord externally, a Handler can (optionally) use a Formatter to localize and format the message before publishing it to an I/O stream.

The previous context describes this graphic

Each Logger keeps track of a set of output Handlers. By default all Loggers also send their output to their parent Logger. But Loggers may also be configured to ignore Handlers higher up the tree.

Some Handlers may direct output to other Handlers. For example, the MemoryHandler maintains an internal ring buffer of LogRecords and on trigger events it publishes its LogRecords through a target Handler. In such cases, any formatting is done by the last Handler in the chain.

The previous context describes this graphic

The APIs are structured so that calls on the Logger APIs can be cheap when logging is disabled. If logging is disabled for a given log level, then the Logger can make a cheap comparison test and return. If logging is enabled for a given log level, the Logger is still careful to minimize costs before passing the LogRecord into the Handlers. In particular, localization and formatting (which are relatively expensive) are deferred until the Handler requests them. For example, a MemoryHandler can maintain a circular buffer of LogRecords without having to pay formatting costs.

1.2 Log Levels

Each log message has an associated log Level. The Level gives a rough guide to the importance and urgency of a log message. Log level objects encapsulate an integer value, with higher values indicating higher priorities.

The Level class defines seven standard log levels, ranging from FINEST (the lowest priority, with the lowest value) to SEVERE (the highest priority, with the highest value).

1.3 Loggers

As stated earlier, client code sends log requests to Logger objects. Each logger keeps track of a log level that it is interested in, and discards log requests that are below this level.

Loggers are normally named entities, using dot-separated names such as "java.awt". The namespace is hierarchical and is managed by the LogManager. The namespace should typically be aligned with the Java packaging namespace, but is not required to follow it slavishly. For example, a Logger called "java.awt" might handle logging requests for classes in the java.awt package, but it might also handle logging for classes in sun.awt that support the client-visible abstractions defined in the java.awt package.

In addition to named Loggers, it is also possible to create anonymous Loggers that don't appear in the shared namespace. See section 1.14.

Loggers keep track of their parent loggers in the logging namespace. A logger's parent is its nearest extant ancestor in the logging namespace. The root Logger (named "") has no parent. Anonymous loggers are all given the root logger as their parent. Loggers may inherit various attributes from their parents in the logger namespace. In particular, a logger may inherit:

1.4 Logging Methods

The Logger class provides a large set of convenience methods for generating log messages. For convenience, there are methods for each logging level, named after the logging level name. Thus rather than calling "logger.log(Level.WARNING,..." a developer can simply call the convenience method "logger.warning(..."

There are two different styles of logging methods, to meet the needs of different communities of users.

First, there are methods that take an explicit source class name and source method name. These methods are intended for developers who want to be able to quickly locate the source of any given logging message. An example of this style is:

void warning(String sourceClass, String sourceMethod, String msg);

Second, there are a set of methods that do not take explicit source class or source method names. These are intended for developers who want easy-to-use logging and do not require detailed source information.

void warning(String msg);

For this second set of methods, the Logging framework will make a "best effort" to determine which class and method called into the logging framework and will add this information into the LogRecord. However, it is important to realize that this automatically inferred information may only be approximate. The latest generation of virtual machines perform extensive optimizations when JITing and may entirely remove stack frames, making it impossible to reliably locate the calling class and method.

1.5 Handlers

Java SE provides the following Handlers:

It is fairly straightforward to develop new Handlers. Developers requiring specific functionality can either develop a Handler from scratch or subclass one of the provided Handlers.

1.6 Formatters

Java SE also includes two standard Formatters:

As with Handlers, it is fairly straightforward to develop new Formatters.

1.7 The LogManager

There is a global LogManager object that keeps track of global logging information. This includes:

There is a single LogManager object that can be retrieved using the static LogManager.getLogManager method. This is created during LogManager initialization, based on a system property. This property allows container applications (such as EJB containers) to substitute their own subclass of LogManager in place of the default class.

1.8 Configuration File

The logging configuration can be initialized using a logging configuration file that will be read at startup. This logging configuration file is in standard java.util.Properties format.

Alternatively, the logging configuration can be initialized by specifying a class that can be used for reading initialization properties. This mechanism allows configuration data to be read from arbitrary sources, such as LDAP, JDBC, etc. See the LogManager API Specification for details.

There is a small set of global configuration information. This is specified in the description of the LogManager class and includes a list of root-level Handlers to install during startup.

The initial configuration may specify levels for particular loggers. These levels are applied to the named logger and any loggers below it in the naming hierarchy. The levels are applied in the order they are defined in the configuration file.

The initial configuration may contain arbitrary properties for use by Handlers or by subsystems doing logging. By convention these properties should use names starting with the name of the handler class or the name of the main Logger for the subsystem.

For example, the MemoryHandler uses a property "java.util.logging.MemoryHandler.size" to determine the default size for its ring buffer.

1.9 Default Configuration

The default logging configuration that ships with the JRE is only a default, and can be overridden by ISVs, system admins, and end users.

The default configuration makes only limited use of disk space. It doesn't flood the user with information, but does make sure to always capture key failure information.

The default configuration establishes a single handler on the root logger for sending output to the console.

1.10 Dynamic Configuration Updates

Programmers can update the logging configuration at run time in a variety of ways:

1.11 Native Methods

There are no native APIs for logging.

Native code that wishes to use the Java Logging mechanisms should make normal JNI calls into the Java Logging APIs.

1.12 XML DTD

The XML DTD used by the XMLFormatter is specified in Appendix A.

The DTD is designed with a "<log>" element as the top-level document. Individual log records are then written as "<record>" elements.

Note that in the event of JVM crashes it may not be possible to cleanly terminate an XMLFormatter stream with the appropriate closing </log>. Therefore tools that are analyzing log records should be prepared to cope with un-terminated streams.

1.13 Unique Message IDs

The Java Logging APIs do not provide any direct support for unique message IDs. Those applications or subsystems requiring unique message IDs should define their own conventions and include the unique IDs in the message strings as appropriate.

1.14 Security

The principal security requirement is that untrusted code should not be able to change the logging configuration. Specifically, if the logging configuration has been set up to log a particular category of information to a particular Handler, then untrusted code should not be able to prevent or disrupt that logging.

A new security permission LoggingPermission is defined to control updates to the logging configuration.

Trusted applications are given the appropriate LoggingPermission so they can call any of the logging configuration APIs. Untrusted applets are a different story. Untrusted applets can create and use named Loggers in the normal way, but they are not allowed to change logging control settings, such as adding or removing handlers, or changing log levels. However, untrusted applets are able to create and use their own "anonymous" loggers, using Logger.getAnonymousLogger. These anonymous Loggers are not registered in the global namespace and their methods are not access-checked, allowing even untrusted code to change their logging control settings.

The logging framework does not attempt to prevent spoofing. The sources of logging calls cannot be determined reliably, so when a LogRecord is published that claims to be from a particular source class and source method, it may be a fabrication. Similarly, formatters such as the XMLFormatter do not attempt to protect themselves against nested log messages inside message strings. Thus, a spoof LogRecord might contain a spoof set of XML inside its message string to make it look as if there was an additional XML record in the output.

In addition, the logging framework does not attempt to protect itself against denial of service attacks. Any given logging client can flood the logging framework with meaningless messages in an attempt to conceal some important log message.

1.15 Configuration Management

The APIs are structured so that an initial set of configuration information is read as properties from a configuration file. The configuration information may then be changed programatically by calls on the various logging classes and objects.

In addition, there are methods on LogManager that allow the configuration file to be re-read. When this happens, the configuration file values will override any changes that have been made programatically.

1.16 Packaging

All of the logging class are in the java.* part of the namespace, in the java.util.logging package.

1.17 Localization

Log messages may need to be localized.

Each Logger may have a Resource Bundle name associated with it. The corresponding Resource Bundle can be used to map between raw message strings and localized message strings.

Normally localization will be performed by Formatters. As a convenience, the formatter class provides a formatMessage method that provides some basic localization and formatting support.

1.18 Remote Access and Serialization

As with most Java platform APIs, the logging APIs are designed for use inside a single address space. All calls are intended to be local. However, it is expected that some Handlers will want to forward their output to other systems. There are a variety of ways of doing this:

Some Handlers (such as the SocketHandler) may write data to other systems using the XMLFormatter. This provides a simple, standard, inter-change format that can be parsed and processed on a variety of systems.

Some Handlers may wish to pass LogRecord objects over RMI. The LogRecord class is therefore serializable. However there is a problem in how to deal with the LogRecord parameters. Some parameters may not be serializable and other parameters may have been designed to serialize much more state than is required for logging. To avoid these problems the LogRecord class has a custom writeObject method that converts the parameters to strings (using Object.toString()) before writing them out. See the LogRecord API Specification for details.

Most of the logging classes are not intended to be serializable. Both Loggers and Handlers are stateful classes that are tied into a specific virtual machine. In this respect they are analogous to the java.io classes, which are also not serializable.

2.0 Examples

2.1 Simple Use

The following is a small program that performs logging using the default configuration.

This program relies on the root handlers that were established by the LogManager based on the configuration file. It creates its own Logger object and then makes calls to that Logger object to report various events.

package com.wombat;
import java.util.logging.*;

public class Nose{
    // Obtain a suitable logger.
    private static Logger logger = Logger.getLogger("com.wombat.nose");
    public static void main(String argv[]) {
        // Log a FINE tracing message
        logger.fine("doing stuff");
        try{
            Wombat.sneeze();
        } catch (Exception ex) {
            // Log the exception
            logger.log(Level.WARNING, "trouble sneezing", ex);
        }
        logger.fine("done");
    }
}

2.2 Changing the Configuration

Here's a small program that dynamically adjusts the logging configuration to send output to a specific file and to get lots of information on wombats. The pattern "%t" means the system temporary directory.

public static void main(String[] args) {
    Handler fh = new FileHandler("%t/wombat.log");
    Logger.getLogger("").addHandler(fh);
    Logger.getLogger("com.wombat").setLevel(Level.FINEST);
    ...
}

2.3 Simple Use, Ignoring Global Configuration

Here's a small program that sets up its own logging Handler and ignores the global configuration.

package com.wombat;

import java.util.logging.*;

public class Nose {
    private static Logger logger = Logger.getLogger("com.wombat.nose");
    private static FileHandler fh = new FileHandler("mylog.txt");
    public static void main(String argv[]) {
        // Send logger output to our FileHandler.
        logger.addHandler(fh);
        // Request that every detail gets logged.
        logger.setLevel(Level.ALL);
        // Log a simple INFO message.
        logger.info("doing stuff");
        try {
            Wombat.sneeze();
        } catch (Exception ex) {
            logger.log(Level.WARNING, "trouble sneezing", ex);
        }
        logger.fine("done");
    }
}

2.4 Sample XML Output

Here's a small sample of what some XMLFormatter XML output looks like:
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!DOCTYPE log SYSTEM "logger.dtd">
<log>
<record>
  <date>2000-08-23 19:21:05</date>
  <millis>967083665789</millis>
  <sequence>1256</sequence>
  <logger>kgh.test.fred</logger>
  <level>INFO</level>
  <class>kgh.test.XMLTest</class>
  <method>writeLog</method>
  <thread>10</thread>
  <message>Hello world!</message>
</record>
</log>

3.0 Appendix A: DTD for XMLFormatter Output

<!-- DTD used by the java.util.logging.XMLFormatter -->
<!-- This provides an XML formatted log message. -->

<!-- The document type is "log" which consists of a sequence
of record elements -->
<!ELEMENT log (record*)>

<!-- Each logging call is described by a record element. -->
<!ELEMENT record (date, millis, sequence, logger?, level,
class?, method?, thread?, message, key?, catalog?, param*, exception?)>

<!-- Date and time when LogRecord was created in ISO 8601 format -->
<!ELEMENT date (#PCDATA)>

<!-- Time when LogRecord was created in milliseconds since
midnight January 1st, 1970, UTC. -->
<!ELEMENT millis (#PCDATA)>

<!-- Unique sequence number within source VM. -->
<!ELEMENT sequence (#PCDATA)>

<!-- Name of source Logger object. -->
<!ELEMENT logger (#PCDATA)>

<!-- Logging level, may be either one of the constant
names from java.util.logging.Level (such as "SEVERE"
or "WARNING") or an integer value such as "20". -->
<!ELEMENT level (#PCDATA)>

<!-- Fully qualified name of class that issued
logging call, e.g. "javax.marsupial.Wombat". -->
<!ELEMENT class (#PCDATA)>

<!-- Name of method that issued logging call.
It may be either an unqualified method name such as
"fred" or it may include argument type information
in parenthesis, for example "fred(int,String)". -->
<!ELEMENT method (#PCDATA)>

<!-- Integer thread ID. -->
<!ELEMENT thread (#PCDATA)>

<!-- The message element contains the text string of a log message. -->
<!ELEMENT message (#PCDATA)>

<!-- If the message string was localized, the key element provides
the original localization message key. -->
<!ELEMENT key (#PCDATA)>

<!-- If the message string was localized, the catalog element provides
the logger's localization resource bundle name. -->
<!ELEMENT catalog (#PCDATA)>

<!-- If the message string was localized, each of the param elements
provides the String value (obtained using Object.toString())
of the corresponding LogRecord parameter. -->
<!ELEMENT param (#PCDATA)>

<!-- An exception consists of an optional message string followed
by a series of StackFrames. Exception elements are used
for Java exceptions and other java Throwables. -->
<!ELEMENT exception (message?, frame+)>

<!-- A frame describes one line in a Throwable backtrace. -->
<!ELEMENT frame (class, method, line?)>

<!-- an integer line number within a class's source file. -->
<!ELEMENT line (#PCDATA)>