Like Falling Off a Log

In many Java programming projects, logging is one of those "eat your vegetables" kind of tasks: you have every every intention of doing it, but somehow never manage to. Instead, if any logging is in your code, it's the remnants of a few System.out.println() statements you forgot to remove after debugging a class.

In many Java programming projects, logging is one of those “eat your vegetables” kind of tasks: you have every every intention of doing it, but somehow never manage to. Instead, if any logging is in your code, it’s the remnants of a few System.out.println() statements you forgot to remove after debugging a class.

Even if you consider logging “spaghetti code debugging”, logging does have its place. In some situations, it is difficult, sometimes impossible, to use traditional debugging techniques. Unfortunately, putting temporary code into a Java program is normally problematic. After all, Java doesn’t support conditional compilation.

(If you really want temporary code in your Java class, try this gimmick: put logging statements inside an if block with a static Boolean variable as the condition. While you’re debugging, set the variable to true; when you’re done, set it to false. Optimizing compilers can figure out that the code in the if statement can never be executed, and will omit it from the final bytecode.)

Jakarta (http://jakarta.apache.org), a Java development project of the Apache Software Foundation (best known for its Tomcat Web server and Ant build tool), offers a set of classes that makes logging in Java easy. Log4j, which is distributed under the Apache Software License, is a six-year-old effort to enable logging in Java. Log4j supports a large number of output formats, does not significantly impact the performance of your code, and logging can be turned on or off at runtime.

Apache recently released version 1.2.3 of Log4j, adding support for Java 2 version 1.4. Let’s jump right in and see how to use Log4j.

No Excuses

The Log4j package is aimed at programmers who want to include well-organized logging capabilities, but don’t want to sprinkle lots of cumbersome new code throughout their Java classes. Log4j makes logging as painless as possible. The developers, led by founder Ceki Gülcü, designed Log4j to address all of the concerns (and excuses) that keep programmers from integrating logging into their classes.

  • Log4j was designed with speed a higher priority than flexibility. Indeed, several times during its history, the Log4j components have been rewritten to increase performance. On the benchmark machine referenced by the Log4j developers — a 233 MHz IBM ThinkPad running Java 1.1.7B — the time required to evaluate and skip a logging statement (when logging is disabled at runtime) is 46 nanoseconds. When logging is enabled, the time required to log a message ranges from 79 to 164 millionths of a second; the time to print an exception clocks in at “around a millisecond,” according to the Log4j documentation.

  • The entry point for Log4j is simple to learn and extremely useful. You can begin reaping the benefits of logging by exploring just a few of its core classes and features.

  • Log4j is designed so you can easily produce the logging information you want and turn it off everywhere else.

Logging can take place in one class and be inherited or overridden in its subclasses. The design of Log4j encourages this object-oriented approach — you create an object of the Logger class and relate it to a specific class that requires logging. If subclasses require different kinds of logging — or no logging at all — you can create a child Logger object with different behavior.

Loggers Ahead

The Logger class in the org.apache.log4j package is the core of Log4j. You can start using logs immediately if you learn how to work with Logger and two other parts of the package: the Layout class (and its subclasses) and the Appender interface.

To create a Logger object, you call one of two class methods: Logger.getRootLogger() or Logger.getLogger(String).

Loggers are organized into parent-child relationships based on their names. You specify the name of a logger as the argument to getLogger(String) and use similar names to define a hierarchy that acts like a class hierarchy. If one logger’s name is a prefix to another logger’s name, the first logger is an ancestor of the other. For an example, see Figure One.

Figure One: Creating a hierarchy of Loggers

Logger log1 = Logger.getLogger(“org.cadenhead.web”);
Logger log2 = Logger.getLogger(“org.cadenhead.web.rss”);
Logger log3 = Logger.getLogger(“org.cadenhead.web.rss.v092″);
Logger log4 = Logger.getLogger(“org.cadenhead.web.scrape”);

The logger named org.cadenhead.web is the parent of two child loggers: org.cadenhead.web.rss and org.cadenhead. web.scrape. The logger named org.cadenhead.web.rss is the parent of org.cadenhead.web.rss.v092.

There’s also a root logger that doesn’t have a name but is the parent of all other loggers. The Logger.getRootLogger() method is used to retrieve a reference to the root logger.

Calls to getLogger(String) with the same name will always return the same object. So, you can have two methods that both make use of the same logger, as in Figure Two. The variables alpha and beta refer to the same Logger object.

Figure Two: alpha and beta refer to the same Logger

private void loadData() {
Logger alpha = Logger.getLogger(“org.cadenhead.web.scrape”);

private void analyzeData() {
Logger beta = Logger.getLogger(“org.cadenhead.web.scrape”);

After you create a logger, you can assign it a priority level using one of the class variables of the Level class: DEBUG, INFO, WARN, ERROR, or FATAL. Call the logger’s setLevel(Level) method, as in this example:

Logger log5 = Logger.getLogger
log5.setLevel( (Level) Level.INFO );

The priority level determines the kind of logging events the logger will report and the events it will ignore. A logger with priority ERROR will report ERROR and FATAL logging events. A logger with priority INFO will report INFO, WARN, ERROR, and FATAL events. A logger with priority DEBUG will report everything.

If a logger is not assigned a priority level, it inherits the level of its closest ancestor. So if a logger named org.cadenhead.web has the priority ERROR, a logger named org.cadenhead.web.rss will inherit that priority unless another one is set up by calling setLevel().

To log events, you call one of the following methods with a message to add to the log as the only argument:

public void debug(Object)
public void info(Object)
public void warn(Object)
public void error(Object)
public void fatal(Object)

The Object used as an argument to these methods is usually a String, though support for other kinds of objects is offered in Log4j. The following statement tells the Log5 logger object created earlier to log two messages:

log5.debug(“Reading the XML file here.”);
log5.info(“The value of recCount: ” + recCount);

Because log5 was set up with INFO priority, only Reading the XML file here. will be logged; the call to debug() is ignored.


A Logger object can log messages to one or more destinations (file, console, etc.) Destinations are represented by classes that implement the Appender interface.

Log4j offers Appenders that log to the console (Console Appender), text files (FileAppender and Rolling FileAppender), e-mail (SMTPAppender), database files (JDBCAppender), syslog daemons (SyslogAppender), socket servers (SocketAppender), the Java Message Service (JMSAppender), and others. There’s even an Appender that emulates /dev/null, called NullAppender.

To add a destination to a Logger, call its addAppender (Appender) method. Log events will be sent to every appender associated with a logger.

Appenders also are inherited. If you associate a File Appender with the root logger, all loggers will log to that file (though some will be ignored depending on the priority of the logger).


The format of a log message is determined by the Layout object that you specify when adding an Appender to a Logger.

The Layout class in the org.apache.log4j package is the superclass of all layouts supported by Log4j. The most basic kind of layout is SimpleLayout, which displays the priority of the event followed by a hyphen and the message. For instance, SimpleLayout produces:

FATAL – Core breach in Sector 12!

In Figure Three, statements create a logger with simple layout that saves events to a file called /var/log/app.log. The logger named application will append log events to the file /var/log/app.log. If you also create loggers named application.send and application.queue, they will inherit this behavior and will log events to the same place.

Figure Three: Associating a format and an external file

Logger appLogger = Logger.getLogger(“application”);
SimpleLayout appLayout = new SimpleLayout();
FileAppender logFile = new FileAppender(appLayout, “app.log”);


Though you can set a logger’s priority level programatically, this forces you to edit and recompile your code every time you want to see more or less logging. (According to Log4j’s developers, programs that support logging devote an average of 4% of their code to this purpose. The time required to change it can add significantly to a project.)

A more flexible and powerful approach is to use configuration files to configure your loggers. Log4j can be configured using XML and a Java properties file.

Figure Four shows lines in a properties file which configure a console logger that only reports WARN, ERROR, and FATAL log events.

Figure Four: Properties to configure a console log

log4j.rootLogger=WARN, APPENDER1

Figure Five shows lines which configure a logger that saves to a file called rss_log reports all log events.

Figure Five: Properties to configure a file log

log4j.logger.org.cadenhead.rss=DEBUG, APPENDER2

To make your loggers use a properties file, call the Property Configurator.configure(String) class method with the name of the property file as an argument. Figure Six contains an example program (Demo.java) and properties file (demo.properties) to tie all of this together.

Figure Six: A sample class that uses Log4J and is configured witth a properties file

import org.apache.log4j.*;

public class Demo {
public Demo() {
DemoData data = new DemoData();
Logger logger1 = Logger.getLogger(“demo”);
logger1.info(“Adding values …”);
int result = data.value1 + DemoChange.flip(data.value2);

public static void main(String[] arguments) {
Demo d = new Demo();

class DemoData {
int value1;
int value2;

DemoData() {
Logger logger2 = Logger.getLogger(“demo.demoData”);
value1 = 6;
value2 = 4;
logger2.debug(“Value1 is ” + value1);
logger2.debug(“Value2 is ” + value2);

class DemoChange {
static int flip(int input) {
Logger logger3 = Logger.getLogger(“demo.demoChange”);
logger3.warn(“The input value is ” + (input * -1));
return input * -1;


log4j.logger.demo=INFO, DEMO2


log4j.logger.demo.demoChange=INFO, DEMO3


If you implement this project, run it a few times with different priority levels in the properties file. As written, the file demo.log will contain the messages Adding values… and The input value is -4. The console will also display The input value is -4. Other logging statements will be ignored.

Java Logging API

Java 2 Version 1.4 introduced the Java Logging API. While similar to log4j, and as easy to learn as log4j, the java.util. logging package is different. If you have experience with both packages, please send in your comments.

Let us know if you’re “eating vegetables” grown in the Sun or by Apache.

Rogers Cadenhead is a Web application developer and the coauthor of Teach Yourself Java 2 in 21 Days, Third Edition from Sams Publishing. To contact him, visit his Web site at http://www.cadenhead.info.

Comments are closed.