Resolve multiple SLF4J bindings in maven project
Asked Answered
C

3

19

This is a question sounds like bunch of similar questions on SE sites, so I should be quite verbose to make my question clear. So, here is project's minimal pom.xml:

<dependencies>
     <dependency>
        <groupId>ch.qos.logback</groupId>
        <artifactId>logback-classic</artifactId>
        <version>1.0.6</version>
    </dependency>

   <dependency>
        <groupId>org.codehaus.gmaven.runtime</groupId>
        <artifactId>gmaven-runtime-1.7</artifactId>
        <version>1.3</version>
   </dependency>

</dependencies>
<build>
    <plugins>
        <plugin>
            <groupId>org.codehaus.mojo</groupId>
            <artifactId>exec-maven-plugin</artifactId>
            <version>1.2.1</version>
            <configuration>
                <mainClass>org.shabunc.App</mainClass>
            </configuration>
        </plugin>
    </plugins>
</build>

Here is the dependency tree produced by maven.

mvn dependency:tree -Dverbose -Dincludes=org.slf4j:

[INFO] [dependency:tree {execution: default-cli}]
[INFO] org.shabunc:logdebug:jar:1.0-SNAPSHOT
[INFO] \- ch.qos.logback:logback-classic:jar:1.0.6:compile
[INFO]    \- org.slf4j:slf4j-api:jar:1.6.5:compile

Now, let's remove exclusion and check dependencies again. We'll get:

 [INFO] org.shabunc:logdebug:jar:1.0-SNAPSHOT
[INFO] +- ch.qos.logback:logback-classic:jar:1.0.6:compile
[INFO] |  \- org.slf4j:slf4j-api:jar:1.6.5:compile
[INFO] \- org.codehaus.gmaven.runtime:gmaven-runtime-1.7:jar:1.3:compile
[INFO]    +- (org.slf4j:slf4j-api:jar:1.5.10:compile - omitted for conflict with 1.6.5)
[INFO]    +- org.codehaus.gmaven.feature:gmaven-feature-support:jar:1.3:compile
[INFO]    |  \- (org.slf4j:slf4j-api:jar:1.5.10:compile - omitted for conflict with 1.6.5)
[INFO]    \- org.codehaus.gmaven.runtime:gmaven-runtime-support:jar:1.3:compile
[INFO]       +- (org.slf4j:slf4j-api:jar:1.5.10:compile - omitted for conflict with 1.6.5)
[INFO]       \- org.sonatype.gshell:gshell-io:jar:2.0:compile
[INFO]          \- org.sonatype.gossip:gossip:jar:1.0:compile
[INFO]             \- (org.slf4j:slf4j-api:jar:1.5.8:compile - omitted for conflict with 1.6.5)

So, as we can see, everything works as expected, and conflicting dependency is actually get excluded. But the thing is that even with dependency excluded I still get following message while compiling and calling mvn exec:java:

SLF4J: Class path contains multiple SLF4J bindings.
SLF4J: Found binding in [jar:file:/home/shabunc/.m2/repository/ch/qos/logback/logback-classic/1.0.6/logback-classic-1.0.6.jar!/org/slf4j/impl/StaticLoggerBinder.class]
SLF4J: Found binding in [jar:file:/home/shabunc/.m2/repository/org/sonatype/gossip/gossip/1.0/gossip-1.0.jar!/org/slf4j/impl/StaticLoggerBinder.class]

The question is: Why I still see this warning and what exactly should be done to make only one version of slf4j reachable during execution?

Collyer answered 1/8, 2012 at 11:56 Comment(9)
No offense intended but declaring a dependency on slf4j-api and then excluding slf4j-api is wrong. The following statement is also incorrect. "let us make sure that slf4j-api is indeed loaded only once, just as we expected when we've added exclusions/exclusion part to gmaven-runtime dependency." When dependencioes declare different versions of an artifact, e.g. slf4j-api, Maven will not place slf4j-api multiple times on the class path. Even if Maven did (which it doesn't), slf4j-api would not be loaded multiple times. Please edit your question as not to mislead future readers.Jabon
Ceki, it will be OK even if it will be offense that will teach me something ))) The fact is slf4j-api is loaded multiple time thus creating conflicts when I'm trying to deploy as war to tomcat. One slf4j is in logblack and one in the dependency of the dependency of gmaven-runtime. If I am missing something, it will be very kind of you if you'll make it clearer, since all this dependency resolving is not the thing I am an expert in.Collyer
If you are using Maven there is no risk that different versions of slf4j-api being present on your class path. However, it is common for different slf4j bindings, e.g. slf4j-jdj14.jar, slf4j-log4j.jar or logback-classic.jar to be present simultaneously on the class path. In your case, you have logback-classic-1.0.6.jar gossip-1.0.jar present. Tom Anderson has already provided a good answer for excluding 'gossip'.Jabon
Sorry, may be I just don't get you right, but it looks like that this is exactly how you claim it can not be: there are different SLF4J bindings - SLF4J: See slf4j.org/codes.html#multiple_bindings for an explanation. SLF4J: slf4j-api 1.6.x (or later) is incompatible with this binding. SLF4J: Your binding is version 1.5.5 or earlier. SLF4J: Upgrade your binding to version 1.6.x. or 2.0.xCollyer
What is possible and common: slf4j-api and its binding having different versions. Example: slf4j-api-1.5.jar and slf4j-simple-1.6.jar being present on the class path. What is uncommon and automatically prevented by Maven: two different versions of slf4j-api present on the classpath. Even if two slf4j-api.jar files with different versions are present, only one will be loaded by the JVM.Jabon
there are exactly two slf4j-api.jar loaded, and I've finally got your point, sure only one will be loaded by JVM. It just that in my case it tries to load the wrong one) @Ceki, what kind of edit will be appropriate - "let us make sure that Class path does not contains multiple SLF4J bindings" - is this a valid statement?Collyer
And anyway, @Ceki, thank a lot both for logback and SLF4J projects, they indeed deliver a major improvement to a coder's user experience.Collyer
Have you noticed that the output produced by "mvn dependency:tree" contains lines like "org.slf4j:slf4j-api:jar:1.5.10:compile - omitted for conflict with 1.6.5" ? When Maven detects that conflicting (read distinct) versions of an artifact are pulled-in it will retain the version declared nearest to the current project's pom. The other declaration for the artifact with other versions are simply ignored (=omitted). Thus, instead of excluding slf4j-api in "gmaven-runtime" (which is really awful) you should explicitly declare a dependency on slf4j-api in your project's pom.Jabon
I edited the question removing misleading clutter but retaining the gist of the questionJabon
S
23

