Search code examples
javajunitlogbackslf4jjunit5

Static Context Configuration for Logback Appenders


I've found a few examples (even on Stack Overflow) of some programmatic configuration of Logback logging appenders, but as much as I've incorporated into my own setup hasn't worked for me so far. Some examples produce an actual Logger instance, but considering I've already got a Logger being statically instantiated within my class, I want to be able to programmatically enable an Appender that I've defined for unit testing purposes.

Here is my custom appender:

package org.example.logging;

import ch.qos.logback.classic.spi.ILoggingEvent;
import ch.qos.logback.core.AppenderBase;

import java.util.ArrayList;
import java.util.List;

// Credit to https://stackoverflow.com/a/29077499/5476186
public class TestAppender extends AppenderBase<ILoggingEvent> {
    private static List<ILoggingEvent> events = new ArrayList<>();

    @Override
    protected void append(ILoggingEvent e) {
        events.add(e);
    }

    public static List<ILoggingEvent> events() {
        return List.copyOf(events);
    }

    public static void clear() {
        events.clear();
    }
}

And in my testing code, I'm trying to configure my TestAppender to "kick in" so that, after invoking this method in my test setup, I can capture the logs and validate them:

package org.example.logging;

import ch.qos.logback.classic.LoggerContext;
import org.slf4j.LoggerFactory;

    // ...

    // Mostly modeled after https://stackoverflow.com/a/7825548/5476186
    private static void startAppender() {
        LoggerContext logCtx = (LoggerContext) LoggerFactory.getILoggerFactory();

        TestAppender appender = new TestAppender();
        appender.setContext(logCtx);
        appender.setName("TEST");
        // I was hoping this would statically allow the appender to kick in,
        // but all of the examples then attach this appender to a Logger instance.
        appender.start();
    }

Obviously, this isn't working for me. So I guess I have two contingent questions.

  1. Is this possible and, if so, how can I make it work?
  2. If this is not possible, what's the cleanest way to accomplish what I'm trying to do? (Enable/disable appenders during testing without having to manually mess with a config file.)

In one of the threads linked above, I found this answer which looks like one possible solution is to modify the text in the configuration file and to force a reload, but that doesn't seem super clean to me. Another option would be to create my own wrapper Logger factory which I could use to provide loggers with my TestAppender during test execution with dependency injection. I'll probably be creating a wrapper anyway, even though I'm using SLF4J.

Side note: I know that my test code as currently written is pretty tightly coupled with Logback instead of SLF4J, so I'm open to criticism/advice on that issue, too.


Solution

  • If you're using slf4j in your production code, then there is already a project that can help in testing: Its called slf4j-test

    In a nutshell, it provides an API to retrieve a "test logger" in the test that will keep all the logged messages in memory so that you'll be able to verify them.

    So that you:

    1. Execute a method that logs something
    2. Retrieve a test logger
    3. call getLoggingEvents() on the test logger and verify the logged events

    The link that I've provided contains an example of the API as well as maven integration example.

    If, alternatively you would like to use logback directly for the tests or something, there is already a ListAppender shipped as a part of logback distribution that allows retrieval of events that have passed through the appender. You can add it programmatically to the logger and use inside the test.

    Here you can find a comprehensive example of doing that