From bc848ebb623ee2e0a68fb4d36c5933683dffe92c Mon Sep 17 00:00:00 2001 From: Antoine Jaury Date: Fri, 19 Jun 2026 11:44:59 +0200 Subject: [PATCH] new: add logging abstraction with Kermit integration --- gradle/libs.versions.toml | 2 ++ shared/build.gradle.kts | 3 +++ .../gwenedeg/core/logging/KermitLogger.kt | 26 ++++++++++++++++++ .../fr/ajaury/gwenedeg/core/logging/Logger.kt | 27 +++++++++++++++++++ .../kotlin/fr/ajaury/gwenedeg/di/Modules.kt | 3 +++ .../player/ui/viewmodel/PlayerViewModel.kt | 12 ++++++--- 6 files changed, 70 insertions(+), 3 deletions(-) create mode 100644 shared/src/commonMain/kotlin/fr/ajaury/gwenedeg/core/logging/KermitLogger.kt create mode 100644 shared/src/commonMain/kotlin/fr/ajaury/gwenedeg/core/logging/Logger.kt diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 129ac7a..ed84fad 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -17,6 +17,7 @@ kotlinxCoroutines = "1.11.0" kotlinSerialization = "2.4.0" material3 = "1.11.0-alpha07" koin = "4.2.1" +kermit = "2.1.0" vorbisspi = "1.0.3.3" [libraries] @@ -34,6 +35,7 @@ koin-compose = { module = "io.insert-koin:koin-compose" } koin-compose-viewmodel = { module = "io.insert-koin:koin-compose-viewmodel" } koin-core = { module = "io.insert-koin:koin-core" } koin-core-viewmodel = { module = "io.insert-koin:koin-core-viewmodel" } +kermit = { module = "co.touchlab:kermit", version.ref = "kermit" } kotlin-test = { module = "org.jetbrains.kotlin:kotlin-test", version.ref = "kotlin" } kotlinx-browser = { module = "org.jetbrains.kotlinx:kotlinx-browser", version.ref = "kotlinxBrowser" } kotlinx-coroutinesCore = { module = "org.jetbrains.kotlinx:kotlinx-coroutines-core", version.ref = "kotlinxCoroutines" } diff --git a/shared/build.gradle.kts b/shared/build.gradle.kts index fa00046..db97269 100644 --- a/shared/build.gradle.kts +++ b/shared/build.gradle.kts @@ -69,6 +69,9 @@ kotlin { implementation(libs.compose.components.resources) implementation(libs.compose.uiToolingPreview) + // Logging + implementation(libs.kermit) + // Lifecycle implementation(libs.lifecycle.viewmodelCompose) implementation(libs.lifecycle.runtimeCompose) diff --git a/shared/src/commonMain/kotlin/fr/ajaury/gwenedeg/core/logging/KermitLogger.kt b/shared/src/commonMain/kotlin/fr/ajaury/gwenedeg/core/logging/KermitLogger.kt new file mode 100644 index 0000000..f8af6d9 --- /dev/null +++ b/shared/src/commonMain/kotlin/fr/ajaury/gwenedeg/core/logging/KermitLogger.kt @@ -0,0 +1,26 @@ +package fr.ajaury.gwenedeg.core.logging + +import co.touchlab.kermit.Logger as Kermit + +/** + * Kermit-backed implementation (adapter) of the [Logger] port. + */ +class KermitLogger( + private val tag: String = "Gwenedeg", +) : Logger { + override fun debug(message: String, throwable: Throwable?) { + Kermit.d(throwable = throwable, tag = tag) { message } + } + + override fun info(message: String, throwable: Throwable?) { + Kermit.i(throwable = throwable, tag = tag) { message } + } + + override fun warning(message: String, throwable: Throwable?) { + Kermit.w(throwable = throwable, tag = tag) { message } + } + + override fun error(message: String, throwable: Throwable?) { + Kermit.e(throwable = throwable, tag = tag) { message } + } +} diff --git a/shared/src/commonMain/kotlin/fr/ajaury/gwenedeg/core/logging/Logger.kt b/shared/src/commonMain/kotlin/fr/ajaury/gwenedeg/core/logging/Logger.kt new file mode 100644 index 0000000..984371b --- /dev/null +++ b/shared/src/commonMain/kotlin/fr/ajaury/gwenedeg/core/logging/Logger.kt @@ -0,0 +1,27 @@ +package fr.ajaury.gwenedeg.core.logging + +/** + * Logging abstraction so the rest of the app depends on this interface + * rather than on a concrete logging library. + */ +interface Logger { + fun debug( + message: String, + throwable: Throwable? = null, + ) + + fun info( + message: String, + throwable: Throwable? = null, + ) + + fun warning( + message: String, + throwable: Throwable? = null, + ) + + fun error( + message: String, + throwable: Throwable? = null, + ) +} diff --git a/shared/src/commonMain/kotlin/fr/ajaury/gwenedeg/di/Modules.kt b/shared/src/commonMain/kotlin/fr/ajaury/gwenedeg/di/Modules.kt index 3e961e5..a6f88d4 100644 --- a/shared/src/commonMain/kotlin/fr/ajaury/gwenedeg/di/Modules.kt +++ b/shared/src/commonMain/kotlin/fr/ajaury/gwenedeg/di/Modules.kt @@ -1,5 +1,7 @@ package fr.ajaury.gwenedeg.di +import fr.ajaury.gwenedeg.core.logging.KermitLogger +import fr.ajaury.gwenedeg.core.logging.Logger import fr.ajaury.gwenedeg.player.ui.viewmodel.PlayerViewModel import fr.ajaury.gwenedeg.records.data.InMemoryRecordRepository import fr.ajaury.gwenedeg.records.domain.RecordRepository @@ -10,6 +12,7 @@ import org.koin.core.module.dsl.viewModelOf import org.koin.dsl.module val sharedModule = module { + single { KermitLogger() } factoryOf(::InMemoryRecordRepository) { bind() } viewModelOf(::RecordsViewModel) viewModelOf(::PlayerViewModel) diff --git a/shared/src/commonMain/kotlin/fr/ajaury/gwenedeg/player/ui/viewmodel/PlayerViewModel.kt b/shared/src/commonMain/kotlin/fr/ajaury/gwenedeg/player/ui/viewmodel/PlayerViewModel.kt index 4726bf0..40ca0d0 100644 --- a/shared/src/commonMain/kotlin/fr/ajaury/gwenedeg/player/ui/viewmodel/PlayerViewModel.kt +++ b/shared/src/commonMain/kotlin/fr/ajaury/gwenedeg/player/ui/viewmodel/PlayerViewModel.kt @@ -2,6 +2,7 @@ package fr.ajaury.gwenedeg.player.ui.viewmodel import androidx.lifecycle.ViewModel import androidx.lifecycle.viewModelScope +import fr.ajaury.gwenedeg.core.logging.Logger import fr.ajaury.gwenedeg.player.AudioPlayer import fr.ajaury.gwenedeg.player.AudioSessionManager import fr.ajaury.gwenedeg.player.PlaybackState @@ -16,6 +17,7 @@ import kotlin.time.Duration.Companion.seconds class PlayerViewModel( private val audioPlayer: AudioPlayer, private val audioSessionManager: AudioSessionManager, + private val logger: Logger, ) : ViewModel() { private var filePath: String? = null @@ -32,9 +34,13 @@ class PlayerViewModel( } fun play(filePath: String) { - this.filePath = filePath - audioPlayer.load(uri = Res.getUri(filePath)) - audioPlayer.play() + try { + this.filePath = filePath + audioPlayer.load(uri = Res.getUri(filePath)) + audioPlayer.play() + } catch (exception: Exception) { + logger.error(message = "Failed to play audio: $filePath", throwable = exception) + } } fun performMainPlayAction() {