EdwinF
EdwinF

Reputation: 209

How to write a unit test for a custom logger in log4j2

I've created a couple of custom loggers with some levels that override the custom ones in Log4J2. I've followed the guide at http://logging.apache.org/log4j/2.x/manual/customloglevels.html.

I need to create some unit test to verify that the events are being registered on their correct custom levels and configuration.

I appreciate any hint on how to start. Thanks a lot.

Upvotes: 11

Views: 18362

Answers (4)

user3467108
user3467108

Reputation: 1

Here is an example of how to test logging. This code is based on sample tests from the log4j repository. I added an appender that saves messages to a StringWriter. In my test environment, there is a message converter that masks sensitive information and my test uses it even though I didn't describe its configuration and test cases demonstrates it.

package org.cyberpro.debate.integration.logger_config;

import org.apache.logging.log4j.Level;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.apache.logging.log4j.core.Appender;
import org.apache.logging.log4j.core.LoggerContext;
import org.apache.logging.log4j.core.appender.WriterAppender;
import org.apache.logging.log4j.core.config.Configuration;
import org.apache.logging.log4j.core.config.LoggerConfig;
import org.apache.logging.log4j.core.layout.PatternLayout;
import org.assertj.core.api.Assertions;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.TestInfo;
import org.slf4j.LoggerFactory;

import java.io.StringWriter;
import java.lang.reflect.Method;

final class TestLogger {
    private StringWriter writer;
    private String testMethodName;

    @BeforeEach
    void setUp(final TestInfo testInfo) {
        testMethodName = testInfo.getTestMethod().map(Method::getName).orElseGet(testInfo::getDisplayName);
        prepareAppender(testMethodName);
    }
    
    void prepareAppender(String writerName) {
        writer = new StringWriter();
        final LoggerContext context = LoggerContext.getContext(false);
        final Configuration config = context.getConfiguration();
        final PatternLayout layout = PatternLayout.newBuilder()
                .withConfiguration(config)
                .withPattern("%spi%n")
                .build();
        final Appender appender = WriterAppender.createAppender(layout, null, writer, writerName, false, true);
        appender.start();
        config.addAppender(appender);
        updateLoggers(appender, config);
    }

    void updateLoggers(final Appender appender, final Configuration config) {
        for (final LoggerConfig loggerConfig : config.getLoggers().values()) {
            loggerConfig.addAppender(appender, Level.ERROR, null);
        }
        config.getRootLogger().addAppender(appender, Level.ERROR, null);
    }
    

    @Test
    void name1() {
        final Logger logger = LogManager.getLogger(testMethodName);
        logger.error("Test message");
        logger.error("1234564564546456466466");
        Assertions.assertThat(writer.toString())
                .contains("Test message")
                .contains("1234**************6466");
    }

    @Test
    void slf4j_test() {
        LoggerFactory.getLogger(getClass()).error("slf4j_test");
        Assertions.assertThat(writer.toString())
                .contains("slf4j_test")
                .doesNotContain("Test message");
    }
}

Upvotes: 0

David Lopez Carrasco
David Lopez Carrasco

Reputation: 251

Here you have what I've done in one of my JUnit Test.

1- Create a custom appender holding a list of messages in memory.

package com.example.appender;

import java.io.Serializable;
import java.util.ArrayList;
import java.util.List;

import lombok.Getter;
import org.apache.logging.log4j.core.Filter;
import org.apache.logging.log4j.core.Layout;
import org.apache.logging.log4j.core.LogEvent;
import org.apache.logging.log4j.core.appender.AbstractAppender;
import org.apache.logging.log4j.core.config.Property;
import org.apache.logging.log4j.core.config.plugins.Plugin;
import org.apache.logging.log4j.core.config.plugins.PluginAttribute;
import org.apache.logging.log4j.core.config.plugins.PluginElement;
import org.apache.logging.log4j.core.config.plugins.PluginFactory;
import org.apache.logging.log4j.core.layout.PatternLayout;

/**
 * @author carrad
 * 
 */
@Plugin(name = "TestAppender", category = "Core", elementType = "appender", printObject = true)
public class TestAppender extends AbstractAppender {

    @Getter
    private final List<LogEvent> messages = new ArrayList<>();

    protected TestAppender(String name, Filter filter, Layout<? extends Serializable> layout) {
        super(name, filter, layout, true, Property.EMPTY_ARRAY);
    }

    @Override
    public void append(LogEvent event) {
        messages.add(event);
    }

    @PluginFactory
    public static TestAppender createAppender(
            @PluginAttribute("name") String name,
            @PluginElement("Layout") Layout<? extends Serializable> layout,
            @PluginElement("Filter") final Filter filter,
            @PluginAttribute("otherAttribute") String otherAttribute
    ) {
        if (name == null) {
            LOGGER.error("No name provided for TestAppender");
            return null;
        }
        if (layout == null) {
            layout = PatternLayout.createDefaultLayout();
        }
        return new TestAppender(name, filter, layout);
    }
}

2- Add the appender to the log4j2-test.xml

<?xml version="1.0" encoding="UTF-8"?>
<Configuration status="WARN" packages="com.example.appender">
    <Appenders>
        <Console name="Console" target="SYSTEM_OUT">
            <PatternLayout pattern="%d{HH:mm:ss.SSS} [%t] %-5level %logger{36} - %msg%n" />
        </Console>
        <TestAppender name="TestAppender" >
            <PatternLayout pattern="%d{HH:mm:ss.SSS} [%t] %-5level %logger{36} - %msg%n" />
        </TestAppender> 
    </Appenders>
    <Loggers>
        <Logger name="com.example" level="All" />
        <Root>
            <AppenderRef ref="Console" level="All" />
            <AppenderRef ref="TestAppender" level="All" /> 
        </Root>
    </Loggers>
</Configuration>

3- Get a reference to the appender in the Junit test.

public class LoggingInterceptorTest {

    @Autowired  // Whatever component you want to test
    private InterceptedComponent helperComponent;

    private TestAppender appender;

    @Before
    public void setUp() {
        final LoggerContext ctx = (LoggerContext) LogManager.getContext(false);
        final Configuration config = ctx.getConfiguration();
        appender = (TestAppender) config.getAppenders().get("TestAppender");
    }

    @Test
    public void test_wrapping() {
        helperComponent.doStuff("437");
        Assert.assertEquals(appender.getMessages().size(), 2);
    }
}

In your test case you can check for the number of messages written or the list containing the messages you want, including meta-information such as level and so on.

Upvotes: 25

rewolf
rewolf

Reputation: 5871

One option is to configure the logger to write to an in-memory string (byte array) stream, using a custom OutputStreamAppender subclass, which you'll have to code.

You can then use assertions against the resulting string in tests.

I recently made a blogpost about doing just this here. Maybe it'll help you.

Upvotes: 1

Remko Popma
Remko Popma

Reputation: 36754

I recommend taking a look at the JUnit tests in log4j2.

A number of log4j2 unit tests use a FileAppender with immediateFlush=true, then read in the file and check that some expected Strings exist in the output. Others configure a (org.apache.logging.log4j.test.appender.) ListAppender (this class lives in the core test jar) and obtain the LogEvent objects directly from the list.

You may need to fork a new process for your log4j2 JUnit tests to make sure a different configuration has not already been loaded by some previous process.

Upvotes: 6

Related Questions