Migrating from Logback

Logback is a logging implementation for the SLF4J logging API, just like Log4j Core is a logging implementation for the Log4j API. In this page we will guide you through migrating from Logback to Log4j Core as your logging implementation.

Instead of migrating your logging implementation, Logback, are you looking for migrating your logging API, SLF4J? Please refer to Migrating from SLF4J.

Struggling with the logging API, implementation, and bridge concepts? Click for an introduction.
Logging API

A logging API is an interface your code or your dependencies directly logs against. It is required at compile-time. It is implementation agnostic to ensure that your application can write logs, but is not tied to a specific logging implementation. Log4j API, SLF4J, JUL (Java Logging), JCL (Apache Commons Logging), JPL (Java Platform Logging) and JBoss Logging are major logging APIs.

Logging implementation

A logging implementation is only required at runtime and can be changed without the need to recompile your software. Log4j Core, JUL (Java Logging), Logback are the most well-known logging implementations.

Logging bridge

Logging implementations accept input from a single logging API of their preference; Log4j Core from Log4j API, Logback from SLF4J, etc. A logging bridge is a simple logging implementation of a logging API that forwards all messages to a foreign logging API. Logging bridges allow a logging implementation to accept input from other logging APIs that are not their primary logging API. For instance, log4j-slf4j2-impl bridges SLF4J calls to Log4 API and effectively enables Log4j Core to accept input from SLF4J.

To make things a little bit more tangible, consider the following visualization of a typical Log4j Core installation with bridges for an application:

Visualization of a typical Log4j Core installation with SLF4J, JUL, and JPL bridges
@startuml

frame "Compile time" {
  [Application] --> [Log4j API] : logs to

  [Log4j API] #Cyan

  [SLF4J] #Cyan

  [Library 1] --> [SLF4J] : logs to
  [Application] --> [Library 1] : uses
  [Application] --> [Library 2] : uses
  [Application] --> [Library 3] : uses
}

frame Runtime {

  [Log4j Core] <.. [Log4j API] : is implemented by
  [Log4j Core] <.. (log4j2.xml) : is provided to
  [Log4j Core] #LightGreen

  [JPL-to-Log4j] ..> [Log4j Core] : forwards to
  [JPL-to-Log4j] #Yellow

  [SLF4J-to-Log4j] ..> [Log4j Core] : forwards to
  [SLF4J-to-Log4j] #Yellow

  [JUL-to-Log4j] ..> [Log4j Core] : forwards to
  [JUL-to-Log4j] #Yellow

  frame JRE {
    [JPL] #Cyan
    [JUL] #Cyan
  }

}

[Library 2] --> [JUL] : logs to
[Library 3] --> [JPL] : logs to

[JPL] ..> [JPL-to-Log4j] : is implemented by
[JUL] ..> [JUL-to-Log4j] : is implemented by
[SLF4J] ..> [SLF4J-to-Log4j] : is implemented by

legend top right
  | <#LightGreen> | Logging implementation |
  | <#Yellow> | Logging bridge |
  | <#Cyan> | Logging API |
  | <size:18><U+2192></size> | Compile-time usage |
  | <size:18><U+21E2></size> | Runtime usage |
endlegend

@enduml

Migrating

You either have an application using Logback at runtime, or have a library using Logback for tests. In either case, you can replace Logback with Log4j Core as follows:

  1. Remove ch.qos.logback:logback-classic dependency

  2. Remove logback.xml and logback-test.xml files

  3. Follow the instructions shared in the "Getting started" page

Next you need to re-organize your logging API bridges such that all foreign APIs are bridged to Log4j API, the logging API implemented by Log4j Core. This is explained in the next section.

Bridges

It is highly likely that you were bridging all logging APIs (including Log4j API!) to SLF4J, the logging API implemented by Logback. There are two particular approaches you can take here to ensure all logging APIs are instead bridged to Log4j API, the logging API implemented by Log4j Core:

Bridge all logging APIs to Log4j API

We strongly advise you to bridge all foreign logging APIs directly to Log4j API. You can use the cheat sheet shared below to implement that.

Table 1. Dependency migration cheat sheet
If dependency present replace with

org.apache.logging.log4j:log4j-to-slf4j

org.apache.logging.log4j:log4j-slf4j2-impl

org.slf4j:jcl-over-slf4j

commons-logging:commons-logging version >=1.3.0

org.slf4j:jul-to-slf4j

org.apache.logging.log4j:log4j-jul

org.slf4j:log4j-over-slf4j

org.apache.logging.log4j:log4j-1.2-api

org.springframework:spring-boot-starter-logging

org.springframework:spring-boot-starter-log4j2

Bridge all logging APIs to SLF4J, and bridge SLF4J to Log4j API

You can implement this by replacing org.apache.logging.log4j:log4j-to-slf4j dependency with org.apache.logging.log4j:log4j-slf4j2-impl.

This approach is not recommended! It incurs certain drawbacks since some logging API calls will need to cross multiple bridges. For instance, a call to JUL will first be bridged to SLF4J, and then from there to Log4j API.

Configuration

It might not always be trivial to match the contents of the newly created log4j2.xml and log4j2-test.xml files with your old logback.xml and logback-test.xml files. While all Logback components have corresponding equivalents in Log4j Core, they might not be sharing the same name or configuration. To assist with migrating Logback configuration components to Log4j Core, see the following pages:

For the complete list of all Log4j configuration knobs, see the Configuration page.

Parameterized logging

A common mistake in parameterized logging is to add a {} placeholder for the exception associated with a log event:

} catch (Exception exception) {
    logger.error("The foo process exited with an error: {}", exception);
}

Log4j Core and Logback differ in the way they treat this statement:

Logback

Logback interprets the exception argument as throwable and removes it from the list of parameters. We end up with a parameterized statement with one placeholder, but zero parameters. The placeholder therefore remains as is:

The foo process exited with and error: {}
java.lang.RuntimeException: Message
    at example.MigrateFromLogback.doLogWrong(MigrateFromLogback.java:10)
...
Log4j Core

Log4j Core first looks for the parameters of the message. Since the format string has one placeholder, the exception argument is interpreted as a parameter of the log message. The throwable associated to the log event is null, which results in a missing stack trace:

The foo process exited with and error: java.lang.RuntimeException: Message

To fix this problem and get the same output in both backends, you should remove the placeholder from the format string:

} catch (Exception exception) {
    logger.error("The foo process exited with an error.", exception);
}

After the change, the output will look us:

The foo process exited with and error.
java.lang.RuntimeException: Message
    at example.MigrateFromLogback.doLogWrong(MigrateFromLogback.java:10)
...

As a temporary solution, the SLF4J-to-Log4j API bridges contain a special MessageFactory that classifies trailing Throwable arguments in the same way Logback does. To use it, you need to set the log4j2.messageFactory configuration property to org.apache.logging.slf4j.message.ThrowableConsumingMessageFactory.