diff --git a/extensions/kermit-coil3/README.md b/extensions/kermit-coil3/README.md new file mode 100644 index 00000000..24dc5487 --- /dev/null +++ b/extensions/kermit-coil3/README.md @@ -0,0 +1,114 @@ +# Kermit-Coil3 Integration + +The Kermit-Coil3 module lets +an [existing Kermit logger](https://kermit.touchlab.co/docs/configuration/LOGGER_SETUP) receive logs +from Coil3 image loaders. + +Firstly, add the Gradle dependency to your project. + +```kotlin +sourceSets { + commonMain { + dependencies { + implementation("co.touchlab:kermit-coil3:x.y.z") // Add the latest version + } + } +} +``` + +Then add the Kermit logger when you create your Coil image loader. + +```kotlin +val imageLoader = ImageLoader.Builder(context) + .logger(KermitCoil3Logger(logger = KermitLogger)) + .build() +``` + +or create the Kermit logger from a mutable configuration: + +```kotlin +val imageLoader = ImageLoader.Builder(context) + .logger( + KermitCoil3Logger( + config = mutableLoggerConfigInit(CommonWriter(), minSeverity = Severity.Info), + ), + ) + .build() +``` + +or create the Kermit logger from severity with optional logWriters: + +```kotlin +val imageLoader = ImageLoader.Builder(context) + .logger( + KermitCoil3Logger( + severity = Severity.Info, + CommonWriter(), + ), + ) + .build() +``` + +or if you want to use Coil3's `Logger.Level` to control the minimum log level, +you can use the `minLevel` property: + +```kotlin +import coil3.util.Logger as CoilLogger + +val imageLoader = ImageLoader.Builder(context) + .logger( + KermitCoil3Logger( + minLevel = CoilLogger.Level.Info, + CommonWriter(), + ), + ) + .build() +``` + +Like Coil3's default logger, `KermitCoil3Logger` can update the minimum log level at runtime. +You can change the minimum log level by setting the `minLevel` property on the logger instance: +> ⚠️ Note: Runtime `minLevel` updates require the underlying `LoggerConfig` to be a +> `MutableLoggerConfig`. If the logger you pass in was created with an immutable config, +> setting `minLevel` at runtime will throw. If you need to change the log level at runtime, +> create your Kermit logger with a mutable config, or use one of the constructors +> that take a `MutableLoggerConfig`, `Severity`, or `CoilLogger.Level` directly. + +```kotlin +import coil3.util.Logger as CoilLogger + +val kermitCoil3Logger = KermitCoil3Logger( + minLevel = CoilLogger.Level.Info, + CommonWriter(), +) + +// Later in your code, you can change the minimum log level: +kermitCoil3Logger.minLevel = CoilLogger.Level.Debug +``` + +Additionally, `KermitCoil3Logger` bridges Coil3's `Logger.Level` and Kermit's `Severity` using two +public extension functions: + +```kotlin +import coil3.util.Logger as CoilLogger + +fun CoilLogger.Level.toKermitSeverity(): Severity +fun Severity.toCoilLoggerLevel(): CoilLogger.Level +``` + +Most levels map one-to-one: + +| Coil3 `Logger.Level` | Kermit `Severity` | +|----------------------|-------------------| +| `Verbose` | `Verbose` | +| `Debug` | `Debug` | +| `Info` | `Info` | +| `Warn` | `Warn` | +| `Error` | `Error` | + +Since Coil3 has no equivalent to Kermit's `Severity.Assert`, `Severity.Assert` is mapped down to +`CoilLogger.Level.Error` when converting from Kermit to Coil3. This conversion is lossy in one +direction only — converting back from `CoilLogger.Level.Error` will always produce +`Severity.Error`, never `Severity.Assert`. + +These extensions are public and can be used independently if you need to convert between the two +logging systems outside of `KermitCoil3Logger`. diff --git a/extensions/kermit-coil3/api/android/kermit-coil3.api b/extensions/kermit-coil3/api/android/kermit-coil3.api new file mode 100644 index 00000000..bc2434cc --- /dev/null +++ b/extensions/kermit-coil3/api/android/kermit-coil3.api @@ -0,0 +1,15 @@ +public final class co/touchlab/kermit/coil3/ExtensionsKt { + public static final fun toCoil3LoggerLevel (Lco/touchlab/kermit/Severity;)Lcoil3/util/Logger$Level; + public static final fun toKermitSeverity (Lcoil3/util/Logger$Level;)Lco/touchlab/kermit/Severity; +} + +public final class co/touchlab/kermit/coil3/KermitCoil3Logger : coil3/util/Logger { + public fun (Lco/touchlab/kermit/Logger;)V + public fun (Lco/touchlab/kermit/MutableLoggerConfig;)V + public fun (Lco/touchlab/kermit/Severity;[Lco/touchlab/kermit/LogWriter;)V + public fun (Lcoil3/util/Logger$Level;[Lco/touchlab/kermit/LogWriter;)V + public fun getMinLevel ()Lcoil3/util/Logger$Level; + public fun log (Ljava/lang/String;Lcoil3/util/Logger$Level;Ljava/lang/String;Ljava/lang/Throwable;)V + public fun setMinLevel (Lcoil3/util/Logger$Level;)V +} + diff --git a/extensions/kermit-coil3/api/jvm/kermit-coil3.api b/extensions/kermit-coil3/api/jvm/kermit-coil3.api new file mode 100644 index 00000000..bc2434cc --- /dev/null +++ b/extensions/kermit-coil3/api/jvm/kermit-coil3.api @@ -0,0 +1,15 @@ +public final class co/touchlab/kermit/coil3/ExtensionsKt { + public static final fun toCoil3LoggerLevel (Lco/touchlab/kermit/Severity;)Lcoil3/util/Logger$Level; + public static final fun toKermitSeverity (Lcoil3/util/Logger$Level;)Lco/touchlab/kermit/Severity; +} + +public final class co/touchlab/kermit/coil3/KermitCoil3Logger : coil3/util/Logger { + public fun (Lco/touchlab/kermit/Logger;)V + public fun (Lco/touchlab/kermit/MutableLoggerConfig;)V + public fun (Lco/touchlab/kermit/Severity;[Lco/touchlab/kermit/LogWriter;)V + public fun (Lcoil3/util/Logger$Level;[Lco/touchlab/kermit/LogWriter;)V + public fun getMinLevel ()Lcoil3/util/Logger$Level; + public fun log (Ljava/lang/String;Lcoil3/util/Logger$Level;Ljava/lang/String;Ljava/lang/Throwable;)V + public fun setMinLevel (Lcoil3/util/Logger$Level;)V +} + diff --git a/extensions/kermit-coil3/build.gradle.kts b/extensions/kermit-coil3/build.gradle.kts new file mode 100644 index 00000000..a46d14ce --- /dev/null +++ b/extensions/kermit-coil3/build.gradle.kts @@ -0,0 +1,76 @@ +/* + * Copyright (c) 2026 Touchlab + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except + * in compliance with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License + * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express + * or implied. See the License for the specific language governing permissions and limitations under + * the License. + */ + +plugins { + alias(libs.plugins.android.library) + alias(libs.plugins.kotlin.multiplatform) + id("kermit-jvm-target") + id("wasm-setup") + id("kermit-publish") +} + +kotlin { + androidTarget { + publishLibraryVariants("release") + } + jvm() + macosArm64() + iosArm64() + iosSimulatorArm64() + js { + browser() + nodejs() + } + + // TODO: These targets are stopped the support by Coil3 in newer versions: + macosX64() + iosX64() + + // TODO: These targets are supported by Coil3 in newer versions: + // linuxX64() + // linuxArm64() + + // TODO: These targets aren't supported by Coil3 yet: + // tvosArm64() + // tvosSimulatorArm64() + // tvosX64() + // watchosArm32() + // watchosArm64() + // watchosSimulatorArm64() + // watchosDeviceArm64() + // watchosX64() + // mingwX64() + // androidNativeArm32() + // androidNativeArm64() + // androidNativeX86() + // androidNativeX64() + + sourceSets { + commonMain.dependencies { + implementation(libs.coil3.core) + implementation(project(":kermit")) + } + } +} + +android { + namespace = "co.touchlab.kermit.coil3" + compileSdk = libs.versions.compileSdk.get().toInt() + defaultConfig { + minSdk = libs.versions.minSdk.get().toInt() + } + compileOptions { + sourceCompatibility = JavaVersion.VERSION_1_8 + targetCompatibility = JavaVersion.VERSION_1_8 + } +} diff --git a/extensions/kermit-coil3/src/commonMain/kotlin/co/touchlab/kermit/coil3/Extensions.kt b/extensions/kermit-coil3/src/commonMain/kotlin/co/touchlab/kermit/coil3/Extensions.kt new file mode 100644 index 00000000..6616c6b6 --- /dev/null +++ b/extensions/kermit-coil3/src/commonMain/kotlin/co/touchlab/kermit/coil3/Extensions.kt @@ -0,0 +1,34 @@ +/* + * Copyright (c) 2026 Touchlab + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except + * in compliance with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License + * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express + * or implied. See the License for the specific language governing permissions and limitations under + * the License. + */ + +package co.touchlab.kermit.coil3 + +import co.touchlab.kermit.Severity +import coil3.util.Logger as CoilLogger + +fun CoilLogger.Level.toKermitSeverity(): Severity = when (this) { + CoilLogger.Level.Verbose -> Severity.Verbose + CoilLogger.Level.Debug -> Severity.Debug + CoilLogger.Level.Info -> Severity.Info + CoilLogger.Level.Warn -> Severity.Warn + CoilLogger.Level.Error -> Severity.Error +} + +fun Severity.toCoil3LoggerLevel(): CoilLogger.Level = when (this) { + Severity.Verbose -> CoilLogger.Level.Verbose + Severity.Debug -> CoilLogger.Level.Debug + Severity.Info -> CoilLogger.Level.Info + Severity.Warn -> CoilLogger.Level.Warn + Severity.Error -> CoilLogger.Level.Error + Severity.Assert -> CoilLogger.Level.Error // Map Assert to Error for CoilLogger +} diff --git a/extensions/kermit-coil3/src/commonMain/kotlin/co/touchlab/kermit/coil3/KermitCoil3Logger.kt b/extensions/kermit-coil3/src/commonMain/kotlin/co/touchlab/kermit/coil3/KermitCoil3Logger.kt new file mode 100644 index 00000000..49869d93 --- /dev/null +++ b/extensions/kermit-coil3/src/commonMain/kotlin/co/touchlab/kermit/coil3/KermitCoil3Logger.kt @@ -0,0 +1,67 @@ +/* + * Copyright (c) 2026 Touchlab + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except + * in compliance with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License + * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express + * or implied. See the License for the specific language governing permissions and limitations under + * the License. + */ + +package co.touchlab.kermit.coil3 + +import co.touchlab.kermit.LogWriter +import co.touchlab.kermit.Logger as KermitLogger +import co.touchlab.kermit.MutableLoggerConfig +import co.touchlab.kermit.Severity +import co.touchlab.kermit.mutableLoggerConfigInit +import coil3.util.Logger as CoilLogger + +class KermitCoil3Logger : CoilLogger { + + private val logger: KermitLogger + + constructor( + logger: KermitLogger, + ) : super() { + this.logger = logger + } + + constructor( + config: MutableLoggerConfig, + ) : super() { + logger = KermitLogger(config) + } + + constructor( + severity: Severity, + vararg logWriters: LogWriter, + ) : super() { + logger = KermitLogger( + mutableLoggerConfigInit(*logWriters, minSeverity = severity), + ) + } + + constructor( + minLevel: CoilLogger.Level, + vararg logWriters: LogWriter, + ) : super() { + logger = KermitLogger( + mutableLoggerConfigInit(*logWriters, minSeverity = minLevel.toKermitSeverity()), + ) + } + + override var minLevel: CoilLogger.Level + get() = logger.config.minSeverity.toCoil3LoggerLevel() + set(value) { + logger.mutableConfig.minSeverity = value.toKermitSeverity() + } + + override fun log(tag: String, level: CoilLogger.Level, message: String?, throwable: Throwable?) { + if (message == null && throwable == null) return + logger.log(level.toKermitSeverity(), tag, throwable, message.orEmpty()) + } +} diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index eec89f10..dd8de894 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -36,6 +36,7 @@ mavenPublish = "0.34.0" dokka = "2.0.0" touchlab-docusaurus-template = "0.1.11" ktor = "3.2.3" +coil3 = "3.3.0" ktlint-gradle = "12.3.0" @@ -56,6 +57,7 @@ bugsnag-android = { module = "com.bugsnag:bugsnag-android", version.ref = "bugsn ktor-logging = { module = "io.ktor:ktor-client-logging", version.ref = "ktor" } ktor-client-core = { module = "io.ktor:ktor-client-core", version.ref = "ktor" } ktor-client-cio = { module = "io.ktor:ktor-client-cio", version.ref = "ktor" } +coil3-core = { module = "io.coil-kt.coil3:coil-core", version.ref = "coil3" } stately-collections = { module = "co.touchlab:stately-collections", version.ref = "stately" } testhelp = { module = "co.touchlab:testhelp", version.ref = "testhelp" } diff --git a/settings.gradle.kts b/settings.gradle.kts index 6b2ab394..f1e6a8b8 100644 --- a/settings.gradle.kts +++ b/settings.gradle.kts @@ -20,11 +20,13 @@ include(":kermit-crashlytics") include(":kermit-bugsnag") include(":kermit-koin") include(":kermit-ktor") +include(":kermit-coil3") project(":kermit-crashlytics").projectDir = File("extensions/kermit-crashlytics") project(":kermit-bugsnag").projectDir = File("extensions/kermit-bugsnag") project(":kermit-koin").projectDir = File("extensions/kermit-koin") project(":kermit-ktor").projectDir = File("extensions/kermit-ktor") +project(":kermit-coil3").projectDir = File("extensions/kermit-coil3") // include(":kermit-gradle-plugin") // include(":kermit-ir-plugin")