Frequently Asked Questions about SLF4J

Ceki Gülcü
April 2005, last updated on June 28th, 2005


Section 1. Generalities
Question 1.1 What is SLF4J?
Question 1.2 When should SLF4J be used?
Question 1.3 When using SLF4J, do I have to recompile my application to switch to a different logging system?
Question 1.4 Why is SLF4J licensed under X11 type license instead of the Apache Software License?
Question 1.5 Where can I get a particular SLF4J binding?
Question 1.6 Should my library attempt to configure logging?

Section 2. About the SLF4J API
Question 2.1 Why don't the printing methods in the Logger interface accept message of type Object, but only messages of type String?
Question 2.2 Can I log an exception without an accompanying message?
Question 2.3 What is the fastest way of (not) logging?
Question 2.4 How can I log the string contents of a single (possibly complex) object?
Question 2.5 Why doesn't the org.slf4j.Logger interface have methods for the FATAL level?
Question 2.6 Why doesn't the org.slf4j.Logger interface have methods for the TRACE level?
Question 2.7 What is the difference between slf4j-log4j12.jar and slf4j-log4j13.jar?

Section 3. Implementing the SLF4J API
Question 3.1 How do I make my logging framework SLF4J compatible?
Question 3.2 How can my logging system add support for the Marker interface?

 

Section 1. Generalities

This section contains general questions about SLF4J.

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. On the other hand, it does not always make sense for stand-alone applications to use SLF4J. Stand-alone applications should consider invoking the logging system of their choice directly.

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 same point in time, any non-trivial application will need to directly invoke the underlying logging API implementation. In other words, complete independence from the logging API implementation is not possible for a stand-alone application. Nevertheless, SLF4J reduces the impact of this dependence to near-painless levels.

Suppose that your CRM application uses log4j for its logging. However, one of your important clients request that logging be performed through JDK 1.4 logging. If your application is riddles with thousands of direct log4j calls, migration to JDK 1.4 would be a long and error-prone process. Had you been invoking SLF4J instead of log4j, the migration could be completed in a matter of minutes instead of hours.

SLF4J lets component developers to defer the choice of the logging system to the end-user. However, eventually a choice needs to be made.

1.3 When using SLF4J, do I have to recompile my application to switch to a different logging system?
No, you do not need to recompile your application. You can switch to a different logging system by removing the previous SLF4J binding and replacing it with the binding of your choice.

For example, if you were using the NOP implementation and would like to switch to log4j version 1.2, simply replace slf4j-nop.jar with slf4j-log4j12.jar on your class path but do not forget to add log4j-1.2.x.jar as well. Want to switch to JDK 1.4 logging? Just replace slf4j-log4j12.jar with slf4j-jdk14.jar.

1.4 Why is SLF4J licensed under X11 type license instead of the Apache Software License?

SLF4J is licensed under a permissive X11 type license instead of the ASL or the LGPL because the X11 license is deemed by both the Apache Software Foundation as well as the Free Software Foundation as compatible with their respective licenses.

1.5 Where can I get a particular SLF4J binding?

SLF4J bindings for SimpleLogger, NOPLogger, LoggerLoggerAdapter and JDK14LoggerAdapter are contained within the files slf4j-nop.jar, slf4j-simple.jar, slf4j-log4j12.jar, slf4j-log4j13.jar and slf4j-jdk14.jar. These files ship with the official SLF4J distribution.

The binding for NLOG4J ships with the NLOG4J distribution. If you use NLOG4J, you don't need to load SLF4J because NLOG4J 1.2.10 and later already contain the SLF4J interfaces and classes.

1.6 Should my library attempt to configure logging?

Embedded components such as libraries do not need and should not configure the logging system. They invoke SLF4J to log but should let the end-user configure the logging environment. When embedded components try to configure logging on their own, they often override the end-user's wishes. At the end of the day, it is the end-user who has to read the logs and process them. She should be the person to decide how she wants her logging configured.

Section 2. About the SLF4J API

This section contains questions about the SLF4J API, in particular the Logger interface.

2.1 Why don't the printing methods in the Logger interface accept message of type Object, but only messages of type String?

In SLF4J 1.0beta4, the printing methods such as debug(), info(), warn(), error() in the Logger interface were modified so as to accept only messages of type String instead of Object.

Thus, the set of printing methods for the DEBUG level became:

debug(String msg); debug(String format, Object arg); debug(String format, Object arg1, Object arg2); debug(String msg, Throwable t);

Previously, the first argument in the above methods was of type Object.

