Custom log4j2 Logger interface


Log4j2 already supports a lot of use cases out of the box. But let’s assume you need to alter the interface of your logger. A new log level for example, or a custom method. This article will describe how you can easily create your own custom logger interface.

In our example we will create a custom interface that will not only print the message but also a specific error number in case of an error. This is a common use case. A lot of companies have numbers attached to their error messages to identify and track the reason behind an error. It would make sense to add those error numbers not only to the output message but also to the log itself. Therefore we need a custom interface with the error number as parameter.

public void error(int number, String text);

The most obvious way to achieve this is by wrapping the default logger into a custom class.

import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;

public class MyLogger {

    // DON'T DO THIS!
    private final Logger logger;

    public MyLogger(String name) {
        this.logger = LogManager.getLogger(name);
    }

    public MyLogger(Class<?> clazz) {
        this(clazz.getName());
    }

    public void error(int number, String text) {
        this.logger.info(String.format("ERROR-%s %s", number, text));
    }
}

This seems to work, but causes trouble with line numbers if we add them to our logger output. Our pattern here is %d %p %class{1.} [%t] %location %m %ex%n.

public class Main {

    private static final MyLogger log = new MyLogger(Main.class);

    public static void main(String[] args) {
        log.error(1, "First log");
        log.error(2, "Second log");
    }
}
2021-11-22 14:39:49,003 INFO MyLogger [main] MyLogger.error(MyLogger.java:18) ERROR-1 First log 
2021-11-22 14:39:49,005 INFO MyLogger [main] MyLogger.error(MyLogger.java:18) ERROR-2 Second log 

Both log lines appear to be in line 18 although our custom logger was called once in line 6 and once in line 7. To fix this we need to dive a little bit deeper into the log mechanism of log4j2.

Log4j2 will find line numbers and class names by analyzing the current stack, and will use the last ones found before the logger itself. If we wrap the logger within our MyLogger class, log4j2 will use our wrapper as reference. To fix this we have to track the fully qualified class name (FQCN). This can be done by passing the FQCN of our wrapper to the logIfEnabled-methods from the ExtendedLogger interface.

import org.apache.logging.log4j.Level;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.apache.logging.log4j.spi.ExtendedLogger;

import java.io.Serializable;

public final class MyLogger implements Serializable {
    private static final String FQCN = MyLogger.class.getName();
    private final ExtendedLogger logger;

    private MyLogger(final Logger logger) {
        this.logger = (ExtendedLogger) logger;
    }

    public MyLogger(final String name) {
        this(LogManager.getLogger(name));
    }

    public MyLogger(final Class<?> clazz) {
        this(clazz.getName());
    }

    public void error(int number, String text) {
        this.logger.logIfEnabled(FQCN, Level.ERROR, null, String.format("ERROR-%s %s", number, text));
    }
}

The log lines should now look like this:

2021-11-22 15:51:48,246 ERROR Main [main] Main.main(Main.java:6) ERROR-1 First log
2021-11-22 15:51:48,248 ERROR Main [main] Main.main(Main.java:7) ERROR-2 Second log

We can now also add custom Log levels to our logger.

import org.apache.logging.log4j.Level;
...
private static final Level SWEET = Level.forName("SWEET", 350);
...
public void sweet(String text) {
    this.logger.logIfEnabled(FQCN, SWEET, null, text);
}

The default levels are

LevelInt value
OFF0
FATAL100
ERROR200
WARN300
INFO400
DEBUG500
TRACE600
ALLInteger.MAX_VALUE

TLDR

If you just need a template wrapper you can customize by your own and don’t care about 1000 lines of code you can call the CustomLogGenerator method provided by log4j2. It will generate a custom logger for you. The jar file can be downloaded here.

java -cp .\log4j-core-2.14.1.jar org.apache.logging.log4j.core.tools.CustomLoggerGenerator MyLogger ERROR=200 SWEET=350 > MyLogger.java

More in detail information can be found on the apache homepage.