Logback Configuration Example
In this post, we are going to show you how to configure your application to use slf4j
and logback
as logger solution.
1. What is logback?
Logback is intended as a successor to the popular log4j project. It was designed by Ceki Gülcü, log4j’s founder. It builds upon a decade of experience gained in designing industrial-strength logging systems. The resulting product, i.e. logback, is faster and has a smaller footprint than all existing logging systems, sometimes by a wide margin. Just as importantly, logback offers unique and rather useful features missing in other logging systems.
Before continuing with logback, let’s talk a little about SL4J.
1.1 What is SLF4J?
SLF4J is a simple facade for logging systems allowing the end-user to plug-in the desired logging system at deployment time.
1.2 When should SLF4J be used?
In short, libraries and other embedded components should consider SLF4J for their logging needs because libraries cannot afford to impose their choice of logging framework on the end-user. On the other hand, it does not necessarily make sense for stand-alone applications to use SLF4J. Stand-alone applications can invoke the logging framework of their choice directly. In the case of logback, the question is moot because logback exposes its logger API via SLF4J.
SLF4J is only a facade, meaning that it does not provide a complete logging solution. Operations such as configuring appenders or setting logging levels cannot be performed with SLF4J. Thus, at some point in time, any non-trivial application will need to directly invoke the underlying logging system. In other words, complete independence from the API underlying logging system is not possible for a stand-alone application. Nevertheless, SLF4J reduces the impact of this dependence to near-painless levels.
2. Logback – Modular Architecture
Logback’s basic architecture is sufficiently generic so as to apply under different circumstances. At the present time, logback is divided into three modules, logback-core, logback-classic and logback-access.
The core module lays the groundwork for the other two modules. The classic module extends core. The classic module corresponds to a significantly improved version of log4j.
Logback-classic natively implements the SLF4J API so that you can readily switch back and forth between logback and other logging systems such as log4j or java.util.logging (JUL)
introduced in JDK 1.4. The third module called access integrates with Servlet containers to provide HTTP-access log functionality.
The logback-core module forms the foundation upon which the other two modules are built. Interestingly enough, logback-core has no notion of “logger”. Logback-classic relies on logback-core for basic services. It natively implements the SLF4J API.
3. Adding Maven dependencies
Before we start to execute some code and see how logback works, we need to add the following dependencies to our pom.xml file:
<dependency> <groupId>org.slf4j</groupId> <artifactId>slf4j-api</artifactId> <version>1.7.7</version> </dependency> <dependency> <groupId>ch.qos.logback</groupId> <artifactId>logback-classic</artifactId> <version>1.1.2</version> </dependency> <dependency> <groupId>ch.qos.logback</groupId> <artifactId>logback-core</artifactId> <version>1.1.2</version> </dependency>
4. Configuration in logback
Inserting log requests into the application code requires a fair amount of planning and effort. Observation shows that approximately four percent of code is dedicated to logging. Consequently, even a moderately sized application will contain thousands of logging statements embedded within its code. Given their number, we need tools to manage these log statements.
Logback can be configured either programmatically or with a configuration script expressed in XML or Groovy format.
Let us begin by discussing the initialization steps that logback follows to try to configure itself:
- Logback tries to find a file called logback.groovy in the classpath.
- If no such file is found, logback tries to find a file called logback-test.xml in the classpath.
- If no such file is found, it checks for the file logback.xml in the classpath.
- If neither file is found, logback configures itself automatically using the
BasicConfigurator
which will cause logging output to be directed to the console.
4.1 Automatically configuring logback
The simplest way to configure logback is by letting logback fall back to its default configuration. Let us give a taste of how this is done in an imaginary application called App:
package com.javacodegeeks.examples.logbackexample; import org.slf4j.Logger; import org.slf4j.LoggerFactory; public class BasicConfApp { final static Logger logger = LoggerFactory.getLogger(BasicConfApp.class); public static void main(String[] args) { logger.info("Msg #1"); logger.warn("Msg #2"); logger.error("Msg #3"); logger.debug("Msg #4"); } }
Assuming the configuration files logback-test.xml or logback.xml are not present, logback will default to invoking BasicConfigurator which will set up a minimal configuration. This minimal configuration consists of a ConsoleAppender
attached to the root logger. The output is formatted using a PatternLayoutEncoder
set to the pattern %d{HH:mm:ss.SSS} [%thread] %-5level %logger{36} – %msg%n. Moreover, by default the root logger is assigned the DEBUG level.
The output of the command java com.javacodegeeks.examples.logbackexample.BasicConfApp should be similar to:
13:38:02.492 [main] INFO c.j.e.logbackexample.BasicConfApp - Msg #1 13:38:02.495 [main] WARN c.j.e.logbackexample.BasicConfApp - Msg #2 13:38:02.495 [main] ERROR c.j.e.logbackexample.BasicConfApp - Msg #3 13:38:02.495 [main] DEBUG c.j.e.logbackexample.BasicConfApp - Msg #4
4.1.1 Automatic configuration with logback-test.xml or logback.xml
As mentioned earlier, logback will try to configure itself using the files logback-test.xml or logback.xml if found on the class path.
Logback delegates the task of writing a logging event to components called appenders. Appenders must implement the ch.qos.logback.core.Appender
.
Let’s see an example of a logback.xml file.
<?xml version="1.0" encoding="UTF-8"?> <configuration> <!-- Send debug messages to System.out --> <appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender"> <!-- By default, encoders are assigned the type ch.qos.logback.classic.encoder.PatternLayoutEncoder --> <encoder> <pattern>%d{HH:mm:ss.SSS} [%thread] %-5level %logger{5} - %msg%n</pattern> </encoder> </appender> <logger name="com.javacodegeeks.examples.logbackexample.beans" level="INFO" additivity="false"> <appender-ref ref="STDOUT" /> </logger> <!-- By default, the level of the root level is set to DEBUG --> <root level="DEBUG"> <appender-ref ref="STDOUT" /> </root> </configuration>
As an Appender, we are using a Console Appender. The ConsoleAppender
, as the name indicates, appends on the console, or more precisely on System.out
or System.err
, the former being the default target. ConsoleAppender formats events with the help of an encoder specified by the user. Both System.out and System.err are of type java.io.PrintStream
. Consequently, they are wrapped inside an OutputStreamWriter which buffers I/O operations.
We are defining two loggers:
- An user defined logger – Which handles classes logging in the package com.javacodegeeks.examples.logbackexample.beans, has an INFO level and points to the standard output console
- A ROOT logger – Which handles logging in all of the classes that are not in the package com.javacodegeeks.examples.logbackexample.beans, has a DEBUG level and points to the standard output console
We are going to use it along with the following code:
MarcoPoloBean.java
package com.javacodegeeks.examples.logbackexample.beans; import org.slf4j.Logger; import org.slf4j.LoggerFactory; public class MarcoPoloBean { private static final Logger logger = LoggerFactory.getLogger(MarcoPoloBean.class); public void sayMarco() { String msg = "I'm Marco"; logger.info("Hello there. I am {}", msg); logger.debug("Debugging message"); } }
App.java
package com.javacodegeeks.examples.logbackexample; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import com.javacodegeeks.examples.logbackexample.beans.MarcoPoloBean; public class App { private static final Logger logger = LoggerFactory.getLogger(App.class); public static void main(String[] args) { MarcoPoloBean poloBean = new MarcoPoloBean(); poloBean.sayMarco(); logger.debug("I am Polo"); } }
The output of the command java com.javacodegeeks.examples.logbackexample.App should be similar to:
17:49:11.703 [main] INFO c.j.e.l.b.MarcoPoloBean - Hello there. I am I'm Marco 17:49:11.707 [main] DEBUG c.j.e.l.App - I am Polo
Let’s examine our code!
Class MarcoPolo.java is inside the com.javacodegeeks.examples.logbackexample.beans package. This package is defined in our logback.xml to handle logging at an INFO level. This class, tries to logging two messages: One at an INFO level, and one at a DEBUG level, but as you can see in the output, only the logging message at the INFO leve is displayed.
Class App.java is not inside the com.javacodegeeks.examples.logbackexample.beans package. So, logging messages are going to be handled by the ROOT logger.
Let’s explain the methods used in the above example.
Class LoggerFactory
– The LoggerFactory is a utility class producing Loggers for various logging APIs, most notably for log4j, logback and JDK 1.4 logging.public static Logger getLogger(Class clazz)
– Return a logger named corresponding to the class passed as parameter, using the statically boundILoggerFactory
instance.void info(String msg)
– Log a message at the INFO level.void info(String format, Object arg)
– Log a message at the INFO level according to the specified format and argument. This form avoids superfluous object creation when the logger is disabled for the INFO level.void warn(String msg)
– Log a message at the WARN level.void error(String msg)
– Log a message at the ERROR level.void debug(String msg)
– Log a message at the DEBUG level.
4.1.2 Logging to a file
The following logback.xml configuration file, shows an example on how to configure logback to redirect the logging output to a file.
<?xml version="1.0" encoding="UTF-8"?> <configuration> <!-- Send debug messages to System.out --> <appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender"> <!-- By default, encoders are assigned the type ch.qos.logback.classic.encoder.PatternLayoutEncoder --> <encoder> <pattern>%d{HH:mm:ss.SSS} [%thread] %-5level %logger{5} - %msg%n</pattern> </encoder> </appender> <!-- Send debug messages to a file at "c:/jcg.log" --> <appender name="FILE" class="ch.qos.logback.core.rolling.RollingFileAppender"> <file>c:/jcg.log</file> <encoder class="ch.qos.logback.classic.encoder.PatternLayoutEncoder"> <Pattern>%d{yyyy-MM-dd_HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n</Pattern> </encoder> <rollingPolicy class="ch.qos.logback.core.rolling.FixedWindowRollingPolicy"> <FileNamePattern>c:/jcg.%i.log.zip</FileNamePattern> <MinIndex>1</MinIndex> <MaxIndex>10</MaxIndex> </rollingPolicy> <triggeringPolicy class="ch.qos.logback.core.rolling.SizeBasedTriggeringPolicy"> <MaxFileSize>2MB</MaxFileSize> </triggeringPolicy> </appender> <logger name="com.javacodegeeks.examples.logbackexample.beans" level="INFO" additivity="false"> <appender-ref ref="STDOUT" /> <appender-ref ref="FILE" /> </logger> <!-- By default, the level of the root level is set to DEBUG --> <root level="DEBUG"> <appender-ref ref="STDOUT" /> </root> </configuration>
As an Appender, we are using a Rolling File Appender. RollingFileAppender
extends FileAppender
with the capability to rollover log files. For example, RollingFileAppender can log to a file named log.txt file and, once a certain condition is met, change its logging target to another file.
There are two important sub-components that interact with RollingFileAppender. The first RollingFileAppender sub-component, namely RollingPolicy
, is responsible for undertaking the actions required for a rollover. A second sub-component of RollingFileAppender, namely TriggeringPolicy
, will determine if and exactly when rollover occurs. Thus, RollingPolicy is responsible for the what and TriggeringPolicy is responsible for the when.
To be of any use, a RollingFileAppender must have both a RollingPolicy and a TriggeringPolicy set up. However, if its RollingPolicy also implements the TriggeringPolicy interface, then only the former needs to be specified explicitly.
When rolling over, FixedWindowRollingPolicy renames files according to a fixed window algorithm as described below.
The fileNamePattern option represents the file name pattern for the archived (rolled over) log files. This option is required and must include an integer token %i somewhere within the pattern.
Here are the available properties for FixedWindowRollingPolicy:
- minIndex – This option represents the lower bound for the window’s index.
- maxIndex – This option represents the upper bound for the window’s index.
- fileNamePattern – This option represents the pattern that will be followed by the FixedWindowRollingPolicy when renaming the log files. It must contain the string %i, which will indicate the position where the value of the current window index will be inserted. For example, using MyLogFile%i.log associated with minimum and maximum values of 1 and 3 will produce archive files named MyLogFile1.log, MyLogFile2.log and MyLogFile3.log. Note that file compression is also specified via this property. For example, fileNamePattern set to MyLogFile%i.log.zip means that archived files must be compressed using the zip format; gz format is also supported.
Given that the fixed window rolling policy requires as many file renaming operations as the window size, large window sizes are strongly discouraged. When large values are specified by the user, the current implementation will automatically reduce the window size to 20.
Let us go over a more concrete example of the fixed window rollover policy. Suppose that minIndex is set to 1, maxIndex set to 3, fileNamePatternproperty set to foo%i.log, and that file property is set to foo.log.
With this logback.xml configuration, logging output is redirected to a file at “jcg.log”
4.1.3 Automatic configuration using Groovy
First, we need to add Groovy to our pom.xml file
<dependency> <groupId>org.codehaus.groovy</groupId> <artifactId>groovy</artifactId> <version>2.3.5</version> </dependency>
Then, we need to create a logback.groovy file with the same configuration as our logback.xml file
import ch.qos.logback.classic.encoder.PatternLayoutEncoder import ch.qos.logback.core.ConsoleAppender import static ch.qos.logback.classic.Level.DEBUG import static ch.qos.logback.classic.Level.INFO appender("STDOUT", ConsoleAppender) { encoder(PatternLayoutEncoder) { pattern = "%d{HH:mm:ss.SSS} [%thread] %-5level %logger{5} Groovy - %msg%n" } } logger("com.javacodegeeks.examples.logbackexample.beans", INFO) root(DEBUG, ["STDOUT"])
The output of the command java com.javacodegeeks.examples.logbackexample.App should be similar to:
17:49:11.703 [main] INFO c.j.e.l.b.MarcoPoloBean Groovy - Hello there. I am I'm Marco 17:49:11.707 [main] DEBUG c.j.e.l.App Groovy - I am Polo
5. Download the source code
This was an example of how to do logging using the logback library.
You can download the full source code of this example here : logbackexample
I was looking for the use of spi.Configurator as a Java service