This change enforces the notion that logging systems are about decorating and handling messages of type String, and not any arbitrary type (Object).

Just as importantly, the new set of method signatures offer a clearer differentiation between the overladed methods whereas previously the choice of the invoked method due to Java overloding rules were not always easy to follow.

It was also easy to make mistakes. For example, previously it was legal to write:

logger.debug(new Exception("some error"));

Unfortunately, the above call did not print the stack trace of the exception. Thus, a potentially crucial piece of information could be lost. When the first parameter is restricted to be of type String, then only the method

debug(String msg, Throwable t)

can be used to log exceptions. Note that this method ensures that every logged exception is accompanied with a descriptive message.

2.2 Can I log an exception without an accompanying message?

In short, no.

If e is an Exception, and you would like to log an exception at the ERROR level, you must add an accompanying message. For example,

logger.error("some accompanying message", e);

You might correctly observe that not all exceptions have a meaningful message to accompany them. Moreover, a good exception should already contain a self explanatory description. The accompanying message may therefore be considered redundant.

While these are valid arguments, there are three opposing arguments also worth considering. First, on many, albeit not all occasions, the accompanying message can convey useful information nicely complementing the description contained in the exception. Frequently, at the point where the exception is logged, the developer has access to more contextual information than at the point where the exception is thrown. Second, it is not difficult to imagine more or less generic messages, e.g. "Exception caught", "Exception follows", that can be used as the first argument for error(String msg, Throwable t) invocations. Third, most log output formats display the message on a line, followed by the exception on a separate line. Thus, the message line would look inconsistent without a message.

In short, if the user were allowed to log an exception without an accompanying message, it would be the job of the logging system to invent a message. This is actually what the throwing(String sourceClass, String sourceMethod, Throwable thrown) method in java.util.logging package does. (It decides on its own that accompanying message is the string "THROW".)

It may initially appear strange to require an accompanying message to log an exception. Nevertheless, this is common practice in all log4j derived systems such as java.util.logging, logkit, etc. and of course log4j itself. It seems that the current consensus considers requiring an accompanying message as a good a thing (TM).

2.3 What is the fastest way of (not) logging?

For some Logger logger, writing,

logger.debug("Entry number: " + i + " is " + String.valueOf(entry[i]));

incurs the cost of constructing the message parameter, that is converting both integer i and entry[i] to a String, and concatenating intermediate strings. This, regardless of whether the message will be logged or not.

One possible way to avoid the cost of parameter construction is by surrounding the log statement with a test. Here is an example.

if(logger.isDebugEnabled()) { logger.debug("Entry number: " + i + " is " + String.valueOf(entry[i])); }

This way you will not incur the cost of parameter construction if debugging is disabled for logger. On the other hand, if the logger is enabled for the DEBUG level, you will incur the cost of evaluating whether the logger is enabled or not, twice: once in debugEnabled and once in debug. This is an insignificant overhead because evaluating a logger takes less than 1% of the time it takes to actually log a statement.

Better alternative based on format messages

There exists a very convenient alternative based on message formats. Assuming entry is an object, you can write:

Object entry = new SomeObject(); logger.debug("The entry is {}.", entry);

After evaluting whether to log or not, and only if the decision is affirmative, will the logger implementation format the message and replace the '{}' pair with the string value of entry. In other words, tis form does not incur the cost of parameter construction in case the log statement is disabled.

The following two lines will yield the exact same output. However, the second form will outperform the first form by a factor of at least 30, in case of a disabled logging statement.

logger.debug("The new entry is "+entry+"."); logger.debug("The new entry is {}.", entry);

A two argument variant is also availalble. For example, you can write:

logger.debug("The new entry is {}. It replaces {}.", entry, oldEntry);

If three or more arguments need to be passed, an Object[] variant is also availalble. For example, you can write:

logger.debug("Value {} was inserted between {} and {}.", new Object[] {newVal, below, above});

2.4 How can I log the string contents of a single (possibly complex) object?

In relatively rare cases where the message to be logged is the string form of an object, then the parameterized printing method of the appropriate level can be used. Assuming complexObject is an object of certain complexity, for a log statement of level DEBUG, you can write:

logger.debug("{}", complexObject);

The logging system will invoke complexObject.toString() method only after it has ascertained that the log statement was enabled. Otherwise, the cost of complexObject.toString() conversion will be advantageously avoided.

2.5 Why doesn't the org.slf4j.Logger interface have methods for the FATAL level?

