Stupid log4j tricks

Tagged:

I know the cool kids don't even use log4j anymore (at least not directly), but I still do (just habit, and it still seems to work just fine). Recently on a project we ran into an issue where log4j was configured twice. A developer had done the BasicConfigurator.configure() thing in a standalone file (not part of the main API), and there was also a log4j.xml on the classpath.

The result was more than one root logger and double log output.

The easy way to fix this is what I have always done over the years, kick anyone in the shin that provides a log4j config file WITH A LIBRARY. You can't do that man, you take away the control of the user of the library, and you can create all sorts of weird stuff. If your library JAR has a log4.xml (or log4j.properties) you will cause headaches. Turns out this simple "just don't do that" approach is a bit naive. There is more to this tale on a few fronts.

When you don't include any configuration for log4j, but do use it in a library, you still put a little burden on the users of your library. That is to say, you expect them to configure logging somehow, or they will see this (and we have all seen this):

log4j:WARN No appenders could be found for logger (YourLibraryClassHere).
log4j:WARN Please initialize the log4j system properly.


So what if you want to BOTH allow the user to configure their own logging settings if they want, and also not bother them at all if they don't. Well, it turns out there is a bit of a hack that seems to work pretty well for this.

. . .
private static Logger log;
 . . .
static {
      boolean rootIsConfigured = Logger.getRootLogger().getAllAppenders().hasMoreElements();
      if (!rootIsConfigured) {
         BasicConfigurator.configure();
         Logger.getRootLogger().setLevel(Level.INFO);
      } 
      log = Logger.getLogger(YourLibraryClassHere.class);
   }


What this essentially does is check if there are any root loggers, no matter how they might have been configured, and then only if and when there are none it does the BasicConfigurator thing. This allows users to setup the config if they desire, and also avoids the error message and still gives them some logging (to the console generally, but up to your library), if they don't.

There are a few downsides to this too. First your Logger isn't a constant anymore, and really it should be (normally you want private static final Logger LOG = Logger.getLogger(YourLibraryClassHere.class);). Second it's a little verbose.
Third, it's a hack. It assumes that anyone who does bother to configure log4j will make a root logger, and there may be rare cases where people don't do that (I have never seen one, but it could happen).

I might not use this for normal API classes, but it works well for command line main method type classes. And, I suppose it could be done in one "bootstrap" type class for your API too, if you really wanted to.

The idea here is to make it nice for the people, whether they want to configure the logging, or not.

(Credit where due, I originally found the jist of this approach here: http://marc.info/?l=log4j-user&m=102775658930710&w=2.)

Comments

Use a static factory method

You can put this in another class and use this to configure your logger.

Instead of using a basic configurator you can use a property configurator instead. This makes it easier to setup your logger.

Sample code below. I have used an internal flag and lock object to ensure the log4j system is only ever configured once.

import java.io.IOException;
import java.io.InputStream;
import java.util.Properties;

import org.apache.log4j.Logger;
import org.apache.log4j.PropertyConfigurator;

public class LoggerFactory {
private static boolean isConfigured = false;
private static Object lock = new Object();

public static Logger getLogger(Class<?> klass) {
if (!isConfigured) {
synchronized (lock) {
if (!isConfigured) {
boolean rootIsConfigured = Logger.getRootLogger().getAllAppenders().hasMoreElements();
if (!rootIsConfigured) {

//log4j setup
Properties p = new Properties();
try {
InputStream is = LoggerFactory.class.getResourceAsStream("com.acme.myapplication.default-log4j.properties");
p.load(is);
PropertyConfigurator.configure(p);
is.close();
} catch (IOException e) {
//can't do anything about this
}
}
}
isConfigured = true;
}
}
return Logger.getLogger(klass);
}
}


And in your class

public class Foobar {
private final static Logger log = LogFactory.getLogger(FooBar.class);
}

The point here wasn't how to

The point here wasn't how to configure logging, that can be done many ways, but rather checking whether or not logging is already configured. For that your code uses the same approach as the article:

"boolean rootIsConfigured = Logger.getRootLogger().getAllAppenders().hasMoreElements();"

Your factory does address the static Logger thing though, yes.

What do people use instead of log4j?

When you say "I know the cool kids don't even use log4j anymore", what do people use instead these days?

java.util.logging logback etc

java.util.logging

logback

etc

Comment viewing options

Select your preferred way to display the comments and click "Save settings" to activate your changes.