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 LoggerLogger
class MyClass
{
private static final Logger log = Logger.getLogger(MyClass.class);
//...
f()
{ log.debug( "hello world" );
}
}
and in an ideal world, you'd use your own logger in an identical way:
class MyClass
{
private static final Logger log = MyLogger.getLogger(MyClass.class);
//...
f()
{ log.debug( "hello world" );
ExtendedLogger elog = (ExtendedLogger) log; // use an extended feature
elog.myExtendedFeature( "abcd" );
}
That is, the MyLoggerstaticgetLogger(String name)Logger
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()
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):
private static final Logger log2 = MyLogger.getLogger("my.logger.name");
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 ClassCastExceptionlogLogger
MyLogger myLog = (MyLogger) log2;
By the same token, you'll get the ClassCastException
private static final MyLogger log = MyLogger.getLogger(MyClass.class);
(because getLoggerorg.apache.log4j.LoggerMyLoggerLogger
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.Loggerorg.apache.log4j.LoggerLogger
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
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 PrintWriterBufferedWriterFileWriterWriter
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
interface Writer
{ void write( String buffer );
//...
}
class FileWriter implements Writer
{ @Override void write( String buffer );
{ // implementation goes here
}
//...
}
You could (but shouldn't) extend the implementation class
class MyBadWriter extends FileWriter // NO! NO! NO!
{ @Override void write( String buffer );
{
//...
super.write( buffer );
}
//...
}
but you can achieve exactly the same effect by implementing the common interface and encapsulating the object that would otherwise be the base class:
class MyGoodWriter implements Writer
{
private Writer wrapped;
public MyGoodWriter( Writer wrapped )
{ this.wrapped = wrapped; // or create one locally
}
@Override void write( String buffer );
{ //...
wrapped.write( buffer );
}
//...
}
This strategy lets us pass both a stock FileWriterMyGoodWriterWriterMyGoodWriterFileWriter
Unfortunately, org.apache.log4j.Logger


