Search code examples
spring-bootgradlelogbackslf4j

Spring boot 3 logging dependency conflict


We're in the process of upgrading to spring boot 3 in our application at the moment and we're getting some issues at runtime with logger factories clashing.

We use logback for our logging implementation, but via slf4j.

We're pulling in the spring-boot-starter-logging module, and not specifying our own versions of slf4j or logback in our build.gradle.

It's a really complicated project with lots of dependencies, but the logging dependencies are all here:

integrationTestRuntimeClasspath - Runtime classpath of source set 'integration test'.
+--- net.logstash.logback:logstash-logback-encoder:6.6
|    \--- com.fasterxml.jackson.core:jackson-databind:2.12.0 -> 2.14.2
|         +--- com.fasterxml.jackson.core:jackson-annotations:2.14.2
|         |    \--- com.fasterxml.jackson:jackson-bom:2.14.2
|         |         +--- com.fasterxml.jackson.core:jackson-annotations:2.14.2 (c)
|         |         +--- com.fasterxml.jackson.core:jackson-core:2.14.2 (c)
|         |         +--- com.fasterxml.jackson.core:jackson-databind:2.14.2 (c)
|         |         +--- com.fasterxml.jackson.datatype:jackson-datatype-guava:2.14.2 (c)
|         |         +--- com.fasterxml.jackson.datatype:jackson-datatype-jdk8:2.14.2 (c)
|         |         +--- com.fasterxml.jackson.module:jackson-module-parameter-names:2.14.2 (c)
|         |         +--- com.fasterxml.jackson.dataformat:jackson-dataformat-cbor:2.14.2 (c)
|         |         +--- com.fasterxml.jackson.datatype:jackson-datatype-jsr310:2.14.2 (c)
|         |         \--- com.fasterxml.jackson.datatype:jackson-datatype-joda:2.14.2 (c)
|         +--- com.fasterxml.jackson.core:jackson-core:2.14.2
|         |    \--- com.fasterxml.jackson:jackson-bom:2.14.2 (*)
|         \--- com.fasterxml.jackson:jackson-bom:2.14.2 (*)
+--- org.springframework.boot:spring-boot-starter-logging -> 3.0.5
|    \--- ch.qos.logback:logback-classic:1.4.6
|         +--- ch.qos.logback:logback-core:1.4.6
|         \--- org.slf4j:slf4j-api:2.0.4 -> 2.0.7
+--- org.springframework:spring-context -> 6.0.7
|    +--- org.springframework:spring-aop:6.0.7
|    |    +--- org.springframework:spring-beans:6.0.7
|    |    |    \--- org.springframework:spring-core:6.0.7
|    |    |         \--- org.springframework:spring-jcl:6.0.7
|    |    \--- org.springframework:spring-core:6.0.7 (*)
|    +--- org.springframework:spring-beans:6.0.7 (*)
|    +--- org.springframework:spring-core:6.0.7 (*)
|    \--- org.springframework:spring-expression:6.0.7
|         \--- org.springframework:spring-core:6.0.7 (*)
+--- org.springframework:spring-beans -> 6.0.7 (*)
+--- com.fasterxml.jackson.core:jackson-databind:{strictly 2.14.2} -> 2.14.2 (*)
+--- com.fasterxml.jackson.core:jackson-annotations:{strictly 2.14.2} -> 2.14.2 (*)
+--- org.apache.tomcat.embed:tomcat-embed-core:9.0.69
|    \--- org.apache.tomcat:tomcat-annotations-api:9.0.69 -> 10.1.7
+--- commons-logging:commons-logging:1.1.3 -> 1.2

We apply the following exclusions and dependencies to all modules in the multimodule build:

    configurations.all {
        // Short version cache so we don't accidentally use old versions
        resolutionStrategy.cacheDynamicVersionsFor 10, 'minutes'
        exclude group: 'org.apache.logging.log4j', module: 'log4j-to-slf4j'
        exclude group: 'org.apache.logging.log4j', module: 'log4j-api'
        exclude group: 'org.slf4j', module: 'jul-to-slf4j'
        exclude group: 'org.slf4j', module: 'slf4j-log4j'
        exclude group: 'org.slf4j', module: 'slf4j-log4j12'
    }

    dependencies {
        implementation 'net.logstash.logback:logstash-logback-encoder:6.6'
        implementation 'org.springframework.boot:spring-boot-starter-logging'

Running my integration tests gives me:

Caused by: java.lang.IllegalArgumentException: LoggerFactory is not a Logback LoggerContext but Logback is on the classpath. Either remove Logback or the competing implementation (class org.slf4j.helpers.SubstituteLoggerFactory loaded from file: .../.gradle/caches/modules-2/files-2.1/org.slf4j/slf4j-api/2.0.7/41eb7184ea9d556f23e18b5cb99cad1f8581fc00/slf4j-api-2.0.7.jar). If you are using WebLogic you will need to add 'org.slf4j' to prefer-application-packages in WEB-INF/weblogic.xml: org.slf4j.helpers.SubstituteLoggerFactory

Strangely, this is only effecting some of the integration tests. Others are running as expected.

I'm at a bit of a loss here. I can't exclude logback or slf4j, as both of them are required. Any help would be appreciated.

EDIT I've done some debugging this morning and what seems to be happening is that whilst the org.slf4j.LoggerFactory is being initialised, it returns the substitute logger.


Solution

  • We've got to the bottom of our issue here. Here's a summary of what was going on:

    • In the LoggerFactory, we could see that SLF4J was finding Logback as the provider, and started initialising it.
    • That initialisation took up to a second in some cases.
    • The reason the initialisation took a while was because it was using our main application logback.xml and apparently initialising the logstash appender is a fairly slow process.

    The root cause of the problem seems to be that whilst slf4j is initialising, any requests to the LoggerFactory#getProvider method return the substitute provider, at which point the LogbackLoggingSystem#getLoggerContext in spring boot throws an exception, as the LoggerFactory returned is not valid.

    I'm going to raise a bug against slf4j for this behaviour. It feels to me that if it's initialising, it shouldn't just return a dummy implementation.