feat(logging): Replace Timber with Kermit for multiplatform logging (#4083)

Signed-off-by: James Rich <2199651+jamesarich@users.noreply.github.com>
This commit is contained in:
James Rich 2025-12-28 08:30:15 -06:00 committed by GitHub
parent a927481e4d
commit 0776e029f3
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
92 changed files with 727 additions and 957 deletions

View file

@ -19,9 +19,10 @@ package org.meshtastic.core.analytics.platform
import androidx.compose.runtime.Composable
import androidx.navigation.NavHostController
import co.touchlab.kermit.Logger
import co.touchlab.kermit.Severity
import org.meshtastic.core.analytics.BuildConfig
import org.meshtastic.core.analytics.DataPair
import timber.log.Timber
import javax.inject.Inject
/**
@ -34,16 +35,17 @@ class FdroidPlatformAnalytics @Inject constructor() : PlatformAnalytics {
// In debug builds we attach a DebugTree for convenient local logging, but
// release builds rely on system logging only.
if (BuildConfig.DEBUG) {
Timber.plant(Timber.DebugTree())
Timber.i("F-Droid platform no-op analytics initialized (DebugTree planted).")
Logger.setMinSeverity(Severity.Debug)
Logger.i { "F-Droid platform no-op analytics initialized (Debug mode }." }
} else {
Timber.i("F-Droid platform no-op analytics initialized.")
Logger.setMinSeverity(Severity.Info)
Logger.i { "F-Droid platform no-op analytics initialized." }
}
}
override fun setDeviceAttributes(firmwareVersion: String, model: String) {
// No-op for F-Droid
Timber.d("Set device attributes called: firmwareVersion=$firmwareVersion, deviceHardware=$model")
Logger.d { "Set device attributes called: firmwareVersion=$firmwareVersion, deviceHardware=$model" }
}
@Composable
@ -51,7 +53,7 @@ class FdroidPlatformAnalytics @Inject constructor() : PlatformAnalytics {
// No-op for F-Droid, but we can log navigation if needed for debugging
if (BuildConfig.DEBUG) {
navController.addOnDestinationChangedListener { _, destination, _ ->
Timber.d("Navigation changed to: ${destination.route}")
Logger.d { "Navigation changed to: ${destination.route}" }
}
}
}
@ -60,6 +62,6 @@ class FdroidPlatformAnalytics @Inject constructor() : PlatformAnalytics {
get() = false
override fun track(event: String, vararg properties: DataPair) {
Timber.d("Track called: event=$event, properties=${properties.toList()}")
Logger.d { "Track called: event=$event, properties=${properties.toList()}" }
}
}

View file

@ -26,6 +26,8 @@ import androidx.compose.runtime.Composable
import androidx.lifecycle.ProcessLifecycleOwner
import androidx.lifecycle.lifecycleScope
import androidx.navigation.NavHostController
import co.touchlab.kermit.LogWriter
import co.touchlab.kermit.Severity
import com.datadog.android.Datadog
import com.datadog.android.DatadogSite
import com.datadog.android.compose.ExperimentalTrackingApi
@ -43,7 +45,6 @@ import com.datadog.android.rum.tracking.AcceptAllNavDestinations
import com.datadog.android.sessionreplay.SessionReplay
import com.datadog.android.sessionreplay.SessionReplayConfiguration
import com.datadog.android.sessionreplay.compose.ComposeExtensionSupport
import com.datadog.android.timber.DatadogTree
import com.datadog.android.trace.Trace
import com.datadog.android.trace.TraceConfiguration
import com.datadog.android.trace.opentelemetry.DatadogOpenTelemetry
@ -61,10 +62,8 @@ import kotlinx.coroutines.flow.onEach
import org.meshtastic.core.analytics.BuildConfig
import org.meshtastic.core.analytics.DataPair
import org.meshtastic.core.prefs.analytics.AnalyticsPrefs
import timber.log.Timber
import timber.log.Timber.DebugTree
import timber.log.Timber.Tree
import javax.inject.Inject
import co.touchlab.kermit.Logger as KermitLogger
/**
* Google Play Services specific implementation of [PlatformAnalytics]. This helper initializes and manages Firebase and
@ -102,14 +101,15 @@ constructor(
.setBundleWithTraceEnabled(true)
.setBundleWithRumEnabled(true)
.build()
buildList {
add(DatadogTree(datadogLogger))
add(CrashlyticsTree())
val writers = buildList {
add(DatadogLogWriter(datadogLogger))
add(CrashlyticsLogWriter())
if (BuildConfig.DEBUG) {
add(DebugTree())
add(co.touchlab.kermit.LogcatWriter())
}
}
.forEach(Timber::plant)
KermitLogger.setLogWriters(writers)
KermitLogger.setMinSeverity(if (BuildConfig.DEBUG) Severity.Debug else Severity.Info)
// Initial consent state
updateAnalyticsConsent(analyticsPrefs.analyticsAllowed)
@ -177,10 +177,10 @@ constructor(
*/
fun updateAnalyticsConsent(allowed: Boolean) {
if (!isPlatformServicesAvailable || isInTestLab) {
Timber.i("Analytics not available or in test lab, consent update skipped.")
KermitLogger.i { "Analytics not available or in test lab, consent update skipped." }
return
}
Timber.i(if (allowed) "Analytics enabled" else "Analytics disabled")
KermitLogger.i { if (allowed) "Analytics enabled" else "Analytics disabled" }
Datadog.setTrackingConsent(if (allowed) TrackingConsent.GRANTED else TrackingConsent.NOT_GRANTED)
Firebase.crashlytics.isCrashlyticsCollectionEnabled = allowed
@ -221,30 +221,45 @@ constructor(
override val isPlatformServicesAvailable: Boolean
get() = isGooglePlayAvailable && isDatadogAvailable
private class CrashlyticsTree : Tree() {
private class CrashlyticsLogWriter : LogWriter() {
companion object {
private const val KEY_PRIORITY = "priority"
private const val KEY_TAG = "tag"
private const val KEY_MESSAGE = "message"
}
override fun log(priority: Int, tag: String?, message: String, t: Throwable?) {
override fun log(severity: Severity, message: String, tag: String, throwable: Throwable?) {
if (!Firebase.crashlytics.isCrashlyticsCollectionEnabled) return
Firebase.crashlytics.setCustomKeys {
key(KEY_PRIORITY, priority)
key(KEY_TAG, tag ?: "No Tag")
key(KEY_PRIORITY, severity.ordinal)
key(KEY_TAG, tag)
key(KEY_MESSAGE, message)
}
if (t == null) {
if (throwable == null) {
Firebase.crashlytics.recordException(Exception(message))
} else {
Firebase.crashlytics.recordException(t)
Firebase.crashlytics.recordException(throwable)
}
}
}
private class DatadogLogWriter(private val datadogLogger: Logger) : LogWriter() {
override fun log(severity: Severity, message: String, tag: String, throwable: Throwable?) {
val datadogPriority =
when (severity) {
Severity.Verbose -> android.util.Log.VERBOSE
Severity.Debug -> android.util.Log.DEBUG
Severity.Info -> android.util.Log.INFO
Severity.Warn -> android.util.Log.WARN
Severity.Error -> android.util.Log.ERROR
Severity.Assert -> android.util.Log.ASSERT
}
datadogLogger.log(datadogPriority, message, throwable, mapOf("tag" to tag))
}
}
private fun String.extractSemanticVersion(): String {
val regex = "^(\\d+)(?:\\.(\\d+))?(?:\\.(\\d+))?".toRegex()
val matchResult = regex.find(this)
@ -263,7 +278,7 @@ constructor(
is String -> bundle.putString(it.name, it.value as String?) // Explicitly handle String
else -> bundle.putString(it.name, it.value.toString()) // Fallback for other types
}
Timber.tag(TAG).d("Analytics: track $event (${it.name} : ${it.value})")
KermitLogger.withTag(TAG).d { "Analytics: track $event (${it.name} : ${it.value})" }
}
Firebase.analytics.logEvent(event, bundle)
}