From the stand point of a logging system, the distinction between a fatal error and an error is usually not very useful. Most programmers exit the application when a fatal error is encountered. However, a logging library cannot (and should not) decide on its own to terminate an application. The initiative to exit the application must be left to the developer.

Thus, the most the FATAL level can do is to highlight a given error as the cause for application to crash. However, errors are by definition exceptional events that merit attention. If a given situation causes errors to be logged, the causes should be attended to as soon as possible. However, if the "error" is actually a normal situation which cannot be prevented but merits being aware of, then it should be marked as WARN, not ERROR.

Assuming the ERROR level designates exceptional situations meriting close attention, we are inclined to believe that the FATAL level is superfluous.

2.6 Why doesn't the org.slf4j.Logger interface have methods for the TRACE level?

The addition of the TRACE level has been frequently and hotly debated request. By studying various projects, it looks like the TRACE level is mostly used to disable logging output from certain classes without needing to configure logging for those classes. Indeed, the TRACE level is by default disabled in log4j and other logging systems. We believe that the same result could be achieved by adding the appropriate directives in configuration files.

Thus, in the majority of cases the TRACE level has the same semantic meaning as DEBUG. In such case, the TRACE level merely saves a few configuration directives. In the rare but interesting cases where TRACE has a different meaning than DEBUG, Marker objects can be put to use to convey the desired new meaning.

2.7 What is the difference between slf4j-log4j12.jar and slf4j-log4j13.jar?

Although log4j versions 1.2 and 1.3 are compile-time compatible, due to highly technical reasons they are not run-time compatible. This means that an slf4j-log4j binding compiled with log4j 1.2 will not run with log4j 1.3 and vice versa. The slf4j-log4j12.jar binding is compiled with log4j 1.2 and will run without problems with log4j 1.2. However, slf4j-log4j12.jar will not run with log4j 1.3. Similarly, The slf4j-log4j13.jar binding is compiled with log4j 1.3 and will run without problems with log4j 1.3. However, slf4j-log4j13.jar will not run with log4j 1.2.

Section 3. Implementing the SLF4J API

This section contains questions asked by developers of logging systems wishing to conform with SLF4J.

3.1 How do I make my logging framework SLF4J compatible?

We suggest that the Logger class in your framework directly implement the org.slf4j.Logger interface.

We further recommend that your framework copy the SLF4J java source files (.java files) into your source tree. This can be accomplished by checking out SLF4J files from its source code repository using Subversion, more on this later.

Client code will only refer to LoggerFactory to retreive org.slf4j.Logger instances, your framework needs to instruct LoggerFactory to return your framework's Logger instances. This step is usually reffered to as binding SLF4J with your framework. In recent versions of SLF4J, the binding information has been isolated into a small class named StaticLoggerBinder.

With the static binding approach we recommend, your framework's copy of StaticBinder class needs to be customized before compile time. You should be able to easily adapt NLOG4J's copy of StaticLoggerBinder.java to your framework. Note that you do not need to change any other classes to ensure correct binding.

Just as importantly, adapting SLF4J to your system can be an entirely automated process. Thus, when there are small changes to the SLF4J API, you can update to the latest SLF4J version without needing to edit any java files. For example, NLOG4J uses an entirely automated build procedure to synchronize with SLF4J changes.

For a complete example of an automated build file, please refer to the slf4j.xml build file in the NLOG4J project.

3.2 How can my logging system add support for the Marker interface?

Markers consitute a revolunary concept which is supported by LOGBack but not other existing logging systems. Consequently, SLF4J confromant logging systems are allowed to ignore marker data passed by the user.

However, even though marker data may be ignored, the user must still be allowed to specify marker data. Otherwise, users would not be able to switch between logging systems that support markers and those that do not. In order to provide minimal support for markers, SLF4J conformant systems need to to include certain Marker related classes, namely, org.slf4j.Marker, org.slf4j.IMarkerFactory, org.slf4j.MarkerFactory, org.slf4j.impl.BasicMarker, org.slf4j.impl.BasicMarkerFactory, org.slf4j.impl.MarkerIgnoringBase, org.slf4j.impl.StaticMarkerBinder and org.slf4j.spi.MarkerFactoryBinder. Al of these classes are availalbe in the SLF4J subversion repository.

The MarkerIgnoringBase class can serve as a base for adapters or native implementations of logging systems lacking marker support. In MarkerIgnoringBase, methods taking marker data simply invoke the corresponding method without the Marker argument, discarding any Marker data passed as argument. Your SLF4J adapters can extend MarkerIgnoringBase to quickly implement the methods in org.slf4j.Logger which take a Marker as the first argument.