×
Community Blog Java Logging Part 2: Logging and Package Exclusion with SLF4J + Logback

Java Logging Part 2: Logging and Package Exclusion with SLF4J + Logback

This article is a comprehensive guide to Java logging. The second part of this series introduces SLF4J and Logback.

1

By Shangzuo

1. Why is SLF4J + Logback?

After reading through the previous article of the series, which is a little bit theoretical, we need to do something practical now. In this article, I will introduce the optimal logging solution — SLF4J + Logback — that I have discovered in practice. The reasons why I choose this solution are as follows:

  • SLF4J provides more APIs than Jakarta Commons Logging (JCL) and is compatible with all versions of IntelliJ IDEA. This is the core advantage of SLF4J. For more information, see the third part of the series.
  • SLF4J supports lazy evaluation of log content, which is superior to JCL. (Actually, SLF4J does not significantly outperform JCL[1], but coders always pursue ultimacy.)
  • On the prerequisite that SLF4J is determined, Logback, produced by the same developer as SLF4J and with excellent performance, is naturally selected as the implementation of the SLF4J API.
  • Nowadays, the SLF4J + Logback solution has become the priority of most developers (in 2021, SLF4J was selected by 76% of developers and Logback selected by 48% of developers[2]). If developers encounter problems during logging, they can refer to more documentation.

Next, let's set up the environment based on the theoretical knowledge explained above. I will give you the general idea first, and then the overall dependency configuration method. In order to save storage space, dependencies will be expressed in GAV coordinates separated by a colon(:), instead of XML.

2. Basic Dependencies

We can easily learn from the above-mentioned knowledge that the following three packages are necessary:

  • SLF4J is the basic logging facade, and its core API is in org.slf4j:slf4j-api.
  • The core implementation of Logback is in ch.qos.logback:logback-core.
  • The adapter that Logback provides for SLF4J is in ch.qos.logback:logback-classic.

The logback-classic package directly depends on the other two packages, and they are definitely of the most suitable versions for logback-classic. Therefore, your project can explicitly depend on only the logback-classic package to prevent ambiguity. If you want to increase the weight of a version, your project can depend only on this version.

In addition, it should be noted that the versions of SLF4J and Logback are not completely forward-compatible. The correspondence between SLF4J and Logback versions will be described below.

2.1 SLF4J Version Compatibility

A great update in SLF4J 2.0.x[3] is that Java Development Kit (JDK) ServiceLoader[4], a service provider interface (SPI), is used to automatically load and implement the org.slf4j.impl.StaticLoggerBinder class, without the need to search for the class. This is a feature supported in JDK 8, demonstrating SLF4J's dependency on JDK.

SLF4J Version JDK Version Remarks
SLF4J 1.7.x JDK 1.5 or later
SLF4J 2.0.x JDK 8 or later
SLF4J 2.1.x Probably JDK 11 or a later version Ceki is asking for comments.[5]

After releasing a few alpha/beta versions on the SLF4J 1.8.x phase, the developer of SLF4J directly released the 2.0.x version. Therefore, SLF4J 1.8.x is not discussed here.

2.2 Logback Version Compatibility

Due to changes to the SLF4J technology, matching logback-classic versions must be used to implement the SLF4J API. Otherwise, issues will occur. (For more information, see chapter 8 "FAQ".)

Logback Version SLF4J Version JDK Version Remarks
Logback 1.2.x SLF4J 1.7.x JDK 1.5 or later
Logback 1.3.x SLF4J 2.0.x JDK 8 or later
Logback 1.4.x SLF4J 2.0.x JDK 11 or later
Logback 1.5.x SLF4J 2.0.12 or later JDK 11 or later 1.5.x was released to replace 1.4.x.

Logback 1.3.x and 1.4.x listed in the preceding table are concurrently maintained. You need to choose the matching version (1.3.n or 1.4.n) based on the JDK version of your project. However, Logback has been fully updated to the 1.5.x series, and series 1.3.x and 1.4.x are no longer maintained[6]. For more information, see the version release document[7] on the official Logback website.

2.3 Summary

The version compatibility tables in sections 2.1 and 2.2 indicate that:

  • SLF4J 2.0 + Logback 1.3 is recommended for projects using JDK 8.
  • SLF4J 2.0 + Logback 1.5 is recommended for projects using JDK 11 or a later version.

However, the logging system used by Spring Boot applications[8] has additional requirements for SLF4J and Logback versions. We'll discuss this in the next chapter.

3. Adaption to Spring Boot

