JVM Languages

Custom Configuring Java Apps: Extending log4j

By Allen Holub, March 27, 2012

Creating an extended, server-side version of a log4j Logger that automatically configures itself and adds printf-like logging methods to the standard Logger

Last month, I introduced the notion of using a Java enum to solve
the location-based configuration problem. This article continues that discussion, and also puts last month's class to use by building a utility class that I'll be using in future articles: an extended version of a log4j Logger that automatically configures itself and adds
printf()-like logging methods to the standard Logger. Along the way, I'll discuss several
design issues that should be of interest, even if you're not building your own loggers. I'll be using Apache Tomcat for this exposition.

How (and When) Do You Find a Configuration File?

Server-side log4j configuration is tough to do right. Log4j's configuration problems are far from rare, however, so it's a good platform for discussing the configuration issue as a whole.

By default, log4j looks for its configuration file (either
log4j.properties or log4j.xml) in two places:

The directory (or URL) specified in the System property log4j.configuration, either created
in your program using System.setProperty("log4j.configuration","/file/location") or specified
with the java -Dlog4j.configuration=/file/location command-line argument.

On the classpath (as defined by whatever class loader pulled log4j into the program. In Tomcat, this is the WEB-INF/classes directory in your WAR file, not the CLASSPATH environment). Other servers have other rules.

In the case of Tomcat, -D on the command line does work, but you will have to modify
your out-of-the-box startup script to handle it.
You can also put a -D argument into the TOMCAT_OPTS environment variable.
If neither of the automatic approaches work, you can configure the system by writing your own code
to do it. A servlet, for example, could configure log4j in the HttpServlet's
init() method or in a ServletContextListener object. We'll look at an example in a moment.

There are show-stopper problems with all these possibilities, however,
particularly with Web servers such as Tomcat.

First, a given Web server typically hosts many Web applications, and these
applications typically have very different requirements for logging
configuration (at minimum, each application will probably want its
own, uniquely named, log file), so using -D won't work because
the resulting value is global across all applications. The same issues apply
to an environment variable.

Next, you have no control over the classpath — Tomcat effectively
limits the classpath to your Web app's WAR file — so classpath-based
configuration is unworkable.

A given application may be installed on many servers, and
each of those servers might have different configuration requirements.
You really don't want your installation process to require that you
dig into an application's WAR file to modify logging configuration,
and modifications you might make to automatically expanded WARs will be wiped out on the next deployment.

Configuration should happen outside the application entirely, so that you
can define a unique version on each server without modifying the WAR.

Finally, configuration often fails because the bit of code that performs the configuration runs at the wrong time. For example, consider a static field in a servlet that's initialized at compile time:

If the constructor logs something, that logging call will most likely be done
before your servlet's init() method or ServletContextListener object runs, so you're doomed
if you're configuring log4j in either of those places. Log4j just shuts down (permanently) when you try to log before configuring the system, so this innocuous declaration effectively disables logging.

has the same problem. The configuration does happen before any instances of the class are created, but there's no telling when this code will execute relative to other static initializers in other class definitions, and what if one of those initializers reconfigures the system?

This order-of-initialization issue is a particular problem when multiple instances of your Web app are running simultaneously, which can happen when you're providing a customized service to several clients. You might deploy the same Web app many times, with slightly different names given to each WAR file, for example. Ideally, you'd pull configuration information out of a directory that was named after the URL used to access the application, but that's particularly tricky to do because the URL isn't available to a servlet until you actually receive a message. (It's in the
HttpServletRequest object), which is way too late to be useful. The Web app (context) name is available in the ServletContextListener, however, so you can
configure there; but this approach still doesn't solve the static initializer
problem.

So, given that all the standard ways of doing things fail, we're left with the Places class that I presented last month, at least for finding a default
log4j.properties file at a known location outside the WAR file.
(I put the configuration file in Places.CONFIG.file("log4j.properties")).

We still have the static-initializer problem, however. That is, it simply
shouldn't be possible to log a message unless log4j has been configured properly, even if we're logging from a static initializer. I've solved that problem by writing my own version of the Logger class
(Listing One), which leverages the Singleton design pattern
to guarantee configuration.

Now I'll demonstrate some of the approaches you can take to implement a self-configuring log4j Logger.

Dr. Dobb's encourages readers to engage in spirited, healthy debate, including taking us to task.
However, Dr. Dobb's moderates all comments posted to our site, and reserves the right to modify or remove any content that it determines to be derogatory, offensive, inflammatory, vulgar, irrelevant/off-topic, racist or obvious marketing or spam. Dr. Dobb's further reserves the right to disable the profile of any commenter participating in said activities.

Video

This month's Dr. Dobb's Journal

This month,
Dr. Dobb's Journal is devoted to mobile programming. We introduce you to Apple's new Swift programming language, discuss the perils of being the third-most-popular mobile platform, revisit SQLite on Android
, and much more!