Skip to content

fix(android): handle SLF4J 2.x on the classpath#96

Open
tian000 wants to merge 4 commits intoBeTomorrow:masterfrom
tian000:fix/slf4j-2x-compat
Open

fix(android): handle SLF4J 2.x on the classpath#96
tian000 wants to merge 4 commits intoBeTomorrow:masterfrom
tian000:fix/slf4j-2x-compat

Conversation

@tian000
Copy link
Copy Markdown

@tian000 tian000 commented Mar 31, 2026

Summary

Fixes #95FileLogger.configure() hangs indefinitely on Android when SLF4J 2.x is on the classpath.

logback-android:2.0.0 registers with SLF4J using the 1.x binding mechanism (StaticLoggerBinder). When a transitive dependency pulls in slf4j-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 to NOPLoggerFactory. The hard cast to LoggerContext in configureLogger() throws a ClassCastException, and because configure() had no try/catch, the JS promise was never settled — hanging the app at startup.

Changes

  • getOrCreateLoggerContext() — tries LoggerFactory.getILoggerFactory() first (SLF4J 1.x path); when it returns NOPLoggerFactory (SLF4J 2.x), falls back to creating a standalone LoggerContext so logback's file appender still works
  • fileLoggerContext field — stores the context and uses it consistently in configureLogger(), renewAppender(), and for the logger used by write()
  • try/catch in configure() — ensures the promise is always resolved or rejected, even on unexpected errors

How it works

Under SLF4J 1.x with logback bound, behavior is unchanged — getILoggerFactory() returns the real LoggerContext.

Under SLF4J 2.x, we create a standalone LoggerContext that logback configures directly with the rolling file appender. The SLF4J facade logger (LoggerFactory.getLogger()) will still be a NOP, but write() uses the logger obtained from our stored context, so file logging works correctly.

Verified with

  • React Native 0.79.x (New Architecture / TurboModules enabled)
  • logback-android:2.0.0 + slf4j-api:2.0.17 (from Ktor 3.3.1 transitive dep)
  • Confirmed NOPLoggerFactory cannot be cast to LoggerContext error resolved
  • Confirmed configure() promise resolves and file logging works

tian000 added 4 commits March 30, 2026 23:21
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
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Android: FileLogger.configure() hangs when SLF4J 2.x is on the classpath

1 participant