From 1428ccd3b0d365a803faa2f245e1c4249d73f843 Mon Sep 17 00:00:00 2001 From: Maxwell Elliott Date: Wed, 1 Jul 2026 20:56:00 -0400 Subject: [PATCH] fix: silence SLF4J NOP-logger warning from JGit in serve command JGit (bazel-diff serve's in-process git engine) depends on slf4j-api but ships no binding, so SLF4J printed a "Failed to load class StaticLoggerBinder / Defaulting to no-operation (NOP) logger" warning to stderr on first use. Adds slf4j-nop as a runtime dep to supply the binding, silencing the warning while discarding JGit's internal logs. Co-Authored-By: Claude Sonnet 5 --- MODULE.bazel | 6 ++ cli/BUILD | 14 +++++ .../com/bazel_diff/log/Slf4jBindingTest.kt | 61 +++++++++++++++++++ maven_install.json | 19 +++++- 4 files changed, 98 insertions(+), 2 deletions(-) create mode 100644 cli/src/test/kotlin/com/bazel_diff/log/Slf4jBindingTest.kt diff --git a/MODULE.bazel b/MODULE.bazel index a1d9d712..8d1e3f78 100644 --- a/MODULE.bazel +++ b/MODULE.bazel @@ -61,6 +61,12 @@ maven.install( "org.apache.commons:commons-pool2:2.11.1", "org.jetbrains.kotlinx:kotlinx-coroutines-core:1.5.2", "org.mockito.kotlin:mockito-kotlin:5.4.0", + # SLF4J binding for JGit (serve command). JGit depends on slf4j-api but ships no binding, so + # without one SLF4J prints a "Failed to load class StaticLoggerBinder / Defaulting to NOP" + # warning to stderr on first use. slf4j-nop supplies the binding (silencing the warning) and + # discards JGit's internal logs, keeping the CLI's stderr clean. Version tracks the slf4j-api + # that JGit 5.13.x resolves (1.7.30). + "org.slf4j:slf4j-nop:1.7.30", ], fail_if_repin_required = True, lock_file = "//:maven_install.json", diff --git a/cli/BUILD b/cli/BUILD index ee73ab66..2ae5e5b1 100644 --- a/cli/BUILD +++ b/cli/BUILD @@ -41,6 +41,12 @@ kt_jvm_library( "@bazel_diff_maven//:org_eclipse_jgit_org_eclipse_jgit", "@bazel_diff_maven//:org_jetbrains_kotlinx_kotlinx_coroutines_core_jvm", ], + # SLF4J binding for JGit. Not referenced by bazel-diff code (JGit uses it internally), so it is a + # pure runtime dep: it supplies org.slf4j.impl.StaticLoggerBinder, silencing the "Defaulting to + # NOP logger" warning SLF4J prints to stderr when no binding is on the classpath. + runtime_deps = [ + "@bazel_diff_maven//:org_slf4j_slf4j_nop", + ], ) kt_jvm_test( @@ -169,6 +175,12 @@ kt_jvm_test( runtime_deps = [":cli-test-lib"], ) +kt_jvm_test( + name = "Slf4jBindingTest", + test_class = "com.bazel_diff.log.Slf4jBindingTest", + runtime_deps = [":cli-test-lib"], +) + kt_jvm_test( name = "BazelModServiceTest", test_class = "com.bazel_diff.bazel.BazelModServiceTest", @@ -273,6 +285,8 @@ kt_jvm_library( "@bazel_diff_maven//:io_insert_koin_koin_test_jvm", "@bazel_diff_maven//:junit_junit", "@bazel_diff_maven//:org_mockito_kotlin_mockito_kotlin", + # Compile-time API for Slf4jBindingTest; the runtime binding (slf4j-nop) comes via cli-lib. + "@bazel_diff_maven//:org_slf4j_slf4j_api", ], ) diff --git a/cli/src/test/kotlin/com/bazel_diff/log/Slf4jBindingTest.kt b/cli/src/test/kotlin/com/bazel_diff/log/Slf4jBindingTest.kt new file mode 100644 index 00000000..a66f8c78 --- /dev/null +++ b/cli/src/test/kotlin/com/bazel_diff/log/Slf4jBindingTest.kt @@ -0,0 +1,61 @@ +package com.bazel_diff.log + +import assertk.assertThat +import assertk.assertions.doesNotContain +import assertk.assertions.isNotNull +import java.io.ByteArrayOutputStream +import java.io.PrintStream +import org.junit.After +import org.junit.Before +import org.junit.Test +import org.slf4j.LoggerFactory + +/** + * Regression test for the SLF4J warning printed by `bazel-diff serve`: + * ``` + * SLF4J: Failed to load class "org.slf4j.impl.StaticLoggerBinder". + * SLF4J: Defaulting to no-operation (NOP) logger implementation + * ``` + * + * JGit (the serve command's in-process git engine) depends on `slf4j-api` but ships no binding. With + * no `org.slf4j.impl.StaticLoggerBinder` on the classpath, SLF4J falls back to a NOP logger and + * prints the warning above to stderr on first use. Shipping `slf4j-nop` as a runtime dep supplies the + * binding, silencing the warning while discarding JGit's internal logs (keeping the CLI stderr clean). + */ +class Slf4jBindingTest { + private val originalErr = System.err + private lateinit var captured: ByteArrayOutputStream + + @Before + fun setUp() { + captured = ByteArrayOutputStream() + System.setErr(PrintStream(captured)) + } + + @After + fun tearDown() { + System.setErr(originalErr) + } + + @Test + fun staticLoggerBinderIsOnTheClasspath() { + // This is the exact class SLF4J fails to load when no binding is present ("Failed to load class + // org.slf4j.impl.StaticLoggerBinder"). If it resolves, the NOP-fallback warning cannot be + // printed — a deterministic guard that survives SLF4J's once-per-JVM static init ordering. + val binder = Class.forName("org.slf4j.impl.StaticLoggerBinder") + assertThat(binder).isNotNull() + } + + @Test + fun obtainingAndUsingALoggerDoesNotPrintNopFallbackWarning() { + // Exercises the same path JGit takes: resolve a logger and emit a record. With a binding present + // this is silent; without one, SLF4J's static init prints the warning to stderr. + val logger = LoggerFactory.getLogger(Slf4jBindingTest::class.java) + assertThat(logger).isNotNull() + logger.info("bazel-diff slf4j binding smoke test") + + val err = captured.toString() + assertThat(err).doesNotContain("StaticLoggerBinder") + assertThat(err).doesNotContain("NOP logger") + } +} diff --git a/maven_install.json b/maven_install.json index 092a5de0..aab0bd2f 100644 --- a/maven_install.json +++ b/maven_install.json @@ -12,6 +12,7 @@ "org.eclipse.jgit:org.eclipse.jgit": -1935467463, "org.jetbrains.kotlinx:kotlinx-coroutines-core": -542524036, "org.mockito.kotlin:mockito-kotlin": 1836434344, + "org.slf4j:slf4j-nop": 1480452039, "repositories": -1949687017 }, "__RESOLVED_ARTIFACTS_HASH": { @@ -52,7 +53,8 @@ "org.mockito:mockito-core": -960687581, "org.objenesis:objenesis": 1798216877, "org.opentest4j:opentest4j": -1584531193, - "org.slf4j:slf4j-api": -801231047 + "org.slf4j:slf4j-api": -801231047, + "org.slf4j:slf4j-nop": -2006403554 }, "conflict_resolution": { "io.insert-koin:koin-core-jvm:3.1.6": "io.insert-koin:koin-core-jvm:4.0.0" @@ -285,6 +287,12 @@ "jar": "cdba07964d1bb40a0761485c6b1e8c2f8fd9eb1d19c53928ac0d7f9510105c57" }, "version": "1.7.30" + }, + "org.slf4j:slf4j-nop": { + "shasums": { + "jar": "2d550dcefaea23d223b72027dbc7cbdb7327676ccefdd9cfe49cf9ea8e9ac8e0" + }, + "version": "1.7.30" } }, "dependencies": { @@ -371,6 +379,9 @@ "net.bytebuddy:byte-buddy", "net.bytebuddy:byte-buddy-agent", "org.objenesis:objenesis" + ], + "org.slf4j:slf4j-nop": [ + "org.slf4j:slf4j-api" ] }, "packages": { @@ -887,6 +898,9 @@ "org.slf4j.event", "org.slf4j.helpers", "org.slf4j.spi" + ], + "org.slf4j:slf4j-nop": [ + "org.slf4j.impl" ] }, "repositories": { @@ -928,7 +942,8 @@ "org.mockito:mockito-core", "org.objenesis:objenesis", "org.opentest4j:opentest4j", - "org.slf4j:slf4j-api" + "org.slf4j:slf4j-api", + "org.slf4j:slf4j-nop" ] }, "services": {