Skip to content

日志:Kermit

源:Kermit Documentation

Kermit 是专为 Kotlin Multiplatform 设计的轻量级日志库,提供统一的日志 API,自动适配各平台的原生日志系统。

基础配置

依赖配置

toml
[versions]
kermit = "2.0.4"

[libraries]
kermit = { module = "co.touchlab:kermit", version.ref = "kermit" }
kermit-simple = { module = "co.touchlab:kermit-simple", version.ref = "kermit" }
kotlin
kotlin {
    sourceSets {
        commonMain.dependencies {
            implementation(libs.kermit)
        }
    }
}

最新版本查询:https://github.com/touchlab/Kermit/releases

基础使用

kotlin
// commonMain
import co.touchlab.kermit.Logger

class UserRepository {
    // 创建 Logger 实例
    private val logger = Logger.withTag("UserRepository")
    
    suspend fun getUser(id: String): User {
        logger.d { "Fetching user: $id" }
        
        return try {
            val user = api.fetchUser(id)
            logger.i { "User fetched successfully: ${user.name}" }
            user
        } catch (e: Exception) {
            logger.e("Failed to fetch user", e)
            throw e
        }
    }
}

// 使用默认 Logger
fun someFunction() {
    Logger.v { "Verbose message" }      // Verbose
    Logger.d { "Debug message" }        // Debug
    Logger.i { "Info message" }         // Info
    Logger.w { "Warning message" }      // Warning
    Logger.e { "Error message" }        // Error
    Logger.a { "Assert message" }       // Assert
    
    // 带异常
    Logger.e("Error occurred", exception)
}

日志级别

级别方法用途
Verbosev()详细调试信息
Debugd()调试信息
Infoi()一般信息
Warnw()警告信息
Errore()错误信息
Asserta()断言失败

平台适配

各平台自动使用原生日志系统:

平台日志系统
AndroidLogcat (android.util.Log)
iOSNSLog / os_log
JVMprintln 或 SLF4J
JSconsole.log

平台特定日志

kotlin
// androidMain
import android.util.Log

// Kermit 自动使用 Logcat
// 输出:D/UserRepository: Fetching user: 123

// iosMain
import platform.Foundation.NSLog

// Kermit 自动使用 NSLog
// 输出:[UserRepository] Fetching user: 123

高级配置

自定义 LogWriter

kotlin
import co.touchlab.kermit.LogWriter
import co.touchlab.kermit.Severity

// 文件日志
class FileLogWriter(private val filePath: String) : LogWriter() {
    override fun log(
        severity: Severity,
        message: String,
        tag: String,
        throwable: Throwable?
    ) {
        val logMessage = buildString {
            append("[${severity.name}]")
            append(" [$tag]")
            append(" $message")
            throwable?.let { append("\n${it.stackTraceToString()}") }
        }
        
        // 写入文件
        appendToFile(filePath, logMessage)
    }
}

// 网络日志
class RemoteLogWriter(private val apiUrl: String) : LogWriter() {
    override fun log(
        severity: Severity,
        message: String,
        tag: String,
        throwable: Throwable?
    ) {
        if (severity >= Severity.Error) {
            // 仅上报 Error 及以上级别
            sendToServer(apiUrl, severity, message, tag, throwable)
        }
    }
}

配置全局 Logger

kotlin
import co.touchlab.kermit.Logger
import co.touchlab.kermit.StaticConfig
import co.touchlab.kermit.platformLogWriter

// 设置最小日志级别
val config = StaticConfig(
    minSeverity = Severity.Info,  // 仅输出 Info 及以上
    logWriterList = listOf(
        platformLogWriter(),      // 平台原生日志
        FileLogWriter("app.log"), // 文件日志
        RemoteLogWriter("https://api.example.com/logs")  // 远程日志
    )
)

// 设置为默认配置
Logger.setLogWriters(config.logWriterList)
Logger.setMinSeverity(config.minSeverity)

// 或创建自定义 Logger 实例
val customLogger = Logger(config, "MyTag")

条件日志

kotlin
// 仅在 Debug 模式输出
val logger = Logger(
    config = StaticConfig(
        minSeverity = if (BuildConfig.DEBUG) {
            Severity.Verbose
        } else {
            Severity.Warn
        }
    )
)

结构化日志

使用 Lambda

kotlin
// ✅ 推荐:使用 lambda(仅在需要时计算)
logger.d { "User: ${user.name}, Age: ${user.age}" }

// ❌ 不推荐:直接字符串拼接(始终计算)
logger.d("User: ${user.name}, Age: ${user.age}")

上下文信息

