DEV Community

loading...
Cover image for How to make a custom message converter for Log4J2

How to make a custom message converter for Log4J2

pazvanti profile image pazvanti ・4 min read

Article originally posted on my personal website under Building a custom converter in Log4j2

I’ve been using Log4J for many years. It is a powerful logging library that is efficient and highly customizable. You can extend the functionality with ease and do custom actions on the message prior to it being logged. Without too much chit-chat, in this article I will be showing you how to implement a custom converter for Log4J2.

What is a converter?

Log4J2 has multiple components that are called when a message is logged. You have the actual logger, which you call with the raw message. This is where the .info or .warn calls happen. The logger has one or more appenders. These appenders are called with the log event and are useful because you can log the same message in multiple locations if you want. For example, you can have an appender for the console and another one for writing to a file. If you configure the logger to use both appenders, you only need one call at .info() and it will delegate the logging to both appenders.

Next comes the layout. There are multiple layouts available and they specify the actual format of the logged message. Here you define multiple “converters” and the layout will call each one to format part of the message and append the needed data. There is a converter for the date, one for exception, one for the message and many more. Here we will be inserting our code so that we can format the logged message in our own way.

How to print colored text in Log4J2

For this article we will be making a converter that will highlight different words in the logged message. We will make the list of words configurable directly from the log4j configuration file. Keep in mind that this is just a simple example and that converters can be used for much more powerful features, like masking sensitive elements in the logs or formatting code lines in a certain way.

But, to keep it simple, we will be changing the color of certain words to cyan. To do this, we need a custom converter. It will extend the LogEventPatternConverter class. Furthermore, it needs to be marked as a plugin using the @Plugin annotation (we also give a name and category to it) and we need to define converter keys. These will be used in the PatternLayout to identify our converter. When the pattern is parsed and our converter key is identified, it will delegate the log event to our converter. And yes, you can have multiple keys if you want.

@Plugin(name = "highlight", category = PatternConverter.CATEGORY)
@ConverterKeys({"color", "colormsg"})
public class HighlightConverter extends LogEventPatternConverter {

}
Enter fullscreen mode Exit fullscreen mode

Now, we need to provide a constructor. The constructor will get the list of words that we want to highlight in the logs. To make sure we don’t have duplicates, we can make it a set. We also call super() to make sure everything is properly initialized further down the chain. Looking over the documentation for Log4j2 we learn that the converter actually needs a newInstance() method and it takes an array of Strings as the parameter, which represents the options for the converter. In this case, the option is our list of words, but it can be anything that we need to properly initialize our converter.

@Plugin(name = "highlight", category = PatternConverter.CATEGORY)
@ConverterKeys({"color", "colormsg"})
public class HighlightConverter extends LogEventPatternConverter {
    private static final String NO_COLOR = "\u001B[0m"; // No color for Windows
    private static final String HIGHLIGHT_COLOR = "\u001B[96m"; // Cyan for Windows

    private Set<String> words;

    private HighlightConverter(Set<String> words) {
        super("HighlightConverter", null);

        this.words = words;
    }

    public static HighlightConverter newInstance(String[] options) {
        return new HighlightConverter(new HashSet<>(Arrays.asList(options)));
    }
}
Enter fullscreen mode Exit fullscreen mode

Now that we defined our constructor and we prepared the set of words that we want to highlight, we need to alter the message. All Log4j2 converters must implement the append() method. This method takes in a LogEvent and the String Builder where the message must be appended. The LogEvent contains the logged message and we can retrieve it and replace our keywords with the highlighted version. The final version looks something like this:

@Plugin(name = "highlight", category = PatternConverter.CATEGORY)
@ConverterKeys({"color", "colormsg"})
public class HighlightConverter extends LogEventPatternConverter {
    private static final String NO_COLOR = "\u001B[0m";
    private static final String HIGHLIGHT_COLOR = "\u001B[96m";

    private Set<String> words;

    private HighlightConverter(Set<String> words) {
        super("HighlightConverter", null);

        this.words = words;
    }

    public static HighlightConverter newInstance(String[] options) {
        return new HighlightConverter(new HashSet<>(Arrays.asList(options)));
    }



    @Override
    public void format(LogEvent event, StringBuilder toAppendTo) {
        String logMessage = event.getMessage().getFormattedMessage();
        for (String word:words) {
            logMessage = logMessage.replaceAll(word, HIGHLIGHT_COLOR + word + NO_COLOR);
        }

        toAppendTo.append(logMessage);
    }
}
Enter fullscreen mode Exit fullscreen mode

Using a custom converter in Log4J2

We prepared our converter, but how do we tell Log4j2 to use it? We need two things. First, we need to tell Log4J to include our converter class. This is done by adding the package in which the converter resides in our configuration:

<?xml version="1.0" encoding="UTF-8"?>
<Configuration status="WARN" packages="com.ppopescu">

</Configuration>
Enter fullscreen mode Exit fullscreen mode

Next, we define our appender (in this case a Console Appender) that has a Pattern Layout. For the message, instead of using %m (the standard converter key for the logged message), we use our defined converter key: %color or %colormsg. Just adding this won’t do any good since we did not specify what words to highlight. We need to add parameters to the converter. This is done using {param} syntax and we can have as many as we need. Here is a sample configuration that highlights the words “custom”, “highlight” and “words”:

<?xml version="1.0" encoding="UTF-8"?>
<Configuration status="WARN" packages="com.ppopescu">
    <Appenders>
        <Console name="Console" target="SYSTEM_OUT">
            <PatternLayout pattern="%d{HH:mm:ss.SSS} [%t] %-5level - %color{custom}{highlight}{words}%n"/>
        </Console>
    </Appenders>
    <Loggers>
        <Root level="info">
            <AppenderRef ref="Console"/>
        </Root>
    </Loggers>
</Configuration>
Enter fullscreen mode Exit fullscreen mode

Conclusions

It is easy to define a custom message converter that fill in any logging needs. Using it to highlight words is easy, but the converter itself can be much more complex and fulfill more advanced needs like masking, code formatting or anything else you can think of. A similar approach can also be taken for building a custom appender or a custom layout.

Full source and original article on my personal website at Building a custom converter in Log4j2

Discussion (1)

Collapse
dragoncattest profile image
dragoncat

Thanks for your sharing! I will try to reperform your code!

Forem Open with the Forem app