Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
17 commits
Select commit Hold shift + click to select a range
dc9451d
ADFA-4646: Add Logback-free SLF4J provider scaffold to logger module
davidschachterADFA Jul 21, 2026
05cd8a8
ADFA-4646: Register the new SLF4J provider, delete Logback wiring in …
davidschachterADFA Jul 21, 2026
ec9f065
ADFA-4646: Migrate tooling-api-impl off Logback
davidschachterADFA Jul 21, 2026
c167fb9
ADFA-4646: Delete testing/common's Logback configurator
davidschachterADFA Jul 21, 2026
13ede54
ADFA-4646: Replace Sentry's Logback bridge with a direct breadcrumb sink
davidschachterADFA Jul 21, 2026
1e0936e
ADFA-4646: Apply Spotless formatting
davidschachterADFA Jul 21, 2026
3124d20
ADFA-4646: Fix code-review findings in the new logging provider
davidschachterADFA Jul 21, 2026
a53a19b
ADFA-4646: Address remaining code-review findings
davidschachterADFA Jul 22, 2026
e2125c1
ADFA-4646: Add unit tests for the new logging provider
davidschachterADFA Jul 22, 2026
8462270
Merge branch 'stage' into fix/ADFA-4646-remove-logback
davidschachterADFA Jul 22, 2026
87a43b1
ADFA-4646: Fix a false-negative gap in the concurrency test
davidschachterADFA Jul 22, 2026
9be05cc
ADFA-4646: Replace reflection in ToolingApiAppenderTest with a real seam
davidschachterADFA Jul 22, 2026
ebdadcd
ADFA-4646: Apply Spotless formatting
davidschachterADFA Jul 22, 2026
be8de65
Merge remote-tracking branch 'origin/fix/ADFA-4646-remove-logback' in…
davidschachterADFA Jul 22, 2026
d2dee5e
Merge remote-tracking branch 'origin/stage' into fix/ADFA-4646-remove…
davidschachterADFA Jul 23, 2026
62cb4ab
Merge branch 'stage' into fix/ADFA-4646-remove-logback
davidschachterADFA Jul 23, 2026
7b3c786
Merge branch 'stage' into fix/ADFA-4646-remove-logback
davidschachterADFA Jul 23, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
18 changes: 1 addition & 17 deletions app/build.gradle.kts
Original file line number Diff line number Diff line change
@@ -1,8 +1,6 @@
@file:Suppress("UnstableApiUsage")

import ch.qos.logback.core.util.EnvUtil
import com.itsaky.androidide.build.config.BuildConfig
import com.itsaky.androidide.desugaring.ch.qos.logback.core.util.DesugarEnvUtil
import com.itsaky.androidide.desugaring.utils.JavaIOReplacements.applyJavaIOReplacements
import com.itsaky.androidide.plugins.AndroidIDEAssetsPlugin
import org.json.JSONObject
Expand All @@ -24,7 +22,6 @@ import java.util.zip.Deflater
import java.util.zip.ZipEntry
import java.util.zip.ZipFile
import java.util.zip.ZipOutputStream
import kotlin.reflect.jvm.javaMethod

