SLF4J - Not Just Simple Logging in Java

A Java logging framework should be effective but low fuss. There are many to choose from - some would say too many perhaps.  What is needed is something that is simple, productive, reliable and with good performance.

SLF4J is a façade for logging in Java that achieves best-of-breed capability and plays nicely with pre-existing frameworks.

Log4J - the Forerunner

Probably the most successful earlier logging framework was Log4J, a well-respected Apache project. But a decade had passed since Log4j’s inception.  Its author, Ceki Gülcü, and others noted certain issues with Log4j.

  • Inter-operation with other logging frameworks (and there are many) is not very good
  • Performance is poor in certain cases
  • Some useful features were missing

The Simple Logging Façade For Java (SLF4J) was developed to meet these issues.

However, in Log4j’s favour, it had an API that was easy to learn and use and it is not hard to configure.  So SLF4J wisely takes the good features and improves them a little.  If you know Log4j, you’ll learn SLF4J easily.

Jakarta Commons Logging

One of the main motivations behind SLF4J was that an earlier attempt to unify logging, the Apache Jakarta Commons Logging (JCL) framework, resulted in certain technical problems in the implementation.  SLF4J was conceived to be both simpler and more robust. SLF4J is conceptually very similar to JCL.  In a nutshell, SLF4J avoids the class loader issues that plague JCL.

The Façade Pattern

SLF4J uses the Façade Pattern.  Put simply, this means it provides a simple, clean and performant API atop another logging framework.  So it doesn’t actually do any logging itself but relies on something else to do it.  It simply passes the logging messages from the application code to the underlying logging framework.

The purpose of such a façade is to decouple the API from the back-end.  Developers can be more productive by working to a consistent API and the code they produce can be more portable and integrate with work done by others without problems arising from conflicting logging APIs.

Only one back-end logger product is actually needed and it can easily be changed.  For example, a particular application may be delivered to one customer with Log4j at the back-end, but another customer requires use of the Java-util logging framework.  This is no problem if the code has been written using SLF4J because it supports both these back-ends (and several others).  The required back-end is simply swapped in and configured as needed.

Performance Improvements

Having peppered your code with LOG.debug() statements, you’d really like the performance to be good. In particular, when logging is disabled at debug level, it should take very little time to skip over those LOG.debug() statements.  Unfortunately, this is not always the case in Log4j, so SLF4J has taken steps to correct this deficiency. 

Firstly, the computation necessary to determine whether a particular class’s logger is enabled or disabled at each level has to execute quickly, and SLF4J does so (unlike Log4j).

A second benefit arises from the reduced need to call toString() methods and concatenate those strings.  This example illustrates why.

    LOG.debug( "Received message " + message + " from " + source );
    ...

When logging is enabled at debug level, the log message is determined by concatenating two constant strings with the toString() result from two objects, message and source.  When logging is disabled however, the same work is done but then the log message is simply discarded.  So there’s a performance penalty.  The temptation is to fix this just by commenting out the logging calls, but this has clearly missed the point of being unintrusive logging.

SLF4J provides a simple solution: extra logger methods exist which do this work only when needed.  Here’s that example again, modified for SLF4J:

    LOG.debug( "Received message {} from {}", message, source );
    ...

Now we’re using one of those new methods.  It has the signature

    void debug( String message, Object param1, Object param2 );

The message string contains {} placeholders.  These are a bit similar to those in java.text.MessageFormat, except that they are not numbered; their order must match the order of the parameters that follow.

There are two benefits. Firstly, the original string concatenation is not needed, it’s only a small overhead but if it’s in a tight inner loop then it may matter greatly.  Of course, the string processing still happens if debug is enabled.

Secondly, the two objects used as parameters can consume very close to zero processing effort.  Their toString() method will only get called when debug is enabled and not when it is disabled.  Sometimes, objects have slow expensive toString() methods, so it can be a useful performance benefit not calling it when the result would otherwise be thrown away almost immediately.

Additional Features

SLF4J adds a few features not in earlier frameworks.  Principally, the marker interface is intended to allow informative annotation of log messages.  It seems however only one of the logging back-ends (Logback) supports markers, and consequently they’re somewhat ignored.

Conclusion

Based on my own experience, I can quite simply say this: where possible, use SLF4J - it’ll make your life easier and your code (slightly) faster.  It does what it says on the tin.  It may originally have been intended  primarily for library developers, who need to avoid any coupling that causes their users difficulty.  But in a large application a similar argument applies, so you might as well use it anyway.

Further Reading

 
comments powered by Disqus