日志:Kermit
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)
}日志级别
| 级别 | 方法 | 用途 |
|---|---|---|
| Verbose | v() | 详细调试信息 |
| Debug | d() | 调试信息 |
| Info | i() | 一般信息 |
| Warn | w() | 警告信息 |
| Error | e() | 错误信息 |
| Assert | a() | 断言失败 |
平台适配
各平台自动使用原生日志系统:
| 平台 | 日志系统 |
|---|---|
| Android | Logcat (android.util.Log) |
| iOS | NSLog / os_log |
| JVM | println 或 SLF4J |
| JS | console.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 其他日志方案
| 特性 | Kermit | Napier | kotlin-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 项目的理想选择。