Solving Your Logging Problems with Logback

Logback is a logging framework for Java applications, created as a successor to the popular log4jproject. In fact, both of these frameworks were created by the same developer. Given that logging is a crucial part of any application for both debugging and audit purposes, choosing an adequate logging library is a foundational decision for any project. There are several reasons why Logback is a great choice for a logging framework. In this post, we’ll outline its features and how to use it to its full potential.

Logback’s Core Features & Advantages

faster execution compared to log4j

native support for slf4j, which makes it easy to switch to a different logging framework, should that be necessary later on

conditional processing of the defined configuration

advanced filtering capabilities

compression of archived log files

support for setting a maximum number of archived log files

HTTP-access logging

recovery from I/O failures

The Logback project is organized in main 3 modules:

logback-core – contains the basic logging functionality

logback-classic – contains additional logging improvements, such as slf4j support

logback-access – provides integration with servlet containers, such as Tomcat and Jetty

In the following sections, we’ll have a look at how we can make the best use of this library a typical Java application.

Naturally, raw logging is just one aspect of understanding and reacting to the runtime of an application, next to monitoring errors, log management and other techniques that create a more holistic picture of our system.

Basic Setup

To start using the Logback, you first need to add the logback-classic dependency to the classpath. Let’s do that with Maven:

Next, you can use simply use the typical logging APIs corresponding to the log level you’re looking for:

logger.debug("UserService Test");

Logback Configuration Files

To create a configuration for Logback, you can use XML as well as Groovy. The system will automatically pick up and use the configuration automatically, as long as you’re adhering to the naming convention.

There are three valid standard file names you can choose from:

logback-test.xml

logback.groovy

logback.xml

A note worth understanding here is that Logback will search for these files in this exact order.

Going forward, the examples in this tutorial will rely on an XML based, simple logback.xml file.

Let’s see what a basic configuration equivalent to the default one looks like:

The RollingFileAppender

Logging to file is naturally the way to go in any kind of production scenario where you need persistent logs. However, if all the logs are kept in a single file, this runs the risk of becoming too large and difficult to wade through. It’s also to make the long-term storage/warehousing of log data very difficult.

That’s when rolling files come in handy.

To address this well-known limitation, Logback provides the RollingFileAppender, which rolls over the log file when certain conditions are met. The appender has two components:

RollingPolicy – which determines how the rollover is performed

TrigerringPolicy – which determines when the file is rolled over

To better understand these policies, let’s create an appender which makes use of a TimeBasedRollingPolicy and a SizeTriggeringPolicy:

The TimeBasedRollingPolicy implements both a RollingPolicy and a TriggeringPolicy.

The example above configures the fileNamePattern attribute based on the day – which means the name of each file contains the current date, and also that the rollover will happen daily.

Notice how we’re limiting the log data here – maxHistory is set to a value of 30, alongside a totalSizeCap of 3GB – which means that the archived logs will be kept for the past 30 days, up to a maximum size of 3GB.

Finally, the SizeBasedTriggeringPolicy defined configures the rollover of the file whenever it reaches 3 MB. Of course that’s quite a low limit, and a mature log-viewing tool can certainly handle a lot more than that.

You can now see how we’ve slowly moved out from basic examples to a more realistic configuration you can actually start using as the project moves towards production.

The SiftingAppender

This appender can be useful in situations when you want logs to be separated based on a runtime attribute, such as the user session.

The implementation of this appender relies on creating nested appenders and using one of these for logging depending on a discriminator. The default discriminator used is MDCBasedDiscriminator.

To see this functionality in action, let’s configure a SiftingAppender which separates logs into different files based on the userRole key:

For the discriminator to have access to the userRole key, you need to place it in the MDC (Mapped Diagnostic Context). Simply put, MDC allows you to set information to be later retrieved by other Logback components, using a simple, static API:

MDC.put("userRole", "ADMIN");
logger.info("Admin Action");

This will write the log message in a file called ADMIN.log.

Layouts and Encoders

Now that you’re starting to understand how appenders work and just how flexible and powerful they are, let’s focus on another foundational component in Logback.

The components responsible for transforming a log message to the desired output format are layouts and encoders.

Layouts can only transform a message into String, while encoders are more flexible and can transform the message into a byte array, then write that to an OutputStream. This means encoders have more control over when and how bytes are written.

As a result, starting with version 0.9.19, layouts have been deprecated, but they can still be used for a transition period. If you do still use layouts actively, Logback will print a warning message:

This appender no longer admits a layout as a sub-component, set an encoder instead.

While they’re starting to be phased out, layouts are still widely used and quite a powerful component on their own, so they’re well-worth understanding.

