Java: Custom logger factory that automatically infers the class name

In this post I show how you can declare loggers like this:

1
2
3
public class MyService {
    private static final Logger LOG = MyLoggerFactory.getLogger();
}

There is no argument to the MyLoggerFactory::getLogger method. Contrast that to the normal way to declare a logger:

1
2
3
public class MyService {
    private static final Logger LOG = LoggerFactory.getLogger(MyService.class);
}

This is business as usual. But have you ever made that silly mistake where you copy/paste a class - and then, in the new class, forget to change the argument to that logger? It can be horribly misleading when reading log files afterwards.

The custom MyLoggerFactory::getLogger method is really super simple to implement: 3 lines of meat.

UPDATE: Alternative to the custom factory

On Reddit, while announcing this post, I was made aware of a simple alternative to the custom logger factory technique described in this post:

1
2
3
public class MyService {
    private static final Logger LOG = LoggerFactory.getLogger(MethodHandles.Lookup.lookupClass());
}

That technique uses a plain standard Java SE API to get the class name - see [1]. Personally I think this is a clever technique as well!

I guess it is a matter of personal preferences to decide which logger declaration you want to use. If you still find the logger factory compelling - then read on…

Implementation

I have prepared an example on GitHub - consult that to see the code in its entirety and full context. But here it goes, using SLF4j/Logback:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
package com.never.that.copy.paste.mistake.again;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

public class MyLoggerFactory {

    public static Logger getLogger() {

        StackTraceElement[] stackTrace = Thread.currentThread().getStackTrace();

        String callersClassName = stackTrace[2].getClassName();

        return LoggerFactory.getLogger(callersClassName);
    }
}

3 lines of meat. The magic number 2 represents the class calling MyLoggerFactory::getLogger. The first positions in the stackTrace array represents the invocation to the Thread::getStackTrace method as well as this MyLoggerFactory::getLogger method itself.

I chose to name the method getLogger here, so that it aligns with the underlying logging framework SLF4j/Logback.

Please note, that no code here is specific to SLF4j/Logback: So go ahead and implement a corresponding factory for your own favorite logging framework.

The example on GitHub has a very limited JUnit test that shows it works as expected:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
package com.never.that.copy.paste.mistake.again;

import org.junit.Test;
import org.slf4j.Logger;

import static org.junit.Assert.assertEquals;

public class MyLoggerFactoryTests {

    @Test
    public void getLogger_whenGivenNothing_thenReturnsLoggerWithCallersClassName() {

        // Given
        // ( a little bit of magic ... )

        // When
        Logger loggerOne = MyLoggerFactory.getLogger();
        Logger loggerTwo = LoggerTester.LOG;

        // Then
        assertEquals(MyLoggerFactoryTests.class.getName(), loggerOne.getName());
        assertEquals(LoggerTester.class.getName(), loggerTwo.getName());
    }

    private static class LoggerTester {
        private static final Logger LOG = MyLoggerFactory.getLogger();
    }

}

That’s all there is to it.

References

[1] Java SE’s MethodHandles API