Java logger that automatically determines caller's class name

73,663

Solution 1

I guess it adds a lot of overhead for every class. Every class has to be 'looked up'. You create new Throwable objects to do that... These throwables don't come for free.

Solution 2

The MethodHandles class (as of Java 7) includes a Lookup class that, from a static context, can find and return the name of the current class. Consider the following example:

import java.lang.invoke.MethodHandles;

public class Main {
  private static final Class clazz = MethodHandles.lookup().lookupClass();
  private static final String CLASSNAME = clazz.getSimpleName();

  public static void main( String args[] ) {
    System.out.println( CLASSNAME );
  }
}

When run this produces:

Main

For a logger, you could use:

private static Logger LOGGER = 
  Logger.getLogger(MethodHandles.lookup().lookupClass().getSimpleName());

Solution 3

Creating a stack trace is a relatively slow operation. Your caller already knows what class and method it is in, so the effort is wasted. This aspect of your solution is inefficient.

Even if you use static class information, you should not fetch the Logger again for each message. From the author of Log4j,Ceki Gülcü:

The most common error in wrapper classes is the invocation of the Logger.getLogger method on each log request. This is guaranteed to wreak havoc on your application's performance. Really!!!

This is the conventional, efficient idiom for getting a Logger is during class initialization:

private static final Logger log = Logger.getLogger(MyClass.class);

Note that this gives you a separate Logger for each type in a hierarchy. If you come up with a method that invokes getClass() on an instance, you will see messages logged by a base type showing up under the subtype's logger. Maybe this is desirable in some cases, but I find it confusing (and I tend to favor composition over inheritance anyway).

Obviously, using the dynamic type via getClass() will require you to obtain the logger at least once per instance, rather than once per class like the recommended idiom using static type information.

Solution 4

We actually have something quite similar in a LogUtils class. Yes, it's kind of icky, but the advantages are worth it as far as I'm concerned. We wanted to make sure we didn't have any overhead from it being repeatedly called though, so ours (somewhat hackily) ensures that it can ONLY be called from a static initializer context, a la:

private static final Logger LOG = LogUtils.loggerForThisClass();

It will fail if it's invoked from a normal method, or from an instance initializer (i.e. if the 'static' was left off above) to reduce the risk of performance overhead. The method is:

public static Logger loggerForThisClass() {
    // We use the third stack element; second is this method, first is .getStackTrace()
    StackTraceElement myCaller = Thread.currentThread().getStackTrace()[2];
    Assert.equal("<clinit>", myCaller.getMethodName());
    return Logger.getLogger(myCaller.getClassName());
}

Anyone who asks what advantage does this have over

= Logger.getLogger(MyClass.class);

has probably never had to deal with someone who copies and pastes that line from somewhere else and forgets to change the class name, leaving you dealing with a class which sends all its stuff to another logger.

Solution 5

Assuming you are keeping static refs to the loggers, here's a standalone static singleton:

public class LoggerUtils extends SecurityManager
{
    public static Logger getLogger()
    {
        String className = new LoggerUtils().getClassName();
        Logger logger = Logger.getLogger(className);
        return logger;
    }

    private String getClassName()
    {
        return getClassContext()[2].getName();
    }
}

Usage is nice and clean:

Logger logger = LoggerUtils.getLogger();
Share:
73,663
yanchenko
Author by

yanchenko

Updated on June 21, 2020

Comments

  • yanchenko
    yanchenko almost 4 years
    public static Logger getLogger() {
        final Throwable t = new Throwable();
        final StackTraceElement methodCaller = t.getStackTrace()[1];
        final Logger logger = Logger.getLogger(methodCaller.getClassName());
        logger.setLevel(ResourceManager.LOGLEVEL);
        return logger;
    }
    

    This method would return a logger that knows the class it's logging for. Any ideas against it?

    Many years later: https://github.com/yanchenko/droidparts/blob/master/droidparts/src/org/droidparts/util/L.java