mirror of
https://github.com/meshtastic/Meshtastic-Android.git
synced 2026-04-20 22:23:37 +00:00
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:
parent
a927481e4d
commit
0776e029f3
92 changed files with 727 additions and 957 deletions
|
|
@ -17,6 +17,7 @@
|
|||
|
||||
package org.meshtastic.core.data.repository
|
||||
|
||||
import co.touchlab.kermit.Logger
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
import kotlinx.coroutines.flow.MutableStateFlow
|
||||
import kotlinx.coroutines.flow.asStateFlow
|
||||
|
|
@ -26,10 +27,8 @@ import kotlinx.serialization.json.Json
|
|||
import org.meshtastic.core.data.model.CustomTileProviderConfig
|
||||
import org.meshtastic.core.di.CoroutineDispatchers
|
||||
import org.meshtastic.core.prefs.map.MapTileProviderPrefs
|
||||
import timber.log.Timber
|
||||
import javax.inject.Inject
|
||||
import javax.inject.Singleton
|
||||
import kotlin.collections.plus
|
||||
|
||||
interface CustomTileProviderRepository {
|
||||
fun getCustomTileProviders(): Flow<List<CustomTileProviderConfig>>
|
||||
|
|
@ -88,7 +87,7 @@ constructor(
|
|||
try {
|
||||
customTileProvidersStateFlow.value = json.decodeFromString<List<CustomTileProviderConfig>>(jsonString)
|
||||
} catch (e: SerializationException) {
|
||||
Timber.e(e, "Error deserializing tile providers")
|
||||
Logger.e(e) { "Error deserializing tile providers" }
|
||||
customTileProvidersStateFlow.value = emptyList()
|
||||
}
|
||||
} else {
|
||||
|
|
@ -102,7 +101,7 @@ constructor(
|
|||
val jsonString = json.encodeToString(providers)
|
||||
mapTileProviderPrefs.customTileProviders = jsonString
|
||||
} catch (e: SerializationException) {
|
||||
Timber.e(e, "Error serializing tile providers")
|
||||
Logger.e(e) { "Error serializing tile providers" }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -18,12 +18,12 @@
|
|||
package org.meshtastic.core.data.datasource
|
||||
|
||||
import android.app.Application
|
||||
import co.touchlab.kermit.Logger
|
||||
import kotlinx.serialization.ExperimentalSerializationApi
|
||||
import kotlinx.serialization.Serializable
|
||||
import kotlinx.serialization.json.Json
|
||||
import kotlinx.serialization.json.decodeFromStream
|
||||
import org.meshtastic.core.model.BootloaderOtaQuirk
|
||||
import timber.log.Timber
|
||||
import javax.inject.Inject
|
||||
|
||||
class BootloaderOtaQuirksJsonDataSource @Inject constructor(private val application: Application) {
|
||||
|
|
@ -32,7 +32,7 @@ class BootloaderOtaQuirksJsonDataSource @Inject constructor(private val applicat
|
|||
val inputStream = application.assets.open("device_bootloader_ota_quirks.json")
|
||||
inputStream.use { Json.decodeFromStream<ListWrapper>(it).devices }
|
||||
}
|
||||
.onFailure { e -> Timber.w(e, "Failed to load device_bootloader_ota_quirks.json") }
|
||||
.onFailure { e -> Logger.w(e) { "Failed to load device_bootloader_ota_quirks.json" } }
|
||||
.getOrDefault(emptyList())
|
||||
|
||||
@Serializable private data class ListWrapper(val devices: List<BootloaderOtaQuirk> = emptyList())
|
||||
|
|
|
|||
|
|
@ -17,6 +17,7 @@
|
|||
|
||||
package org.meshtastic.core.data.repository
|
||||
|
||||
import co.touchlab.kermit.Logger
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.withContext
|
||||
import org.meshtastic.core.data.datasource.BootloaderOtaQuirksJsonDataSource
|
||||
|
|
@ -27,7 +28,6 @@ import org.meshtastic.core.database.entity.asExternalModel
|
|||
import org.meshtastic.core.model.BootloaderOtaQuirk
|
||||
import org.meshtastic.core.model.DeviceHardware
|
||||
import org.meshtastic.core.network.DeviceHardwareRemoteDataSource
|
||||
import timber.log.Timber
|
||||
import java.util.concurrent.TimeUnit
|
||||
import javax.inject.Inject
|
||||
import javax.inject.Singleton
|
||||
|
|
@ -59,45 +59,40 @@ constructor(
|
|||
@Suppress("LongMethod")
|
||||
suspend fun getDeviceHardwareByModel(hwModel: Int, forceRefresh: Boolean = false): Result<DeviceHardware?> =
|
||||
withContext(Dispatchers.IO) {
|
||||
Timber.d(
|
||||
"DeviceHardwareRepository: getDeviceHardwareByModel(hwModel=%d, forceRefresh=%b)",
|
||||
hwModel,
|
||||
forceRefresh,
|
||||
)
|
||||
Logger.d {
|
||||
"DeviceHardwareRepository: getDeviceHardwareByModel(hwModel=$hwModel, forceRefresh=$forceRefresh)"
|
||||
}
|
||||
|
||||
val quirks = loadQuirks()
|
||||
|
||||
if (forceRefresh) {
|
||||
Timber.d("DeviceHardwareRepository: forceRefresh=true, clearing local device hardware cache")
|
||||
Logger.d { "DeviceHardwareRepository: forceRefresh=true, clearing local device hardware cache" }
|
||||
localDataSource.deleteAllDeviceHardware()
|
||||
} else {
|
||||
// 1. Attempt to retrieve from cache first
|
||||
val cachedEntity = localDataSource.getByHwModel(hwModel)
|
||||
if (cachedEntity != null && !cachedEntity.isStale()) {
|
||||
Timber.d("DeviceHardwareRepository: using fresh cached device hardware for hwModel=%d", hwModel)
|
||||
Logger.d { "DeviceHardwareRepository: using fresh cached device hardware for hwModel=$hwModel" }
|
||||
return@withContext Result.success(
|
||||
applyBootloaderQuirk(hwModel, cachedEntity.asExternalModel(), quirks),
|
||||
)
|
||||
}
|
||||
Timber.d("DeviceHardwareRepository: no fresh cache for hwModel=%d, attempting remote fetch", hwModel)
|
||||
Logger.d { "DeviceHardwareRepository: no fresh cache for hwModel=$hwModel, attempting remote fetch" }
|
||||
}
|
||||
|
||||
// 2. Fetch from remote API
|
||||
runCatching {
|
||||
Timber.d("DeviceHardwareRepository: fetching device hardware from remote API")
|
||||
Logger.d { "DeviceHardwareRepository: fetching device hardware from remote API" }
|
||||
val remoteHardware = remoteDataSource.getAllDeviceHardware()
|
||||
Timber.d(
|
||||
"DeviceHardwareRepository: remote API returned %d device hardware entries",
|
||||
remoteHardware.size,
|
||||
)
|
||||
Logger.d {
|
||||
"DeviceHardwareRepository: remote API returned ${remoteHardware.size} device hardware entries"
|
||||
}
|
||||
|
||||
localDataSource.insertAllDeviceHardware(remoteHardware)
|
||||
val fromDb = localDataSource.getByHwModel(hwModel)?.asExternalModel()
|
||||
Timber.d(
|
||||
"DeviceHardwareRepository: lookup after remote fetch for hwModel=%d %s",
|
||||
hwModel,
|
||||
if (fromDb != null) "succeeded" else "returned null",
|
||||
)
|
||||
Logger.d {
|
||||
"DeviceHardwareRepository: lookup after remote fetch for hwModel=$hwModel ${if (fromDb != null) "succeeded" else "returned null"}"
|
||||
}
|
||||
fromDb
|
||||
}
|
||||
.onSuccess {
|
||||
|
|
@ -105,57 +100,48 @@ constructor(
|
|||
return@withContext Result.success(applyBootloaderQuirk(hwModel, it, quirks))
|
||||
}
|
||||
.onFailure { e ->
|
||||
Timber.w(
|
||||
e,
|
||||
"DeviceHardwareRepository: failed to fetch device hardware from server for hwModel=%d",
|
||||
hwModel,
|
||||
)
|
||||
Logger.w(e) {
|
||||
"DeviceHardwareRepository: failed to fetch device hardware from server for hwModel=$hwModel"
|
||||
}
|
||||
|
||||
// 3. Attempt to use stale cache as a fallback, but only if it looks complete.
|
||||
val staleEntity = localDataSource.getByHwModel(hwModel)
|
||||
if (staleEntity != null && !staleEntity.isIncomplete()) {
|
||||
Timber.d("DeviceHardwareRepository: using stale cached device hardware for hwModel=%d", hwModel)
|
||||
Logger.d { "DeviceHardwareRepository: using stale cached device hardware for hwModel=$hwModel" }
|
||||
return@withContext Result.success(
|
||||
applyBootloaderQuirk(hwModel, staleEntity.asExternalModel(), quirks),
|
||||
)
|
||||
}
|
||||
|
||||
// 4. Fallback to bundled JSON if cache is empty or incomplete
|
||||
Timber.d(
|
||||
"DeviceHardwareRepository: cache %s for hwModel=%d, falling back to bundled JSON asset",
|
||||
if (staleEntity == null) "empty" else "incomplete",
|
||||
hwModel,
|
||||
)
|
||||
Logger.d {
|
||||
"DeviceHardwareRepository: cache ${if (staleEntity == null) "empty" else "incomplete"} for hwModel=$hwModel, falling back to bundled JSON asset"
|
||||
}
|
||||
return@withContext loadFromBundledJson(hwModel, quirks)
|
||||
}
|
||||
}
|
||||
|
||||
private suspend fun loadFromBundledJson(hwModel: Int, quirks: List<BootloaderOtaQuirk>): Result<DeviceHardware?> =
|
||||
runCatching {
|
||||
Timber.d("DeviceHardwareRepository: loading device hardware from bundled JSON for hwModel=%d", hwModel)
|
||||
Logger.d { "DeviceHardwareRepository: loading device hardware from bundled JSON for hwModel=$hwModel" }
|
||||
val jsonHardware = jsonDataSource.loadDeviceHardwareFromJsonAsset()
|
||||
Timber.d(
|
||||
"DeviceHardwareRepository: bundled JSON returned %d device hardware entries",
|
||||
jsonHardware.size,
|
||||
)
|
||||
Logger.d {
|
||||
"DeviceHardwareRepository: bundled JSON returned ${jsonHardware.size} device hardware entries"
|
||||
}
|
||||
|
||||
localDataSource.insertAllDeviceHardware(jsonHardware)
|
||||
val base = localDataSource.getByHwModel(hwModel)?.asExternalModel()
|
||||
Timber.d(
|
||||
"DeviceHardwareRepository: lookup after JSON load for hwModel=%d %s",
|
||||
hwModel,
|
||||
if (base != null) "succeeded" else "returned null",
|
||||
)
|
||||
Logger.d {
|
||||
"DeviceHardwareRepository: lookup after JSON load for hwModel=$hwModel ${if (base != null) "succeeded" else "returned null"}"
|
||||
}
|
||||
|
||||
applyBootloaderQuirk(hwModel, base, quirks)
|
||||
}
|
||||
.also { result ->
|
||||
result.exceptionOrNull()?.let { e ->
|
||||
Timber.e(
|
||||
e,
|
||||
"DeviceHardwareRepository: failed to load device hardware from bundled JSON for hwModel=%d",
|
||||
hwModel,
|
||||
)
|
||||
Logger.e(e) {
|
||||
"DeviceHardwareRepository: failed to load device hardware from bundled JSON for hwModel=$hwModel"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -174,7 +160,7 @@ constructor(
|
|||
|
||||
private fun loadQuirks(): List<BootloaderOtaQuirk> {
|
||||
val quirks = bootloaderOtaQuirksJsonDataSource.loadBootloaderOtaQuirksFromJsonAsset()
|
||||
Timber.d("DeviceHardwareRepository: loaded %d bootloader quirks", quirks.size)
|
||||
Logger.d { "DeviceHardwareRepository: loaded ${quirks.size} bootloader quirks" }
|
||||
return quirks
|
||||
}
|
||||
|
||||
|
|
@ -186,17 +172,11 @@ constructor(
|
|||
if (base == null) return null
|
||||
|
||||
val quirk = quirks.firstOrNull { it.hwModel == hwModel }
|
||||
Timber.d(
|
||||
"DeviceHardwareRepository: applyBootloaderQuirk for hwModel=%d, quirk found=%b",
|
||||
hwModel,
|
||||
quirk != null,
|
||||
)
|
||||
Logger.d { "DeviceHardwareRepository: applyBootloaderQuirk for hwModel=$hwModel, quirk found=${quirk != null}" }
|
||||
return if (quirk != null) {
|
||||
Timber.d(
|
||||
"DeviceHardwareRepository: applying quirk: requiresBootloaderUpgradeForOta=%b, infoUrl=%s",
|
||||
quirk.requiresBootloaderUpgradeForOta,
|
||||
quirk.infoUrl,
|
||||
)
|
||||
Logger.d {
|
||||
"DeviceHardwareRepository: applying quirk: requiresBootloaderUpgradeForOta=${quirk.requiresBootloaderUpgradeForOta}, infoUrl=${quirk.infoUrl}"
|
||||
}
|
||||
base.copy(
|
||||
requiresBootloaderUpgradeForOta = quirk.requiresBootloaderUpgradeForOta,
|
||||
bootloaderInfoUrl = quirk.infoUrl,
|
||||
|
|
|
|||
|
|
@ -17,6 +17,7 @@
|
|||
|
||||
package org.meshtastic.core.data.repository
|
||||
|
||||
import co.touchlab.kermit.Logger
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
import kotlinx.coroutines.flow.flow
|
||||
import org.meshtastic.core.data.datasource.FirmwareReleaseJsonDataSource
|
||||
|
|
@ -26,7 +27,6 @@ import org.meshtastic.core.database.entity.FirmwareReleaseEntity
|
|||
import org.meshtastic.core.database.entity.FirmwareReleaseType
|
||||
import org.meshtastic.core.database.entity.asExternalModel
|
||||
import org.meshtastic.core.network.FirmwareReleaseRemoteDataSource
|
||||
import timber.log.Timber
|
||||
import java.util.concurrent.TimeUnit
|
||||
import javax.inject.Inject
|
||||
import javax.inject.Singleton
|
||||
|
|
@ -68,7 +68,7 @@ constructor(
|
|||
// This gives the UI something to show immediately.
|
||||
val cachedRelease = localDataSource.getLatestRelease(releaseType)
|
||||
cachedRelease?.let {
|
||||
Timber.d("Emitting cached firmware for $releaseType (isStale=${it.isStale()})")
|
||||
Logger.d { "Emitting cached firmware for $releaseType (isStale=${it.isStale()})" }
|
||||
emit(it.asExternalModel())
|
||||
}
|
||||
|
||||
|
|
@ -84,7 +84,7 @@ constructor(
|
|||
// The `distinctUntilChanged()` operator on the collector side will prevent
|
||||
// re-emitting the same data if the cache wasn't actually updated.
|
||||
val finalRelease = localDataSource.getLatestRelease(releaseType)
|
||||
Timber.d("Emitting final firmware for $releaseType from cache.")
|
||||
Logger.d { "Emitting final firmware for $releaseType from cache." }
|
||||
emit(finalRelease?.asExternalModel())
|
||||
}
|
||||
|
||||
|
|
@ -98,7 +98,7 @@ constructor(
|
|||
private suspend fun updateCacheFromSources() {
|
||||
val remoteFetchSuccess =
|
||||
runCatching {
|
||||
Timber.d("Fetching fresh firmware releases from remote API.")
|
||||
Logger.d { "Fetching fresh firmware releases from remote API." }
|
||||
val networkReleases = remoteDataSource.getFirmwareReleases()
|
||||
|
||||
// The API fetches all release types, so we cache them all at once.
|
||||
|
|
@ -109,13 +109,13 @@ constructor(
|
|||
|
||||
// If remote fetch failed, try the JSON fallback as a last resort.
|
||||
if (!remoteFetchSuccess) {
|
||||
Timber.w("Remote fetch failed, attempting to cache from bundled JSON.")
|
||||
Logger.w { "Remote fetch failed, attempting to cache from bundled JSON." }
|
||||
runCatching {
|
||||
val jsonReleases = jsonDataSource.loadFirmwareReleaseFromJsonAsset()
|
||||
localDataSource.insertFirmwareReleases(jsonReleases.releases.stable, FirmwareReleaseType.STABLE)
|
||||
localDataSource.insertFirmwareReleases(jsonReleases.releases.alpha, FirmwareReleaseType.ALPHA)
|
||||
}
|
||||
.onFailure { Timber.w("Failed to cache from JSON: ${it.message}") }
|
||||
.onFailure { Logger.w { "Failed to cache from JSON: ${it.message}" } }
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -27,6 +27,7 @@ import androidx.core.location.LocationListenerCompat
|
|||
import androidx.core.location.LocationManagerCompat
|
||||
import androidx.core.location.LocationRequestCompat
|
||||
import androidx.core.location.altitude.AltitudeConverterCompat
|
||||
import co.touchlab.kermit.Logger
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.asExecutor
|
||||
import kotlinx.coroutines.channels.awaitClose
|
||||
|
|
@ -34,7 +35,6 @@ import kotlinx.coroutines.flow.MutableStateFlow
|
|||
import kotlinx.coroutines.flow.StateFlow
|
||||
import kotlinx.coroutines.flow.callbackFlow
|
||||
import org.meshtastic.core.analytics.platform.PlatformAnalytics
|
||||
import timber.log.Timber
|
||||
import javax.inject.Inject
|
||||
import javax.inject.Singleton
|
||||
|
||||
|
|
@ -68,7 +68,7 @@ constructor(
|
|||
try {
|
||||
AltitudeConverterCompat.addMslAltitudeToLocation(context, location)
|
||||
} catch (e: Exception) {
|
||||
Timber.e(e, "addMslAltitudeToLocation() failed")
|
||||
Logger.e(e) { "addMslAltitudeToLocation() failed" }
|
||||
}
|
||||
}
|
||||
// info("New location: $location")
|
||||
|
|
@ -85,9 +85,9 @@ constructor(
|
|||
}
|
||||
}
|
||||
|
||||
Timber.i(
|
||||
"Starting location updates with $providerList intervalMs=${intervalMs}ms and minDistanceM=${minDistanceM}m",
|
||||
)
|
||||
Logger.i {
|
||||
"Starting location updates with $providerList intervalMs=${intervalMs}ms and minDistanceM=${minDistanceM}m"
|
||||
}
|
||||
_receivingLocationUpdates.value = true
|
||||
analytics.track("location_start") // Figure out how many users needed to use the phone GPS
|
||||
|
||||
|
|
@ -106,7 +106,7 @@ constructor(
|
|||
}
|
||||
|
||||
awaitClose {
|
||||
Timber.i("Stopping location requests")
|
||||
Logger.i { "Stopping location requests" }
|
||||
_receivingLocationUpdates.value = false
|
||||
analytics.track("location_stop")
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue