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

Is or Uses?

Let's look at a class that attempts to solve these configuration problems. There is an important design decision to make before starting to code.
In general, you'd like your logger to work just like the standard log4j Logger. Here's how the standard Logger works:

That is, the MyLogger class would define a staticgetLogger(String name) method that
returned one of your magic loggers instead of a lowly log4j logger, but your logger would
extend the standard Logger class.

Unfortunately, you can't do that.

The problem is that log4j guarantees that every time you request a logger using a given name,
you get the same logger, even of those requests are made from different files. The log4j subsystem keeps
a global cache of loggers, indexed by logger name (which usually is, but doesn't have to be, the class name),
and getLogger() always returns the logger from the cache if one's there (otherwise, it creates one).

Imagine, then, that we have the following line in one file (which happens to load first):

private static final Logger log = Logger.getLogger("my.logger.name");

and the following line in a different file (which happens to load after the first file):

The second call must return the same logger that the first call returns. The problem is the following
line of code, which fails with a ClassCastException because log points at the Logger object created by
the first call.

MyLogger myLog = (MyLogger) log2;

By the same token, you'll get the ClassCastException even if you get your logger like this:

(because getLogger must still return the org.apache.log4j.Logger that was created first, and the assignment won't work because of the JVM effectively checks the types at runtime when it does the assignment. In this situation, you just can't create a MyLogger that has the same name as an existing standard Logger.

You can't solve this problem, but you can work around it by using a wrapping strategy rather than extension — a
uses relationship rather than an is relationship. My logger is an independent class that extends nothing, but it implements all the methods of org.apache.log4j.Logger, so anybody who's used to log4j will be comfortable with it. My logger contains an org.apache.log4j.Logger, and delegates all the logging operations
to the contained object, so my logger can happily interact with other log4j loggers.
You can't, however, convert one of my loggers to a log4j Logger with a simple cast operation — the two classes are in no way related.

Though it's an accepted design principle that "uses" is better than "is,"
the implementation that you're forced to use by log4j makes that decision more difficult than necessary — log4j
is missing an essential interface.
Ideally, the log4j Logger and my extended version would implement a common interface
so that I can use them interchangeably.
The Log4j subsystem defines no such interface, however.

Let's look at the issue in depth.
The correct way to replace derivation with encapsulation is the Decorator design pattern.
Think about how the Java I/O system works. Though you could use extension
(A PrintWriter could extend BufferedWriter, which could extend FileWriter, which could extend Writer),
but the number of classes required to get every combination that you can get with the current system
would exploded (the class count is doubled with every feature).
Java's wrapping strategy (which is a classic Decorator) works better;
you can pick and choose the classes you put into the layers.

One of the important uses of Decorator is to replace extension (is) with wrapping (uses),
and that's what log4j should have done.
In Decorator, you have a common interface that's implemented by a core class (FileWriter):

This strategy lets us pass both a stock FileWriter and a custom MyGoodWriter to any method that
takes a Writer argument, just as if we had used extension. You can't, however, pass a
MyGoodWriter to a method that requires a FileWriter.
All design patterns have limitations, but this one isn't much of a problem in practice.

Unfortunately, org.apache.log4j.Logger does not implement any interfaces at all,
so I can't leverage Decorator to clean up the code.

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!