Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
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
114 changes: 114 additions & 0 deletions extensions/kermit-coil3/README.md
Original file line number Diff line number Diff line change
@@ -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`.
15 changes: 15 additions & 0 deletions extensions/kermit-coil3/api/android/kermit-coil3.api
Original file line number Diff line number Diff line change
@@ -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 <init> (Lco/touchlab/kermit/Logger;)V
public fun <init> (Lco/touchlab/kermit/MutableLoggerConfig;)V
public fun <init> (Lco/touchlab/kermit/Severity;[Lco/touchlab/kermit/LogWriter;)V
public fun <init> (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
}

15 changes: 15 additions & 0 deletions extensions/kermit-coil3/api/jvm/kermit-coil3.api
Original file line number Diff line number Diff line change
@@ -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 <init> (Lco/touchlab/kermit/Logger;)V
public fun <init> (Lco/touchlab/kermit/MutableLoggerConfig;)V
public fun <init> (Lco/touchlab/kermit/Severity;[Lco/touchlab/kermit/LogWriter;)V
public fun <init> (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
}

76 changes: 76 additions & 0 deletions extensions/kermit-coil3/build.gradle.kts
Original file line number Diff line number Diff line change
@@ -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
}
}
Original file line number Diff line number Diff line change
@@ -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
}
Original file line number Diff line number Diff line change
@@ -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())
}
}
2 changes: 2 additions & 0 deletions gradle/libs.versions.toml
Original file line number Diff line number Diff line change
Expand Up @@ -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"

Expand All @@ -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" }

Expand Down
2 changes: 2 additions & 0 deletions settings.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -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")
Expand Down