Your problem isn't getting two copies of the SLF4J API, it's getting two different SLF4J implementations. You need to exclude Gossip, not the API. That means something like:

<dependency>
    <groupId>org.codehaus.gmaven.runtime</groupId>
    <artifactId>gmaven-runtime-1.7</artifactId>
    <version>1.3</version>
    <exclusions>
      <exclusion>
        <groupId>org.sonatype.gossip</groupId>
        <artifactId>gossip</artifactId>
      </exclusion>
    </exclusions>
</dependency>

The Gossip dependency is declared by gshell-io; hopefully, it doesn't actually need Gossip, it just needs an SLF4J SLF4J, which you are supplying in the shape of Logback.

Scandium answered 1/8, 2012 at 12:33 Comment(9)
so, you mean I can exclude gossip dependency for free, without any dramatic consequences? ))) Well, and if there are even more dependencies which, in turn, depend on slf4j, it will quickly become, well, a nightmare (((Collyer
The only way to make SLF4J happy is to either remove your dependency on Logback, or exclude gshell-io's dependency on Gossip. However, i can't promise that this won't have dramatic consequences. I would hope it doesn't - it would be very poor for a library to have a direct dependency on a logging implementation.Scandium
The issue is because of 2 different providers... gossip jar has a slf4j implementation within it.. so you either should remove logback or gossip from your dependency tree..Fecit
The way you can check if gshell-io has a real Gossip dependency is to download the jar (or the source), and search for references to types from Gossip. If there are none, you are safe. If there are some, you might still be safe it the code was carefully written.Scandium
However, i have just had a look, and it seems that it was not carefully written. There are direct dependencies - see Closer.log, StreamJack.log and Flusher.log. This is really bad. Whoever wrote that code should go to jail.Scandium
@TomAnderson, I guess this is the answer, thank you very much. Yet I have one last question. You've said that "it would be very poor for a library to have direct dependency on a logging". Does this mean that it's almost always to wrap logging dependency, thus creating transitive dependency?Collyer
@shabunc: What i mean is that the code can have a direct dependency on a logging API (in this case, SLF4J), but should not depend on a specific implementation. That way, it's up to the user of the code (in this case, you) to choose the right implementation. It's even okay for a library to specify a runtime dependency on a particular implementation, to supply a default, but it should tolerate that dependency being excluded and replaced. It's like when you use JDBC - you depend on the JDBC API, but not on classes from a particular driver.Scandium
@TomAnderson, thanks again. It's a pity I can not upvote twice :)Collyer
PS to Jason Dillon in case he ever reads this comment thread: i don't actually think you should go to jail. That's just a figure of speech hyperbolically expressing the opinion that improvements could be made to the code in question. I think community service or a fine would be sufficient.Scandium
F
4

All you need to do is add something like this

compile "org.sonatype.gossip:gossip:1.0" {
    exclude module:'slf4j-jcl'
    exclude module:'slf4j-log4j12'
}
Fecit answered 1/8, 2012 at 12:21 Comment(4)
The issue is caused due to transitive dependency of gossip jar used. The above code will exclude the dependency of sl4j implementation from gossip.Fecit
"Something like this" here means "the Maven equivalent of this Gradle code"!Scandium
@TomAnderson ya...sorry i didnt mention that :)Fecit
@Fecit can you, please, provide a maven equivalent, since I still don't get what do you exactly mean. If you mean I should add this exclusions as well, this won't work - as I've already tell in the question, dependency:tree already does not consider gossip/slf4j dependency to be a part of the project.Collyer
K
2

I guess you need to play with scope of dependencies, see: http://www.mojohaus.org/exec-maven-plugin/java-mojo.html

classpathScope - defines the scope of the classpath passed to the plugin. Set to compile,test,runtime or system depending on your needs.

Kruller answered 1/8, 2012 at 12:9 Comment(1)
can you, please, be more specific, I still don't get how setting classpathScope will help resolve this very problem.Collyer

© 2022 - 2024 — McMap. All rights reserved.