kotlin
class NetworkLogger(private val baseLogger: Logger) {
    fun logRequest(method: String, url: String, headers: Map<String, String>) {
        baseLogger.d {
            buildString {
                appendLine("=== HTTP Request ===")
                appendLine("Method: $method")
                appendLine("URL: $url")
                appendLine("Headers:")
                headers.forEach { (k, v) ->
                    appendLine("  $k: $v")
                }
            }
        }
    }
    
    fun logResponse(statusCode: Int, body: String) {
        baseLogger.i {
            buildString {
                appendLine("=== HTTP Response ===")
                appendLine("Status: $statusCode")
                appendLine("Body: ${body.take(200)}...")
            }
        }
    }
}

崩溃报告集成

Crashlytics 集成

kotlin
// androidMain
import com.google.firebase.crashlytics.ktx.crashlytics
import com.google.firebase.ktx.Firebase

class CrashlyticsLogWriter : LogWriter() {
    override fun log(
        severity: Severity,
        message: String,
        tag: String,
        throwable: Throwable?
    ) {
        when (severity) {
            Severity.Error, Severity.Assert -> {
                Firebase.crashlytics.log("[$tag] $message")
                throwable?.let {
                    Firebase.crashlytics.recordException(it)
                }
            }
            else -> {
                // 仅记录
                Firebase.crashlytics.log("[$tag] $message")
            }
        }
    }
}

// 配置
Logger.setLogWriters(
    platformLogWriter(),
    CrashlyticsLogWriter()
)

测试支持

测试日志捕获

kotlin
// commonTest
class TestLogWriter : LogWriter() {
    val logs = mutableListOf<String>()
    
    override fun log(
        severity: Severity,
        message: String,
        tag: String,
        throwable: Throwable?
    ) {
        logs.add("[$severity] [$tag] $message")
    }
    
    fun clear() {
        logs.clear()
    }
}

class UserRepositoryTest {
    private val testLogWriter = TestLogWriter()
    private val logger = Logger(
        config = StaticConfig(logWriterList = listOf(testLogWriter)),
        tag = "Test"
    )
    
    @Test
    fun testLogging() {
        logger.d { "Test message" }
        
        assertTrue(testLogWriter.logs.any { it.contains("Test message") })
    }
}

最佳实践

✅ 实践 1:为每个类创建独立 Logger

kotlin
// ✅ 推荐:每个类有自己的 tag
class UserRepository {
    private val logger = Logger.withTag("UserRepository")
}

class PostRepository {
    private val logger = Logger.withTag("PostRepository")
}

// ❌ 不推荐:共享全局 Logger
object AppLogger {
    val instance = Logger.withTag("App")
}

✅ 实践 2:使用 Lambda 延迟计算

kotlin
// ✅ 推荐:仅在日志级别满足时才计算
logger.d { "Expensive operation: ${expensiveCalculation()}" }

// ❌ 不推荐:总是执行计算
logger.d("Expensive operation: ${expensiveCalculation()}")

✅ 实践 3:生产环境降低日志级别

kotlin
// 根据构建类型配置
val logger = Logger(
    config = StaticConfig(
        minSeverity = when {
            isDebugBuild() -> Severity.Verbose
            isStagingBuild() -> Severity.Debug
            else -> Severity.Warn  // 生产环境只记录警告和错误
        }
    )
)

✅ 实践 4:敏感信息脱敏

kotlin
class SecureLogger(private val baseLogger: Logger) {
    fun logUser(user: User) {
        baseLogger.d {
            "User: id=${user.id}, email=${maskEmail(user.email)}"
        }
    }
    
    private fun maskEmail(email: String): String {
        val parts = email.split("@")
        return "${parts[0].take(2)}***@${parts.getOrNull(1)}"
    }
}

❌ 避免在循环中记录过多日志

kotlin
// ❌ 避免
list.forEach { item ->
    logger.d { "Processing item: $item" }  // 可能输出成千上万条
}

// ✅ 推荐
logger.d { "Processing ${list.size} items" }
list.forEach { item ->
    // 处理逻辑
}
logger.d { "Finished processing" }

Kermit vs 其他日志方案

特性KermitNapierkotlin-logging
KMP 支持✅ 完整✅ 完整⚠️ JVM 主导
平台自动适配
Lambda 支持
Tag 支持⚠️ 有限
自定义 Writer✅ 简单✅ 复杂
文件大小

常见配置示例

开发环境配置

kotlin
val devConfig = StaticConfig(
    minSeverity = Severity.Verbose,
    logWriterList = listOf(
        platformLogWriter(),
        FileLogWriter("debug.log")
    )
)

生产环境配置

kotlin
val prodConfig = StaticConfig(
    minSeverity = Severity.Error,
    logWriterList = listOf(
        CrashlyticsLogWriter(),
        RemoteLogWriter("https://api.example.com/logs")
    )
)

Kermit 提供了轻量级、跨平台的日志解决方案,自动适配各平台的原生日志系统,是 KMP 项目的理想选择。