Some of the most commonly used layouts are PatternLayout, HTMLLayout and XMLLayout – let’s have a quick look at these in practice.

The PatternLayout

This layout creates a String from a log message based on a conversion pattern.

The pattern is quite flexible and allows declaring several conversion specifiers – which can control the characteristics of the output String such as length and color and can also insert values into the output String.

Let’s see an example of a PatternLayout that prints the name of the logging thread in green, the logger name with a length of 50 characters and displays the log levels using different colors, with the %highlight modifier:

In the example above, the layout is used by a FileAppender to print logs to a log.html file.

Here’s what the content of the HTML file will look like, using the default CSS:

So far, we’ve used in the layout examples the two main encoders available: PatternLayoutEncoder and LayoutWrappingEncoder. The purpose of these encoders is to support the transition from layouts to encoders.

Of course, future versions of Logback will add additional encoders with more powerful capabilities.

Loggers

Loggers are the third main component of Logback, which developers can use to log messages at a certain level.

Parameterized Logging

In some cases, the log message may contain parameters which have to be calculated. But, keep in mind that, if the log level for the message is not enabled, then the calculation is not really necessary.

One way to avoid this and therefore improve performance is to check the log level before logging the message and constructing the parameter:

This format ensures that the logger will first verify is the log level is enabled, and only afterward will it determine and use the value of the parameters in the log message.

Logger Additivity

By default, a log message will be displayed by the logger which writes it, as well as the ancestor loggers. And, since root is the ancestor of all loggers, all messages will also be displayed by the root logger.

To disable this behavior, you need to set the additivity=false property on the logger element:

Filtering Logs

Deciding what log information gets processed based on the log level is a good way to get started, but at some point, that’s simply not enough.

Logback has solid support for additional filtering, beyond just the log level, This is done with the help of filters – which determine whether a log message should be displayed or not.

Simply put, a filter needs to implement the Filter class, with a single decide() method. This method returns enumeration values of type FilterReply: DENY, NEUTRAL or ACCEPT.

The DENY value indicates the log event will not be processed, while ACCEPT means the log event is processed, skipping the evaluation of the remaining filters.

Finally, NEUTRAL allows the next filters in the chain to be evaluated. If there are no more filters, the message is logged.

Here are the primary types of filters we have available: LevelFilter, ThresholdFilter and EvaluatorFilter.

The LevelFilter and ThresholdFilter are related to the log level, with the difference that LevelFilter verifies if a log message is equal to a given level, while the ThresholdFilter checks if log events are below a specified level.

The example above configures a filter that only accepts log messages that have a level higher than DEBUG and contain the “employee” text.

Finally, for more high-level filtering, Logback also provides the TurboFilter class.

The TurboFilter

This filter behaves in a similar way to the Filter class, with the distinction that it’s not associated to a specific appender. Instead of accessing a logger object, it’s connected to the logging context and is invoked for every logging request.

Here’s a simple implementation of this class – the DuplicateMessageFilter:

This configuration only allows 2 repetitions of the same log message (meaning 3 instances of it) and eliminates all subsequent ones.

Conditional Processing of Configuration Files

Logback supports <if>, <then>, <else> elements which control whether a part of the configuration is processed or not. This is a unique feature among logging libraries and requires the previously mentioned janino library.

To define the conditions evaluated to control the processing of configuration, you can use the Java language. Furthermore, the expressions only have access to context or system properties.

A common use case is enabling different configuration on different environments:

This example configures the root logger to display messages of all levels to the console, but only for the development environment, defined through an env=dev property in the application.properties file.

Extending Logback

Beyond the many features that Logback already contains, its architecture allows for the possibility of creating custom components that you can use in the same way as the default ones.

For example, here are several ways you can extend Logback’s functionality:

create a custom appender by extending the AppenderBase class and implementing the append() method

create a custom layout by subclassing the LayoutBase class and defining a doLayout() method

create a custom filter by extending the Filter class and implementing the decide() method

create a custom TurboFilter by extending the TurboFilter class and overriding the decide() method

The configuration of the custom elements is the same as that of the standard elements.

Let’s define a custom TurboFilter that will ignore all log messages of a specific logger:

Conclusion

You have a number of options to chose from when it comes to logging in the Java ecosystem. Out of all of these, Logback is certainly a great choice and a very powerful library. It also brings several improvements over log4j, such as better overall performance, more advanced filtering possibilities, automatic reloading of configuration files, automatic removal of archived log files and many other advanced and useful features. And, due to the native slf4j support, we also have the unique option to easily switch to a different logging library at any point, if we want to. Overall, the maturity and impressive flexibility of Logback have made it the go-to option next to Log4J2, for most of the Java ecosystem today.