Skip to content

Instantly share code, notes, and snippets.

@betrcode
Last active December 28, 2023 10:48
Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save betrcode/501a98690a3d9e9c7f87568e2f85f718 to your computer and use it in GitHub Desktop.
Save betrcode/501a98690a3d9e9c7f87568e2f85f718 to your computer and use it in GitHub Desktop.
How to get rid of duplicate logging implementations on classpath (slf4j, log4j12, logback)

How to find and remove duplicate logging implementations on classpath

Problem

If you have multiple logging implementations on the classpath, one will be picked in a non-deterministic ("random") way. You may see logging like this.

15:04:56.264 [ERROR] [system.err] SLF4J: Class path contains multiple SLF4J bindings.
15:04:56.265 [ERROR] [system.err] SLF4J: Found binding in [jar:file:/home/max/.gradle/caches/modules-2/files-2.1/org.slf4j/slf4j-log4j12/1.7.6/6953717b9850aeb26d1b8375ca07dbd9c50eca4e/slf4j-log4j12-1.7.6.jar!/org/slf4j/impl/StaticLoggerBinder.class]
15:04:56.265 [ERROR] [system.err] SLF4J: Found binding in [jar:file:/home/max/.gradle/caches/modules-2/files-2.1/ch.qos.logback/logback-classic/1.2.3/7c4f3c474fb2c041d8028740440937705ebb473a/logback-classic-1.2.3.jar!/org/slf4j/impl/StaticLoggerBinder.class]
15:04:56.265 [ERROR] [system.err] SLF4J: See http://www.slf4j.org/codes.html#multiple_bindings for an explanation.
15:04:56.276 [ERROR] [system.err] SLF4J: Actual binding is of type [org.slf4j.impl.Log4jLoggerFactory]
15:04:56.286 [ERROR] [system.err] Exception in thread "main" java.lang.StackOverflowError
15:04:56.288 [ERROR] [system.err]       at java.util.concurrent.ConcurrentHashMap.get(ConcurrentHashMap.java:936)
15:04:56.288 [ERROR] [system.err]       at org.slf4j.impl.Log4jLoggerFactory.getLogger(Log4jLoggerFactory.java:58)
15:04:56.288 [ERROR] [system.err]       at org.slf4j.LoggerFactory.getLogger(LoggerFactory.java:358)
15:04:56.288 [ERROR] [system.err]       at org.apache.log4j.Category.<init>(Category.java:57)
15:04:56.288 [ERROR] [system.err]       at org.apache.log4j.Logger.<init>(Logger.java:37)
15:04:56.288 [ERROR] [system.err]       at org.apache.log4j.Log4jLoggerFactory.getLogger(Log4jLoggerFactory.java:43)
15:04:56.288 [ERROR] [system.err]       at org.apache.log4j.LogManager.getLogger(LogManager.java:45)
15:04:56.288 [ERROR] [system.err]       at org.slf4j.impl.Log4jLoggerFactory.getLogger(Log4jLoggerFactory.java:66)

This document describes how you can fix a problem like this.

Sync all the JARs

./gradlew clean syncRuntimeDependencies

This will put all the JARs into ./build/deps/runtime/ and sibling folders for other configurations (like "test").

Grep the duplicate

grep -l StaticLoggerBinder ./build/deps/runtime/*.jar

Example output:

./build/deps/runtime/logback-classic-1.2.3.jar
./build/deps/runtime/slf4j-log4j12-1.7.6.jar

So, we have two! That's the problem. We want there to be only one!

Filter it out

Decide which one you want to keep and filter the other one out. Put this in your build.gradle:

configurations.all {
    // We don't want to pull in this, ever. Dropwizard uses ch.qos.logback:logback-core
    exclude group: 'org.slf4j', module: 'slf4j-log4j12'
}

Verify

Now clean, sync and do the grep again. You should have only one logging implementation.

Example output:

./build/deps/runtime/logback-classic-1.2.3.jar

PROFIT.

@ollehallin
Copy link

The task syncRuntimeDependencies is not standard Gradle. It is defined by the following code (in build.gradle):
I usually add it to all my projects, as trouble-shooting tool for class path related problems.

configurations.forEach { it ->
    def config = it
    def configName = config.name
    def taskName = "sync${configName.capitalize()}Dependencies"

    task "$taskName"(type: Sync) {
        group = "Troubleshooting"
        description = "Syncs $configName dependencies into build/deps/$configName"

        from config
        into "$buildDir/deps/$configName"
    }
}

@betrcode
Copy link
Author

@ollehallin: Perfect! Thank you.

@missstaff
Copy link

This works, thank you!

@Ayfri
Copy link

Ayfri commented Dec 28, 2023

I'm getting

> Cannot access a file in the destination directory. Syncing to a directory which contains unreadable content is not supported. Use a Copy task with Task.doNotTrackState() instead. For more information, please refer to https://docs.gradle.org/8.5/userguide/incremental_build.html#disable-state-tracking in the Gradle documentation.
   > Could not stat file C:\Users\Pierre\IdeaProjects\bots\doctor-java\property(org.gradle.api.file.Directory, fixed(class org.gradle.api.internal.file.DefaultFilePropertyFactory$FixedDirectory, C:\Users\MyUser\IdeaProjects\my-project\build))\deps\runtimeClasspath

when trying to run the sync task on Windows 11.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment