Skip to main content

Logging with SLF4J

I. Introduction

SLF4J is an abstract layer for logging APIs. The principle is roughly the same as Jakarta Commons Logging. The advantages of the use of such a layer enable to be completely independant of the logging implementation. So it's possible to easily change the logging implementation without modifying the existing code. You only have to change the configuration of the implementation. And finally in the case of the conception of a library, this leaves the user to choose the logging system.

You gonna tell me : if Commons Logging makes already that, why an other framework of logging abstraction ? Simply, because Commons Logging as his defaults. The first one concern the loading of the logging implementation. Indeed, the search is made dynamically at the execution through a classloader system. And this method can be problematic in several situations by example when the application use custom classloaders or when using OSGi. And finally the implementation of the Commons Logging can cause some memory leaks.

Furthermore, Commons Logging constrain the user to test if a loging level is enabled or not before making logging containing heavy concatenation.

We will see than SLF4J solve this problems in an efficient way.

You can download the official distribution on the SLFJ4J website.

2. Select the logging implementation

Unlike of Commons Logging, SLF4J doesn't resolve the logging implementation at execution, but directly at the compilation with a bridging API. So more than the JAR of SLF4J you need the following JARs : the bridging JAR and the JAR of the implementation. Here is what you get with Log4J :

SLF4J Implementation Selection

All that is made only by adding a JAR to the classpath. No need to configure nothing else than the implementation. You just have to be careful to call only the interface of SLF4J otherwise there is no more interest to use an abstract layer

3. Redirect calls from others implementations

More than offer a logging abstraction, SLF4J give the user the possibility to redirect calls for an implementation to SLF4J who will himself redirect them to the used implementation.

For that, you just have to replace the Jar of the xxx implementation by the xxx-to-slf4j.jar file who will intercept the calls and redirect them to the SLF4J implementation.

The following diagram show exactly how the calls are redirected :

SLF4J Calls redirection

Warning : Of course, you mustn't add the implementation and the jar of redirection in the classpath at the same time. And more important, you must never use the jar of redirection of the used SLF4J implementation. This creates an infinite circle that will be make your application crashes.

4. API use

The API of SLF4J is not really hard to use and is very like the others implementations or abstractions like Log4J or Commons Logging. Here is how to get a logger :

org.slf4j.Logger logger = org.slf4j.LoggerFactory.getLogger(HelloWorld.class);

Then, you can use it like any other implementation with the methods debug(), info(), warn(), trace() et error(). Par exemple :"Hello World");

But this is not the strength of SLF4J. Let's take a simple logging example :

logger.debug("Info : x = " + info.getX() + ", y = " + info.getY() + ", str = " + infos.getStr());

That means than all times this line is executed, a concatenation is done. This can quickly be heavy for the performance if the code is executed regularly. It's because we must first test if the level is enabled :

    logger.debug("Info : x = " + info.getX() + ", y = " + info.getY() + ", str = " + infos.getStr());

This times the performances are guaranteed, but this kind of code is not esthetical and is quickly heavy. Moreover the test is already done in the debug method who displays nothing if the debug level is not enabled. So why do the work twice ? SLF4J offer a new alternative really interesting :

logger.debug("Info : x = {}, y = {}, str = {}", new Object[]{info.getX(), info.getY(), infos.getStr()});

The {} will be replaced by the parameters. The, the concatenation is only done if the logging level is enabled. Furthermore, this code is really cleaner than the two previous.

Warning : In the case when the recuperation of the informations is heavy, you must test use the first method and test the level of log before logging the informations. By example we could imagine a getDebugInfos() on an object who mades a lot of informations to get informations. This method must not be executed if the log level is not enabled.

5. Marker API

The Marker allow essentially to associate tags to logs. This tags enable the different appenders to take only some logs. Lets imagine an appender who write the log using encryption and that must only be used on logs marked as confidentials. The Marker enable us to implement that.

This functionaly is only available with the LogBack implementation : it's the only who implements the Marker. Nevertheless, you could use the Marker API with the other implementation, but that will have no effect.

Imagine by example an appender named CryptAppender who encode the log using some algorithm. We could configure it thus :

<appender name="CRYPTED" class="CryptAppender">
  <layout class="ch.qos.logback.classic.html.HTMLLayout">
    <throwableRenderer class="ch.qos.logback.classic.html.DefaultThrowableRenderer" />
  <evaluator class="ch.qos.logback.classic.boolex.OnMarkerEvaluator">

The OnMarkerEvaluator enable to select only logs than have been marked with some tag. In our case, we have to mark to confidential logs with CONFIDENTIAl. Here is how to do that :

Marker confidentialMarker = MarkerFactory.getMarker("CONFIDENTIAL");
logger.error(confidentialMarker, "C'est confidentiel !");

That will not display the log directly but crypted.

An other use of this functionality could by example be the send by mail of some logs.

You could of course configure several appenders for the same tag. You could also create a Marker hierarchy :

Marker parentMarker = MarkerFactory.getMarker("parent");
Marker childMarker = MarkerFactory.getMarker("child");

Doing that, all that is logged with parentMarker or childMarker will be treated in appenders configured for parent and the appenders configured for child will only see what is logged with child.

6. Mapped Diagnostic Context (MDC)

The "Mapped Diagnostic Context" (MDC) is, in summary, a simple map (key-value set) maintained by the logging framework. In that map, the application can put some key-value couple that could be used to add some informations in the logs.

Imagine that we treat informations on persons and that we display in logging. The name and the firstname of the person must be displayed in each lines of logging. We could can use MDC to automate the include of the name and the firstname in the logs.

It is possible to add values in MDC with the put method :

MDC.put("prenom", "Baptiste");
MDC.put("nom", "Wicht");

Then, we can use them in the layout of logging. This layout can be configured in the implementation. At this time only Log4J, JUL and Logback support MDC. Here is a layout using MDC :

%X{prenom} %X{nom} - %m%n

Then, to display informations, we just have to do :"Age {}", age);"Localisation {}", localisation); 

That will display :

Baptiste Wicht - Age 22
Baptiste Wicht - Localisation Suisse

And finally if the MDC is configured between to logging instructions, this will directly change the logging :"Age {}", age);
MDC.put("prenom", "Jacques");"Localisation {}", localisation);

will display :

Baptiste Wicht - Age 22
Jacques Wicht - Localisation Suisse

That can be very practical to stock and display global informations. For example a login, a session id or any other data.

7. Conclusion

In conclusion, SLF4J is really good abstraction layer. It's really powerful, but remains really simple to use and the style is the same as the other existing logging systems.

The author of SLF4J advises to use LogBack as implementation. This is the reference implementation.

If you any comment to do on this article, don't hesitate to put it on the above form.


Comments powered by Disqus