Is it worth to use slf4j with log4j2

Go ahead: program to the log4j2 API instead of slf4j

It's safe: the Log4j2 API offers the exact same guarantees as slf4j - and more.

Now that Log4j2 itself is separated into an API and an implementation module, there is no longer any value in using SLF4J.

Yes, it is good engineering practice to keep your options open. You may want to change to another logging implementation later.

For the last 10 years or so, building such flexibility in your application meant using a wrapper API like SLF4J. This flexibility doesn't come for free though: the disadvantage of this approach is that your application cannot use the richer feature set of the underlying logging library.

Log4j2 offers a solution that doesn't require that your application is restricted to the lowest common denominator.

The escape valve: log4j-to-slf4j

Log4j2 includes a log4j-to-slf4j bridge module. Any application coded against the Log4j2 API can choose to switch the backing implementation to any slf4j-compliant implementation at any time.

log4j-to-slf4j

As mentioned in the question, using the Log4j2 API directly offers more functionality and has some non-functional advantages versus using a wrapper API like slf4j:

  • Message API
  • Lambdas for lazy logging
  • Log any Object instead of just Strings
  • Garbage-free: avoid creating varargs or creating Strings where possible
  • CloseableThreadContext automatically removes items from the MDC when you're finished with them

(See 10 Log4j2 API features not available in SLF4J for more details.)

Applications can safely use these rich features of the Log4j2 API without being locked in to the native Log4j2 core implementation.

SLF4J is still your safety valve, it just doesn't mean your application should code against the SLF4J API anymore.


Disclosure: I contribute to Log4j2.


Update: There seems to be some confusion that programming to the Log4j2 API somehow introduces a "facade for a facade". There is no difference in this respect between the Log4j2 API and SLF4J.

Both APIs require 2 dependencies when using a native implementation, and 4 dependencies for a non-native implementation. SLF4J and the Log4j2 API are identical in this respect. For example:

Required dependencies with log4j-api as API with SLF4J as API
Log4j 2 as implementation 2: log4j-api and log4j-core 4: slf4j, log4j-slf4j-impl, log4j-api, log4j-core
Logback as implementation 4: log4j-api, log4j-to-slf4j, slf4j, Logback 2: slf4j and Logback

There are quite a few considerations which make logging "more complicated than it seems at first glance", (hence the multi-decade of bitter in-fighting!).

Separation of Concerns

At the end of the day, code 'sends log data' and that data 'ends up somewhere'. But where it ends up depends on the purpose for which it is being gathered. Greatly complicating the picture is the fact that modern software is composed of various components, and they all potentially have a need to log.

Let's consider a worst-case: all components use System.out#println(String). At least all the statements are in execution-order, but it might not be simple to discern which component generated each piece of output. And some components might be overly verbose for the context in which they are used.

Let's consider a next-worst-case: all components make their own arrangements for controlling their logging behaviour and destination. The admin might have to configure dozens of log systems for a single piece of software. Now the log statements are not together and they're out of order. Hopefully they all have a consistent timestamp strategy!

We would like something in-between: something whereby code can say 'log this' and an admin can control where it ends up.

An overly brief history

Enter Log4J v1, which tackled the problem with notions of 'levels', 'appenders', 'filters' 'layouts' and 'contexts' ... a conceptual architecture supported by a hierarchical 'logger namespace' (including a way to naturally leverage off Java package namespaces), plus a configuration mechanism for easy management.

That was all fine and good ... so long as all the components in the software relied on the same version! There was a time when these things were in flux. The main contribution of SLF4J was to 'harden' these concepts into a stable API from the point of view of the component-developer, without prejudicing the options of the admin for doing their part of the work. Libraries could rely on the SLF4J 'facade', expecting that they'd be talking to the 'implementation' just a few calls down the stack. Administrators could choose what suited them to compose the logs into a coherent record they cared about.

(It is even more complicated when your software runs in a container, and the container has its own logging needs, and you're not even the same app running in the container ... Tomcat's JULI logging - used for its own internal logging - 'gets out of the way' of apps running in classloader sub-contexts.)

Mysteriously scorning the work by Log4J, the Java Community Process decided to implement much-the-same conceptual architecture in java.util.logging, with arguably less flexibility in the details. However, with j.u.l being essentially a sub-set of the semantic richness of SLF4J, it was easy make SLF4J a facade to j.u.l.

Apache's Commons Util Logging was probably not very necessary. Ceki's own Logback introduced management features which Log4J v1 did not have at the time - not least being an implementation of SLF4J and solving all those very-real classloader headaches, but also providing a competent implementation with some appealing features for the administrator.

Logging for different situations

But logging is done in a lot of different contexts. Whacking those messages out to super-slow I/O without unduly blocking the thread, and not paying the price of computing the log message unless it's needed, and producing coherent logs in multi-threaded contexts ... these things all matter. (Which is why java.util.logging is not often used!).

Sometimes, the required optimisations will impact on the conceptual architecture, which in turn must impact on the developer-side API. For example, the opportunities presented by closures will definitely speed things up if the log message ends up being a no-op because of filtering. Either a SLF4J.next or some other API needs to be considered to make use of that feature, and Log4J2 need not be excluded from this decision. With the API portion being a conceptual super-set of that offered by SLF4J, it is simple to make it a facade either for SLF4J and those implementations under it ... or more direct bridges to what the administrator has preferred.

For the app-developer, it really doesn't matter, so long as ultimately, there is one logging facility chosen by the administrator, and all the components can get a log out to it. If the facility can accept messages via SLF4J and Log4J2-the-API, then that's great. Log4J2-the-implementation does just this. You can have your cake and eat it too: your application can enjoy the opportunities offered by Log4J2-the-API while still using libraries that are adequately catered-for by SLF4J. If the administrator scorns Log4J2-the-implementation (though it is hard to see why they would, from any angle), then they can use anything that already supports SLF4J without waiting for that logging implementation to support Log4J2-the-API. You can have your cake and eat it.

For library developers, it is more of an issue. The safe path is SLF4J because of its widespread adoption. If logging is critical to the success of your library ... particularly if it is multi-threaded, log statements are potentially expensive to produce, and it might be better to omit the processing if they will not ultimately be consumed, if there is a large volume of log statements to handle, performance is critical, and your users are likely to appreciate the benefits of Log4J2-the-implementation, then do Log4J2. But you're not stealing opportunities from your users by remaining with SLF4J, either. The admin can still use Log4J-the-implmenation if they wish.

The bottom line

If you want the features afforded by Log4J2 then go for them. If you don't need them, SLF4J is a mature, stable interface with a lot of support. SLF4J remains an important piece of the open-source infrastructure. Ceki has done the community a great service, for a lot of griping in return.

But rich APIs supported by competent implementations prevail in the end. Today's stability is tomorrows stagnation. The process of refinement keeps going. No need to get off the bus so long as it is going where you want to go.