plugins {
id("com.android.application")
Expand Down Expand Up @@ -57,7 +54,6 @@ apply {

buildscript {
dependencies {
classpath(libs.logging.logback.core)
classpath(libs.composite.desugaringCore)
classpath(libs.org.json)
}
Expand Down Expand Up @@ -160,20 +156,9 @@ kapt { arguments { arg("eventBusIndex", "${BuildConfig.PACKAGE_NAME}.events.AppE

desugaring {
replacements {
includePackage(
"org.eclipse.jgit",
"ch.qos.logback.classic.util",
)
includePackage("org.eclipse.jgit")

applyJavaIOReplacements()

// EnvUtil.logbackVersion() uses newer Java APIs like Class.getModule() which is not available
// on Android. We replace the method usage with DesugarEnvUtil.logbackVersion() which
// always returns null
replaceMethod(
EnvUtil::logbackVersion.javaMethod!!,
DesugarEnvUtil::logbackVersion.javaMethod!!,
)
}
}

Expand Down Expand Up @@ -330,7 +315,6 @@ dependencies {
// Sentry Android SDK (core + replay for quality configuration); our GlitchTip client.
implementation(libs.sentry.core)
implementation(libs.sentry.android.core)
implementation(libs.sentry.logback)

// Firebase Analytics
implementation(platform(libs.firebase.bom))
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,6 @@ import android.provider.Settings
import androidx.lifecycle.DefaultLifecycleObserver
import androidx.lifecycle.LifecycleOwner
import androidx.lifecycle.ProcessLifecycleOwner
import ch.qos.logback.classic.Level
import ch.qos.logback.classic.Logger
import ch.qos.logback.classic.LoggerContext
import com.itsaky.androidide.BuildConfig
import com.itsaky.androidide.analytics.IAnalyticsManager
import com.itsaky.androidide.app.strictmode.StrictModeConfig
Expand All @@ -19,15 +16,17 @@ import com.itsaky.androidide.events.LspJavaEventsIndex
import com.itsaky.androidide.events.ProjectsApiEventsIndex
import com.itsaky.androidide.handlers.CrashEventSubscriber
import com.itsaky.androidide.handlers.GlitchTipDiagnosticsContext
import com.itsaky.androidide.logging.provider.IdeLogRouter
import com.itsaky.androidide.syntax.colorschemes.SchemeAndroidIDE
import com.itsaky.androidide.ui.themes.IThemeManager
import com.itsaky.androidide.utils.Environment
import com.itsaky.androidide.utils.FeatureFlags
import com.termux.shared.reflection.ReflectionUtils
import io.github.rosemoe.sora.widget.schemes.EditorColorScheme
import io.sentry.Breadcrumb
import io.sentry.Sentry
import io.sentry.SentryLevel
import io.sentry.android.core.SentryAndroid
import io.sentry.logback.SentryAppender
import io.sentry.protocol.User
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
Expand All @@ -37,6 +36,7 @@ import org.greenrobot.eventbus.EventBus
import org.koin.core.component.KoinComponent
import org.koin.core.component.inject
import org.slf4j.LoggerFactory
import org.slf4j.event.Level
import kotlin.system.exitProcess

/**
Expand Down Expand Up @@ -84,16 +84,28 @@ internal object DeviceProtectedApplicationLoader :
GlitchTipDiagnosticsContext.install(options)
}

val loggerContext = LoggerFactory.getILoggerFactory() as LoggerContext
val glitchTipLogAppender =
SentryAppender().apply {
context = loggerContext
setMinimumEventLevel(Level.OFF)
setMinimumBreadcrumbLevel(Level.INFO)
setMinimumLevel(Level.WARN)
start()
// Forward INFO+ logs to GlitchTip as breadcrumbs (never as events; crash events are
// only ever sent via explicit Sentry.captureException calls elsewhere). INFO matches
// the old SentryAppender's setMinimumBreadcrumbLevel(Level.INFO) - that threshold is
// independent of setMinimumLevel(Level.WARN), which only gated the separate,
// unused "Sentry Logs" feature.
IdeLogRouter.addSink { level, loggerName, message, _ ->
if (level.toInt() >= Level.INFO.toInt()) {
Sentry.addBreadcrumb(
Breadcrumb().apply {
category = loggerName
this.message = message
this.level =
when (level) {
Level.ERROR -> SentryLevel.ERROR
Level.WARN -> SentryLevel.WARNING
Level.INFO -> SentryLevel.INFO
else -> SentryLevel.DEBUG
}
},
)
}
loggerContext.getLogger(Logger.ROOT_LOGGER_NAME).addAppender(glitchTipLogAppender)
}

Sentry.setUser(
User().apply {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,8 +17,7 @@

package com.itsaky.androidide.services.builder

import ch.qos.logback.core.CoreConstants
import com.itsaky.androidide.logging.JvmStdErrAppender
import com.itsaky.androidide.logging.provider.IdeLogRouter
import com.itsaky.androidide.tasks.cancelIfActive
import com.itsaky.androidide.tasks.ifCancelledOrInterrupted
import com.itsaky.androidide.tooling.api.IToolingApiClient
Expand Down Expand Up @@ -86,13 +85,14 @@ internal class ToolingServerRunner(
* stale entries here (for example Huawei/Honor UniPerf classes) that can make
* native ART registration abort the JVM before the server starts.
*/
private val ANDROID_RUNTIME_ENV_KEYS = setOf(
"BOOTCLASSPATH",
"DEX2OATBOOTCLASSPATH",
"SYSTEMSERVERCLASSPATH",
"STANDALONE_SYSTEMSERVER_JARS",
"CLASSPATH",
)
private val ANDROID_RUNTIME_ENV_KEYS =
setOf(
"BOOTCLASSPATH",
"DEX2OATBOOTCLASSPATH",
"SYSTEMSERVERCLASSPATH",
"STANDALONE_SYSTEMSERVER_JARS",
"CLASSPATH",
)
}

fun setListener(listener: OnServerStartListener?) {
Expand Down Expand Up @@ -128,10 +128,9 @@ internal class ToolingServerRunner(
"java.base/java.util=ALL-UNNAMED",
"--add-opens",
"java.base/java.io=ALL-UNNAMED", // The JAR file to run
"-D${CoreConstants.STATUS_LISTENER_CLASS_KEY}=com.itsaky.androidide.tooling.impl.util.LogbackStatusListener",
"-D${ToolingProps.DAEMON_FORCE_KILL}=${TOOLING_DAEMON_KILL_ENABLED}",
"-D${ToolingProps.DESCENDANT_FORCE_KILL_TIMEOUT_MS}=${TOOLING_DAEMON_KILL_TIMEOUT.inWholeMilliseconds}",
"-D${JvmStdErrAppender.PROP_JVM_STDERR_APPENDER_ENABLED}=${TOOLING_ERR_STREAM_LOGGING_ENABLED}",
"-D${IdeLogRouter.PROP_JVM_STDERR_ENABLED}=${TOOLING_ERR_STREAM_LOGGING_ENABLED}",
"-jar",
Environment.TOOLING_API_JAR.absolutePath,
)
Expand Down
Original file line number Diff line number Diff line change
@@ -1,26 +1,26 @@
package com.itsaky.androidide.viewmodel

import ch.qos.logback.classic.Level
import com.itsaky.androidide.logging.GlobalBufferAppender
import com.itsaky.androidide.logging.provider.IdeGlobalLogBuffer
import com.itsaky.androidide.utils.FeatureFlags
import com.itsaky.androidide.utils.ILogger
import org.slf4j.event.Level

/**
* Consumes IDE logs from [GlobalBufferAppender]. The ViewModel (not the fragment) is the
* consumer so that the appender's buffer replay happens exactly once per ViewModel lifetime --
* Consumes IDE logs from [IdeGlobalLogBuffer]. The ViewModel (not the fragment) is the
* consumer so that the buffer's replay happens exactly once per ViewModel lifetime --
* registering on every fragment view recreation would re-submit the replayed lines into the
* retained log history and duplicate them.
*
* @author Akash Yadav
*/
class IDELogsViewModel :
LogViewModel(),
GlobalBufferAppender.Consumer {
IdeGlobalLogBuffer.Consumer {
override val logLevel: Level
get() = if (FeatureFlags.isDebugLoggingEnabled) Level.DEBUG else Level.INFO

init {
GlobalBufferAppender.registerConsumer(this)
IdeGlobalLogBuffer.registerConsumer(this)
}

override fun consume(
Expand All @@ -29,16 +29,16 @@ class IDELogsViewModel :
) = submit(level.toILoggerLevel(), message)

override fun onCleared() {
GlobalBufferAppender.unregisterConsumer(this)
IdeGlobalLogBuffer.unregisterConsumer(this)
super.onCleared()
}
}

private fun Level.toILoggerLevel(): ILogger.Level =
when {
levelInt <= Level.TRACE_INT -> ILogger.Level.VERBOSE
levelInt <= Level.DEBUG_INT -> ILogger.Level.DEBUG
levelInt <= Level.INFO_INT -> ILogger.Level.INFO
levelInt <= Level.WARN_INT -> ILogger.Level.WARNING
else -> ILogger.Level.ERROR
when (this) {
Level.ERROR -> ILogger.Level.ERROR
Level.WARN -> ILogger.Level.WARNING
Level.INFO -> ILogger.Level.INFO
Level.DEBUG -> ILogger.Level.DEBUG
Level.TRACE -> ILogger.Level.VERBOSE
}
5 changes: 0 additions & 5 deletions gradle/libs.versions.toml
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,6 @@ androidx-espresso = "3.5.1"
retrofit = "2.11.0"
markwon = "4.6.2"
maven-publish-plugin = "0.27.0"
logback = "1.5.3"
room = "2.8.4"
utilcodex = "1.31.1"
viewpager2 = "1.1.0-beta02"
Expand Down Expand Up @@ -150,7 +149,6 @@ firebase-analytics = { module = "com.google.firebase:firebase-analytics-ktx" }
# Aliases stay sentry-* to match the io.sentry: coordinates below.
sentry-core = { module = "io.sentry:sentry", version.ref = "sentry" }
sentry-android-core = { module = "io.sentry:sentry-android-core", version.ref = "sentry" }
sentry-logback = { module = "io.sentry:sentry-logback", version.ref = "sentry" }

# AndroidIDE
androidide-ts = { module = "com.itsaky.androidide.treesitter:android-tree-sitter", version.ref = "tree-sitter" }
Expand Down Expand Up @@ -262,9 +260,6 @@ aapt2-common = { module = "com.android.tools:common", version = "31.2.2" }
aapt2-annotations = { module = "com.android.tools:annotations", version = "31.2.2" }
aapt2-jb-annotations = { module = "org.jetbrains:annotations", version = "24.1.0" }

logging-logback-core = { module = "ch.qos.logback:logback-core", version.ref = "logback" }
logging-logback-classic = { module = "ch.qos.logback:logback-classic", version.ref = "logback" }

# XML
xml-xercesImpl = { module = "xerces:xercesImpl", version = "2.12.2" }
xml-apis = { module = "xml-apis:xml-apis", version = "2.0.2" }
Expand Down
5 changes: 2 additions & 3 deletions logger/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -29,12 +29,11 @@ dependencies {
kapt(libs.google.auto.service)
compileOnly(projects.subprojects.frameworkStubs)

api(libs.logging.logback.core)
api(libs.logging.logback.classic)
api(libs.tooling.slf4j)

implementation(projects.buildInfo)
implementation(libs.google.auto.service.annotations)

testImplementation(libs.tests.junit)
testImplementation(libs.tests.google.truth)
}
}
Loading
Loading