Spring Boot provides the spring-boot-starter-logging[9] dependency for projects to directly use Logback (and indirectly use SLF4J) for logging. After the dependency is added, the Spring Boot logging system uses org.springframework.boot.logging.LoggingSystem[10] to search for logs and automatically adapt them. This way, we do not need to worry about logging dependency issues in most cases when using Spring Boot. However, some Spring Boot versions may not support this dependency due to inconsistent implementations between SLF4J 2.0.x and SLF4J 1.7.x.

Spring Boot Version SLF4J Version Logback Version JDK Version
Spring Boot 1.5 SLF4J 1.7.x Logback 1.1.x JDK 7 or later
Spring Boot 2.x SLF4J 1.7.x Logback 1.2.x JDK 8 or later
Spring Boot 3.x SLF4J 2.0.x Logback 1.4.x JDK 17 or later

Based on the preceding table and the version compatibility correspondences summarized in chapter 2, the following conclusions can be drawn:

  • SLF4J 1.7.x + Logback 1.2.x is recommended for Spring Boot 2 or earlier.
  • SLF4J 2.0.x + Logback 1.4.x is recommended for Spring Boot 3. (Spring did not release an official adaptation solution for Logback 1.5.x when I wrote this article.)

If you use an earlier version of Spring Boot and want to use the latest SLF4J or Logback, you can refer to a related article posted on GitHub[11], where many developers comment their adaptation solutions[12]. However, I haven't verified these solutions yet. So, hope you find one that works.

4. Bridge Other Implementations

Adapters can leverage bridging to ensure proper log printing when the second- and third-party packages that your project depends on use JCL, Log4j, Log4j2, or JUL for logging.

  • org.slf4j:jcl-over-slf4j: Bridges JCL to SLF4J.
  • org.slf4j:log4j-over-slf4j: Bridges Log4j to SLF4J.
  • org.slf4j:jul-to-slf4j: Bridges JUL to SLF4J.
  • org.apache.logging.log4j:log4j-to-slf4j: Bridges Log4j 2 to SLF4J.

Note that the versions of all org.slf4j packages must be identical, which means that the versions of introduced bridge packages must match the selected slf4j-api version. Therefore, a Bill of Materials (BOM) file has been added to projects since SLF4J 2.0.8. This way, you do not need to separately maintain every version package. (For versions earlier than 2.0.8, you still need to ensure the version consistency on your own efforts.)

<dependencyManagement>
    <dependency>
        <groupId>org.slf4j</groupId>
        <artifactId>slf4j-bom</artifactId>
        <version>2.0.9</version>
        <type>pom</type>
    </dependency>
</dependencyManagement>

To my surprise, the log4j-to-slf4j package supports both SLF4J 1 and SLF4J 2.

5. Exclude Useless Dependencies

In fact, a bridge secretly forwards a call to another API by using the same package structure as the destination package. Therefore, if the bridge and the destination package are introduced at the same time, a package collision may occur.

As many tools inadvertently introduce logging facades or implementations, it is necessary to check the entire application and exclude useless facades or implementations, including JCL, Log4j, and Log4j 2.

  • To exclude JCL: commons-logging:commons-logging
  • To exclude Log4j: log4j:log4j
  • To exclude Log4j 2: org.apache.logging.log4j:log4j-core

What's more, other bridge packages that are indirectly introduced in your project may also cause package collisions and need to be excluded. You can determine whether a package is needed by referring to the package relationship diagram in chapter 3 "Summary" in the first article of the this series. The real project environment is complex. So, I will not enumerate these packages here.

5.1 Excluding a Package from Gradle Dependencies

Grade provides all*.exclude[13] to enable global package exclusion.

5.2 Exclude a Package from Maven Dependencies

Unfortunately, Maven has not provided the global package exclusion capability yet. (This issue[14] was proposed as early as in 2006, but the required capability is still not supported now.) It is not realistic to perform exclusion each time when a useless package is introduced. Based on my past experience, I figure out the following answers to the "Is there a way to exclude a Maven dependency globally?" question raised on Stack Overflow[15]:

  • Answer 1: Introduce an empty package (whose version number is special, such as 999-not-exist) to exclude the specified package. However, such an empty package usually do not exist in the Maven central repository, but it may be found in private repositories. You can build your own private repository and upload it, or directly use Version 99 Does Not Exist[16]. This is the optimal solution which applies to both local and remote environments.
  • Answer 2: Use <scope>provided</scope> to label the package to be excluded. In this case, the package will be skipped during compilation. However, the package will still be introduced in the local environment, resulting in inconsistency with the remote environment and thus hindering the debugging.
  • Answer 3: Use the maven-enforcer-plugin[17] plug-in to identify which packages are to be excluded. This plug-in is used only for verification, which means that you still need to exclude the incorrect packages from each dependency on your own.

