Notes on Logback

Subscribe Send me a message home page tags


#logback  #slf4j  #logging  #java  #notes  #introduction 

Related Readings

Notes

This post is a collection of online material on how to set up logback and SLF4J in Java. To start, we could follow the instruction in this tutorial. If it doesn't work, which means the logging format doesn't change, we could use the information below for troubleshooting.

What is SLF4J and Logback?

SLF4J is an abstraction for various logging frameworks and logback is one of those most commonly used framework implementations.

logging-framework-bindings.png

http://www.slf4j.org/manual.html

As we can see, in order to have SLF4J bound to logback, we need

If we use Maven, this will be translated into something like

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
<dependency>
    <groupId>org.slf4j</groupId>
    <artifactId>slf4j-api</artifactId>
    <version>1.7.5</version>
</dependency>

<dependency>
    <groupId>ch.qos.logback</groupId>
    <artifactId>logback-core</artifactId>
    <version>1.2.6</version>
</dependency>

<dependency>
    <groupId>ch.qos.logback</groupId>
    <artifactId>logback-classic</artifactId>
    <version>1.2.6</version>
</dependency>

Is the logback framework configured correctly?

There are two aspects of the logback configuration:

Most of the time, a parser will be used to parse the information in the configuration file so if we make mistakes in the configuration file itself, we should see some obvious errors in the outputs if we run the program.

The second part is a little bit tricky. How do we know the logback configuration is picked up?

First, the configuration needs to be located directly under any of classpath.

Configuration files such as logback.groovy, logback-test.xml or logback.xml can be located directly under any folder declared in the class path. For example, if the class path reads "c:/java/jdk15/lib/rt.jar;c:/mylibs/" then the logback.xml file should be located directly under "c:/mylibs/", that is as "c:/mylibs/logback.xml". Placing it under a sub-folder of c:/mylibs/, say, c:/mylibs/other/, will not work. For web-applications, configuration files can be placed directly under WEB-INF/classes/. (source)

If the java program is built and run using an IDE, we also need to check if the configuration file is indeed located in one of the folders listed in the classpath argument.

Second, we need to make sure, the logback binding(framework) is used.

[...] The warning emitted by SLF4J is just that, a warning. Even when multiple bindings are present, SLF4J will pick one logging framework/implementation and bind with it. The way SLF4J picks a binding is determined by the JVM and for all practical purposes should be considered random. As of version 1.6.6, SLF4J will name the framework/implementation class it is actually bound to. (source)

In general, we should include dependency on only one logging framework binding as described in the document:

This warning, i.e. not an error, message is reported when no SLF4J providers could be found on the class path. Placing one (and only one) of slf4j-nop.jar slf4j-simple.jar, slf4j-log4j12.jar, slf4j-jdk14.jar or logback-classic.jar on the class path should solve the problem. Note that these providers must target slf4j-api 1.8 or later. (source)

Template with Explanation

Here is a sample logback.xml file.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
<?xml version="1.0" encoding="UTF-8"?>
<configuration scan="true" scanPeriod="10 seconds">
    <property name="log_dir" value="your-log-directory"/>
    <appender name="CONSOLE_APPENDER" class="ch.qos.logback.core.ConsoleAppender">
        <encoder>
            <charset>UTF-8</charset>
            <pattern>%date{dd MMM yyyy HH:mm:ss,SSS} [%level] %logger: %msg%n</pattern>
        </encoder>
    </appender>

    <appender name="TIME_BASED_FILE_APPENDER" class="ch.qos.logback.core.rolling.RollingFileAppender">
        <rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
            <fileNamePattern>/log-directory/app.log.%d{yyyy-MM-dd-HH, UTC}</fileNamePattern>
        </rollingPolicy>
        <encoder>
            <charset>UTF-8</charset>
            <pattern>%date{dd MMM yyyy HH:mm:ss.SSS} [%level] %logger: %msg%n</pattern>
        </encoder>
    </appender>

    <appender name="ASYNCHRONOUS_APPENDER" class="ch.qos.logback.classic.AsyncAppender">
        <appender-ref ref="TIME_BASED_FILE_APPENDER"/>
        <queueSize>10</queueSize>
        <discardingThreshold>3</discardingThreshold>
    </appender>

    <!-- This section is for customization. The name file is the java package name or class name -->
    <logger name="leetcode" level="INFO"/>

    <!--
        The default leve is set to WARN. Combined with the customization section above,
        we mute the logging from third-party libraries except warnings and exceptions
        and enable the logging at desired level for our own code.
    -->

    <root level="WARN">
        <!-- Here we include two appenders -->
        <appender-ref ref="CONSOLE_APPENDER"/>
        <appender-ref ref="ASYNCHRONOUS_APPENDER"/>
    </root>

</configuration>

How to split log files?

To split a log file, we need to use the SiftingAppender. Because the log file is divided into multiple parts, we need an ID to identify those smaller parts. In the example below, this ID is called appenderId. We also need to implement some distribution logic so that the system knows the routing between messages and parts.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
<appender name="SIFT_APPENDER" class="ch.qos.logback.classic.sift.SiftingAppender">
    <discriminator class="logback.RandomDiscriminator"/>
    <sift>
        <appender name="MY_FILE_${appenderId}" class="ch.qos.logback.core.rolling.RollingFileAppender">
            <rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
                <fileNamePattern>${log_dir}/application.log.part.${appenderId}.%d{yyyy-MM-dd-HH, UTC}</fileNamePattern>
            </rollingPolicy>

            <encoder>
                <charset>UTF-8</charset>
                <pattern>%date{dd MMM yyyy HH:mm:ss.SSS} [%level] %logger: %msg%n</pattern>
            </encoder>
        </appender>
    </sift>
</appender>

A simple randome discriminator is given as below:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
import ch.qos.logback.classic.spi.ILoggingEvent;
import ch.qos.logback.core.sift.Discriminator;
import java.util.concurrent.atomic.AtomicInteger;

public class RandomDiscriminator implements Discriminator<ILoggingEvent> {
    private static final String KEY = "appenderId";
    private static final int NUM_OF_APPENDERS = 2;
    private final AtomicInteger currentId = new AtomicInteger(0);
    private boolean started;

    @Override
    public String getDiscriminatingValue(ILoggingEvent loggingEvent) {
        this.currentId.compareAndSet(NUM_OF_APPENDERS, 0);
        return String.valueOf(this.currentId.incrementAndGet());
    }

    @Override
    public String getKey() {
        return KEY;
    }

    @Override
    public void start() {
        this.started = true;
    }

    @Override
    public void stop() {
        this.started = false;
    }

    @Override
    public boolean isStarted() {
        return this.started;
    }
}

How to specify appenders for a specific logger?

We can specify appenders for a specific logger in the <logger> tag. For example:

1
2
3
<logger name="lab" level="INFO" additivity="false">
        <appender-ref ref="SIFT_APPENDER"/>
</logger>

The additivity flag can be interpreted as "should we only use appenders defined outside this logger block". Recall that appenders listed in the <root> section are attached to loggers by default.

----- END -----