Programmatically configure LogBack appender

Just, if somebody would looking for a concrete example of programmatic configuration.

Here I setup the charset of ConsoleAppender:

LoggerContext lc = (LoggerContext) LoggerFactory.getILoggerFactory();
ConsoleAppender<ILoggingEvent> appender =
    (ConsoleAppender) lc.getLogger("appconsole").getAppender("STDOUT");
LayoutWrappingEncoder<ILoggingEvent> enc = 
    (LayoutWrappingEncoder<ILoggingEvent>) appender.getEncoder();
enc.setCharset(Charset.forName("utf-8"));

And my logback.xml:

<appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
    <encoder>
        <charset>866</charset>
        <pattern>[%level] %msg%n</pattern>
    </encoder>
</appender>

<logger name="appconsole">
    <appender-ref ref="STDOUT" />
</logger>

Why I need programmaticaly configure a logger? Because, I packaging my app (Spring Boot) into a jar file. Consequently Logback.xml file is appear to be hide inside a jar. Though, it is not to be convenient to unpackage and change it. And I do not need any logback.xml file beside my app.jar. I have only app.yaml file which contains all configuration properties for app.


Here a simple example that works for me (note that I use the FileAppender in this example)

import org.slf4j.LoggerFactory;

import ch.qos.logback.classic.Level;
import ch.qos.logback.classic.Logger;
import ch.qos.logback.classic.LoggerContext;
import ch.qos.logback.classic.encoder.PatternLayoutEncoder;
import ch.qos.logback.classic.spi.ILoggingEvent;
import ch.qos.logback.core.FileAppender;

public class Loggerutils {

    public static void main(String[] args) {
          Logger foo = createLoggerFor("foo", "foo.log");
          Logger bar = createLoggerFor("bar", "bar.log");
          foo.info("test");
          bar.info("bar");
    }

    private static Logger createLoggerFor(String string, String file) {
          LoggerContext lc = (LoggerContext) LoggerFactory.getILoggerFactory();
          PatternLayoutEncoder ple = new PatternLayoutEncoder();

          ple.setPattern("%date %level [%thread] %logger{10} [%file:%line] %msg%n");
          ple.setContext(lc);
          ple.start();
          FileAppender<ILoggingEvent> fileAppender = new FileAppender<ILoggingEvent>();
          fileAppender.setFile(file);
          fileAppender.setEncoder(ple);
          fileAppender.setContext(lc);
          fileAppender.start();

          Logger logger = (Logger) LoggerFactory.getLogger(string);
          logger.addAppender(fileAppender);
          logger.setLevel(Level.DEBUG);
          logger.setAdditive(false); /* set to true if root should log too */

          return logger;
    }

}

You can configure appenders programmatically. Almost all appenders are tested using programmatic configuration. It follows that there are many examples of programmatic appender configuration in the logback project source code. For a logback-core appender, look under logback-core/src/test/java, and for a logback-classic appender look under logback-classic/src/test/java.


As a reference, when you try to modify code responsible for creating loggers, there is a bunch of rules that must be satisfied in order for a logger to work.

These rules were described in a great and helpful article Programmatic configuration of slf4j/logback:

Now I have experience with programmatic configuration of slf4j/logback.

Task

A program must open separate log file for each processed input file.

Solution for task

Instead of configuring logback via xml, the one needs to “manually” instantiate encoders, appenders and loggers, then configure and link them together.

Caveat 1

Logback goes crazy on attempt to share encoder (i.e. PatternLayoutEncoder) between appenders.

Solution for caveat 1

Create separate encoder for each appender.

Caveat 2

Logback refuses to log anything, if encoders and appenders are not associated with logging context.

Solution for caveat 2

Call setContext on each encoder and appender, passing LoggerFactory as a parameter.

Caveat 3

Logback refuses to log anything, if encoders and appenders are not started.

Solution for caveat 3

encoders and appenders need to be started in the correct order, i.e. first encoders, then appenders.

Caveat 4

RollingPolicy objects (i.e. TimeBasedRollingPolicy) produce strange error messages like “date format not recognized”, when they are not attached to the same context as appender.

Solution for caveat 4

call setContext on RollingPolicy same way as on encoders and appenders.

Here is working example of “manual” logback configuration:

package testpackage

import ch.qos.logback.classic.Level
import ch.qos.logback.classic.Logger
import ch.qos.logback.classic.LoggerContext
import ch.qos.logback.classic.encoder.PatternLayoutEncoder
import ch.qos.logback.core.ConsoleAppender
import ch.qos.logback.core.rolling.RollingFileAppender
import ch.qos.logback.core.rolling.TimeBasedRollingPolicy

import org.slf4j.LoggerFactory

class TestLogConfig {

  public static void main(String[] args) {
    LoggerContext logCtx = LoggerFactory.getILoggerFactory();

    PatternLayoutEncoder logEncoder = new PatternLayoutEncoder();
    logEncoder.setContext(logCtx);
    logEncoder.setPattern("%-12date{YYYY-MM-dd HH:mm:ss.SSS} %-5level - %msg%n");
    logEncoder.start();

    ConsoleAppender logConsoleAppender = new ConsoleAppender();
    logConsoleAppender.setContext(logCtx);
    logConsoleAppender.setName("console");
    logConsoleAppender.setEncoder(logEncoder);
    logConsoleAppender.start();

    logEncoder = new PatternLayoutEncoder();
    logEncoder.setContext(logCtx);
    logEncoder.setPattern("%-12date{YYYY-MM-dd HH:mm:ss.SSS} %-5level - %msg%n");
    logEncoder.start();

    RollingFileAppender logFileAppender = new RollingFileAppender();
    logFileAppender.setContext(logCtx);
    logFileAppender.setName("logFile");
    logFileAppender.setEncoder(logEncoder);
    logFileAppender.setAppend(true);
    logFileAppender.setFile("logs/logfile.log");

    TimeBasedRollingPolicy logFilePolicy = new TimeBasedRollingPolicy();
    logFilePolicy.setContext(logCtx);
    logFilePolicy.setParent(logFileAppender);
    logFilePolicy.setFileNamePattern("logs/logfile-%d{yyyy-MM-dd_HH}.log");
    logFilePolicy.setMaxHistory(7);
    logFilePolicy.start();

    logFileAppender.setRollingPolicy(logFilePolicy);
    logFileAppender.start();

    Logger log = logCtx.getLogger("Main");
    log.setAdditive(false);
    log.setLevel(Level.INFO);
    log.addAppender(logConsoleAppender);
    log.addAppender(logFileAppender);
  }
}