6. Final Dependencies

After you perform the preceding steps, the dependencies are finalized. The final dependencies are of the latest versions that meet the requirements as of the date I finished this article (April 2024).

6.1 JDK 8/11 + Spring Boot 1.5/2

  • Basic packages

    • org.slf4j:slf4j-api:1.7.36
    • ch.qos.logback:logback-core:1.2.13
    • ch.qos.logback:logback-classic:1.2.13
  • Bridge packages

    • org.slf4j:jcl-over-slf4j:1.7.36
    • org.slf4j:log4j-over-slf4j:1.7.36
    • org.slf4j:jul-to-slf4j:1.7.36
    • org.apache.logging.log4j:log4j-to-slf4j:2.23.1
  • Exclusion packages

    • commons-logging:commons-logging:99.0-does-not-exist
    • log4j:log4j:99.0-does-not-exist
    • org.apache.logging.log4j:log4j-core:99.0-does-not-exist

6.2 JDK 17/21 + Spring Boot 3

  • Basic packages

    • org.slf4j:slf4j-bom:2.0.12 (The BOM file package is used to collectively manage dependencies.)
    • ch.qos.logback:logback-core:1.4.14
    • ch.qos.logback:logback-classic:1.4.14
  • Bridge packages

    • org.slf4j:jcl-over-slf4j (For more information, see chapter 7 "Usage notes".)
    • org.slf4j:log4j-over-slf4j (For more information, see chapter 7 "Usage notes".)
    • org.slf4j:jul-to-slf4j (For more information, see chapter 7 "Usage notes".)
    • org.apache.logging.log4j:log4j-to-slf4j:2.23.1
  • Exclusion packages

    • commons-logging:commons-logging:99.0-does-not-exist
    • log4j:log4j:99.0-does-not-exist
    • org.apache.logging.log4j:log4j-core:99.0-does-not-exist

7. Usage Notes

As what we said earlier, you need to perform the following two steps in project building:

  • Introduce the desired package and specify its version.
  • Exclude useless packages (by introducing empty packages).

The preceding two steps are usually performed in the <dependencyManagement> section of a parent POM, but this section is available only in the management package version and will not be loaded until the project is actually referenced.

In practice, we often use the following approach for project building:

  • Module A depends on the log4j:log4j package to print logs.
  • If we exclude the log4j:log4j package from a parent POM, an error will occur when module A uses Log4j.
  • To eliminate the error, we introduce log4j-over-slf4j in the parent POM to switch from Log4j to SLF4J.

This approach seems perfect, and the project can start as expected by following it. However, when module A needs to print logs, the error message "log4j:WARN No appenders could be found for logger (xxx.xxx.xxx)" is displayed, which means that log4j-over-slf4j is not really introduced in our project. (Nearly no second-party dependency allows such a package to be introduced unless it wants to be denounced.)

Actually, the solution is simple. That is, introduce log4j-over-slf4j through the <dependencies> section either of a parent POM or a child POM with actual dependencies.

8. FAQ

Normally, if you strictly follow the above-mentioned solutions and procedure, you may not encounter the following issues. However, difficulties may hit upon you in project maintenance accidentally.To help you find the causes and address these difficulties, I collect the errors that are frequently reported during logging.

When I wrote this chapter, I referred to a lot of useful information on the official websites of SLF4J and Logback, including:

  • SLF4J warning or error messages and their meanings [18]
  • Frequently Asked Questions about SLF4J [19]
  • Bridging legacy APIs [20]
  • Logback error messages and their meanings [21]
  • Frequently Asked Questions (Logback) [22]

Error 1: LoggerFactory is not a Logback LoggerContext but Logback is on the classpath. Either remove Logback or the competing implementation

A package collision occurs. You can directly exclude the unnecessary SLF4J adapter. The collision often happens between logback-classic and slf4j-log4j12. In this case, you just need to exclude the one that is not needed based on the logging framework (Logback or Log4j 2) in use.

To be specific, the reason is that Spring Boot obtains the available valid logging system by using LoggingSystem and supports SLF4J, Logback, Log4j 2, and JUL by default when it starts. (For more information, see chapter 3 "Adaption to Spring Boot".)

2
Implementation of LoggingSystem

Adapters of Logback and SLF4J (such as slf4j-log4j12) share the StaticLoggerBinder implementation. Therefore, this error is reported if the hit is not a Logback implementation when Spring Boot uses Logback.

Error 2: java.lang.NoClassDefFoundError: org/slf4j/impl/StaticLoggerBinder

The versions do not match each other. SLF4J has been loaded in SPI mode since the 2.0.x series (for more information, see section 2.1 "SLF4J version compatibility"). Therefore, when the versions of SLF4J and Logback (or Log4j 2) you introduced do not match each other, this error is reported.

