fix(android): handle SLF4J 2.x on the classpath#96
Open
tian000 wants to merge 4 commits intoBeTomorrow:masterfrom
Open
fix(android): handle SLF4J 2.x on the classpath#96tian000 wants to merge 4 commits intoBeTomorrow:masterfrom
tian000 wants to merge 4 commits intoBeTomorrow:masterfrom
Conversation
logback-android 2.0.0 registers with SLF4J using the 1.x binding mechanism (StaticLoggerBinder). When SLF4J 2.x is present — pulled in transitively by libraries like Ktor 3.x — it ignores the 1.x binding and falls back to NOPLoggerFactory. The hard cast to LoggerContext in configureLogger() then throws a ClassCastException, and because configure() had no try/catch the JS promise was never settled, hanging the calling app at startup. Changes: - Add getOrCreateLoggerContext() that falls back to a standalone LoggerContext when the SLF4J factory is not logback's - Store the context in a static field and use it consistently in renewAppender() and for the write() logger - Wrap configure() in try/catch so the promise is always resolved or rejected Fixes BeTomorrow#95
The Java code now handles both SLF4J binding mechanisms, so the Gradle dependency should accept whichever version the host app resolves to instead of pinning 1.7.33 and risking a downgrade conflict.
Runs the same 4 tests against real SLF4J 1.7.36 and 2.0.17 JARs to verify getOrCreateLoggerContext() works with both binding mechanisms. Two custom Gradle configurations (testSlf4j1x, testSlf4j2x) each hold a pinned SLF4J version. afterEvaluate registers test tasks that reuse the compiled test classes from testDebugUnitTest but swap the SLF4J JAR on the classpath. No mocking — the tests exercise the real SLF4J binding/provider discovery. Run with: ./gradlew testBothSlf4jVersions
Runs the dual-classpath SLF4J 1.x/2.x unit tests via the example app's Gradle project, where all Android dependencies (including React Native) resolve correctly. Usage: npm run test:android
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Fixes #95 —
FileLogger.configure()hangs indefinitely on Android when SLF4J 2.x is on the classpath.logback-android:2.0.0registers with SLF4J using the 1.x binding mechanism (StaticLoggerBinder). When a transitive dependency pulls inslf4j-api:2.x(e.g. Ktor 3.x →slf4j-api:2.0.17), Gradle resolves to the higher version. SLF4J 2.x ignores the 1.x binding and falls back toNOPLoggerFactory. The hard cast toLoggerContextinconfigureLogger()throws aClassCastException, and becauseconfigure()had no try/catch, the JS promise was never settled — hanging the app at startup.Changes
getOrCreateLoggerContext()— triesLoggerFactory.getILoggerFactory()first (SLF4J 1.x path); when it returnsNOPLoggerFactory(SLF4J 2.x), falls back to creating a standaloneLoggerContextso logback's file appender still worksfileLoggerContextfield — stores the context and uses it consistently inconfigureLogger(),renewAppender(), and for theloggerused bywrite()configure()— ensures the promise is always resolved or rejected, even on unexpected errorsHow it works
Under SLF4J 1.x with logback bound, behavior is unchanged —
getILoggerFactory()returns the realLoggerContext.Under SLF4J 2.x, we create a standalone
LoggerContextthat logback configures directly with the rolling file appender. The SLF4J facade logger (LoggerFactory.getLogger()) will still be a NOP, butwrite()uses the logger obtained from our stored context, so file logging works correctly.Verified with
logback-android:2.0.0+slf4j-api:2.0.17(from Ktor 3.3.1 transitive dep)NOPLoggerFactory cannot be cast to LoggerContexterror resolvedconfigure()promise resolves and file logging works