Error3: java.lang.ClassNotFoundException: org.slf4j.impl.StaticLoggerBinder

Same answer as that to Error 2.

Error 4: java.lang.ClassCastException:org.apache.logging.slf4j.SLF4JLoggerContext cannot be cast to org.apache.logging.log4j.core.LoggerContext

A package collision occurs. It is highly probable that both Log4j 2 and a bridge that targets Log4j 2 are introduced. For the detailed reason, see chapter 5 "Exclude useless dependencies". However, I cannot help you determine the specific package exclusion solution because it depends on which logging system you want to use. Remember that you can retain only one package of the collided two.

Error 5: java.lang.ClassCastException:org.slf4j.impl.Log4jLoggerFactory cannot be cast to ch.qos.logback.classic.LoggerContext

A package collision occurs. It is highly probable that multiple SLF4J adapters (such as logback-classic and slf4j-log4j12) are introduced at the same time. The cause and solution to this issue are similar to those to Error 4.

Error 6: SLF4J: No SLF4J providers were found.

Only slf4j-api exists, and no adapter is available. This issue can be easily solved by adding logback-classic or slf4j-log4j12.

Error 7: SLF4J: Failed to load class "org.slf4j.impl.StaticLoggerBinder".

Only slf4j-api exists, and no adapter is available. This issue can be easily solved by adding logback-classic or slf4j-log4j12. However, I performed a test and found that the error was reported occasionally even though logback-classic existed, the project started properly, and the log output was normal. I haven't figured out the reason yet, and hope you guys could find me the answer.

Error 8: SLF4J: Class path contains multiple SLF4J bindings.

Multiple SLF4J adapters are found. Generally, the paths of all adapter packages are listed at the end of this error log. You only need to find the unnecessary packages and exclude them. SLF4J selects the first adapter by default. Therefore, if only this error is reported, the system may still properly start, print logs, and perform normally.

Error 9: log4j:WARN No appenders could be found for logger (xxx.xxx.xxx)

In most cases, this issue can be solved by introducing log4j-over-slf4j in your project. For specific reasons, see chapter 7 "Usage notes".

Error 10: java.lang.UnsupportedClassVersionError:ch/qos/logback/classic/spi/LogbackServiceProvider has been compiled by a more recent version of the Java Runtime (class file version 55.0), this version of the Java Runtime only recognizes class file versions up to 52.0

You are using JDK 8 (52.0), but the introduced Logback version is 1.3 or later, which only supports JDK 11 (55.0) or later. For more information, see section 2.2 "Logback version compatibility."

Error 11: Failed to load class org.slf4j.impl.StaticLoggerBinder

I have encountered this error during my project building, but everything is normal in my project and I haven't found the reason yet.

References

[1] https://juejin.cn/post/6915015034565951501
[2] https://logging.apache.org/log4j/2.x/manual/extending.html
[3] https://www.slf4j.org/faq.html#changesInVersion200
[4] https://docs.oracle.com/javase/8/docs/api/java/util/ServiceLoader.html
[5] https://github.com/qos-ch/slf4j/discussions/379
[6] https://logback.qos.ch/download.html
[7] https://logback.qos.ch/news.html
[8] https://docs.spring.io/spring-boot/docs/current/reference/html/features.html#features.logging
[9] https://mvnrepository.com/artifact/org.springframework.boot/spring-boot-starter-logging
[10] https://docs.spring.io/spring-boot/docs/current/api/org/springframework/boot/logging/LoggingSystem.html
[11] https://github.com/spring-projects/spring-boot/issues/12649
[12] https://github.com/spring-projects/spring-boot/issues/12649#issuecomment-1569448932
[13] https://stackoverflow.com/questions/55441430/what-does-this-all-exclude-means-in-gradle-transitive-dependency
[14] https://issues.apache.org/jira/browse/MNG-1977
[15] https://stackoverflow.com/questions/4716310/is-there-a-way-to-exclude-a-maven-dependency-globally
[16] https://github.com/erikvanoosten/version99
[17] https://maven.apache.org/enforcer/maven-enforcer-plugin/
[18] https://www.slf4j.org/codes.html
[19] https://www.slf4j.org/faq.html
[20] https://www.slf4j.org/legacy.html
[21] https://logback.qos.ch/codes.html
[22] https://logback.qos.ch/faq.html


Disclaimer: The views expressed herein are for reference only and don't necessarily represent the official views of Alibaba Cloud.

0 1 0
Share on

Alibaba Cloud Community

993 posts | 242 followers

You may also like

Comments