
Lightweight and extensible logging library with a familiar API, supporting multiple log levels, tagged and scoped logging, and customizable output destinations. Features thread-safe and multiplatform capabilities.
A lightweight, Timber-style logging library for Kotlin Multiplatform. Plant trees to send logs wherever you want — the platform console, a crash reporter, your own UI — with tagging, scoped timing, and zero-cost lazy messages.
If you've used Timber, the API will feel immediately familiar.
{ } overloads skip string building when nothing is listening.dependencies {
implementation("org.kimplify:cedar-logging:0.3.0")
}Gradle metadata resolves the right variant per target automatically — cedar-logging is all you need in commonMain.
Framework export (recommended) — use Cedar directly from Swift:
// iOS app build.gradle.kts
listOf(iosX64(), iosArm64(), iosSimulatorArm64()).forEach { target ->
target.binaries.framework {
export("org.kimplify:cedar-logging:0.3.0")
baseName = "YourApp"
isStatic = true
}
}
// commonMain
api("org.kimplify:cedar-logging:0.3.0")CocoaPods:
pod 'CedarLogger', :git => 'https://github.com/Kimplify/Cedar-Logger.git', :tag => 'v0.3.0'Framework: CedarLogger · static · min iOS 12.0 · arm64 + x64 + simulator.
// Plant a tree once, at startup
Cedar.plant(platformLogTree()) // logs to Logcat / os_log / java.util.logging / console
Cedar.d("Debug message")
Cedar.i("App started")
Cedar.e(throwable, "Something failed")// Levels
Cedar.v("Verbose")
Cedar.d("Debug")
Cedar.i("Info")
Cedar.w("Warning")
Cedar.e("Error")
// With a throwable (throwable-first or message-first both work)
Cedar.e(exception, "Checkout failed")
Cedar.w("Retrying request", exception)
// Tags — group logs by area
Cedar.tag("Network").i("API call succeeded")
Cedar.tag("Database").d("Query executed")
// Lazy — the lambda runs only if a tree is planted
Cedar.d { "Expensive: ${dumpState()}" }
Cedar.tag("Net").e(throwable) { "Request failed for $url" }
// Scoped timing — logs start, end, and elapsed time
Cedar.tag("Startup").scope(message = "Warm cache").use {
warmCache()
}Lazy logging is the recommended default for hot paths and expensive messages: when no tree is planted, the
{ }lambda is never invoked, so the string is never built.
Routes to each platform's native facility (Logcat, os_log, java.util.logging, console) and is configurable:
Cedar.plant(platformLogTree {
iosSubsystem = "com.myapp.network" // groups logs in Console.app
iosCategory = "API"
androidMaxLogLength = 2000 // chunk long Logcat lines
jvmLoggerName = "MyApp.Logger"
enableEmojis = true
})Plain println output with level icons — handy for tests and simple JVM/JS targets:
Cedar.plant(ConsoleTree().withMinPriority(LogPriority.DEBUG))
// 🐞 DEBUG [Network] API call completedA tree is anything that implements LogTree.log. Route logs to a crash reporter, a file, an analytics pipeline, or your UI:
import org.kimplify.cedar.logging.LogPriority
import org.kimplify.cedar.logging.LogTree
class CrashReportingTree : LogTree {
// Only forward warnings and errors to the reporter
override fun isLoggable(tag: String?, priority: LogPriority): Boolean =
priority.isAtLeast(LogPriority.WARNING)
override fun log(priority: LogPriority, tag: String?, message: String, throwable: Throwable?) {
val label = tag ?: "App"
Reporter.log(priority.name, label, message)
if (throwable != null) Reporter.recordException(throwable)
}
}
Cedar.plant(CrashReportingTree())setup() and tearDown() hooks are available for trees that need initialization or cleanup.
Cedar.plant(ConsoleTree(), CrashReportingTree()) // plant several at once
Cedar.treeCount // how many are planted
val tree = ConsoleTree()
Cedar.plant(tree)
Cedar.uproot(tree) // remove one
Cedar.clearForest() // remove allAll planted trees receive every log; each decides via isLoggable what to keep.
A Compose Multiplatform sample (Android / iOS / JVM / wasmJs) shows live logging, tagging, scoped timing, and tree management:
./gradlew :sample:run # JVM desktopLogTree.log / isLoggable now take tag: String?; untagged logs pass null (previously the sentinel "AppLogger").Throwable — use the message-first overloads when there is no throwable.platformLogTree { } factory replaces the old PlatformLogTree() class.LogPriority.compareTo(Int) removed — compare against other LogPriority values (or use isAtLeast).Apache License, Version 2.0. See LICENSE.
Inspired by Timber, built on Kotlin Multiplatform.
Made with ❤️ for the Kotlin Multiplatform community
A lightweight, Timber-style logging library for Kotlin Multiplatform. Plant trees to send logs wherever you want — the platform console, a crash reporter, your own UI — with tagging, scoped timing, and zero-cost lazy messages.
If you've used Timber, the API will feel immediately familiar.
{ } overloads skip string building when nothing is listening.dependencies {
implementation("org.kimplify:cedar-logging:0.3.0")
}Gradle metadata resolves the right variant per target automatically — cedar-logging is all you need in commonMain.
Framework export (recommended) — use Cedar directly from Swift:
// iOS app build.gradle.kts
listOf(iosX64(), iosArm64(), iosSimulatorArm64()).forEach { target ->
target.binaries.framework {
export("org.kimplify:cedar-logging:0.3.0")
baseName = "YourApp"
isStatic = true
}
}
// commonMain
api("org.kimplify:cedar-logging:0.3.0")CocoaPods:
pod 'CedarLogger', :git => 'https://github.com/Kimplify/Cedar-Logger.git', :tag => 'v0.3.0'Framework: CedarLogger · static · min iOS 12.0 · arm64 + x64 + simulator.
// Plant a tree once, at startup
Cedar.plant(platformLogTree()) // logs to Logcat / os_log / java.util.logging / console
Cedar.d("Debug message")
Cedar.i("App started")
Cedar.e(throwable, "Something failed")// Levels
Cedar.v("Verbose")
Cedar.d("Debug")
Cedar.i("Info")
Cedar.w("Warning")
Cedar.e("Error")
// With a throwable (throwable-first or message-first both work)
Cedar.e(exception, "Checkout failed")
Cedar.w("Retrying request", exception)
// Tags — group logs by area
Cedar.tag("Network").i("API call succeeded")
Cedar.tag("Database").d("Query executed")
// Lazy — the lambda runs only if a tree is planted
Cedar.d { "Expensive: ${dumpState()}" }
Cedar.tag("Net").e(throwable) { "Request failed for $url" }
// Scoped timing — logs start, end, and elapsed time
Cedar.tag("Startup").scope(message = "Warm cache").use {
warmCache()
}Lazy logging is the recommended default for hot paths and expensive messages: when no tree is planted, the
{ }lambda is never invoked, so the string is never built.
Routes to each platform's native facility (Logcat, os_log, java.util.logging, console) and is configurable:
Cedar.plant(platformLogTree {
iosSubsystem = "com.myapp.network" // groups logs in Console.app
iosCategory = "API"
androidMaxLogLength = 2000 // chunk long Logcat lines
jvmLoggerName = "MyApp.Logger"
enableEmojis = true
})Plain println output with level icons — handy for tests and simple JVM/JS targets:
Cedar.plant(ConsoleTree().withMinPriority(LogPriority.DEBUG))
// 🐞 DEBUG [Network] API call completedA tree is anything that implements LogTree.log. Route logs to a crash reporter, a file, an analytics pipeline, or your UI:
import org.kimplify.cedar.logging.LogPriority
import org.kimplify.cedar.logging.LogTree
class CrashReportingTree : LogTree {
// Only forward warnings and errors to the reporter
override fun isLoggable(tag: String?, priority: LogPriority): Boolean =
priority.isAtLeast(LogPriority.WARNING)
override fun log(priority: LogPriority, tag: String?, message: String, throwable: Throwable?) {
val label = tag ?: "App"
Reporter.log(priority.name, label, message)
if (throwable != null) Reporter.recordException(throwable)
}
}
Cedar.plant(CrashReportingTree())setup() and tearDown() hooks are available for trees that need initialization or cleanup.
Cedar.plant(ConsoleTree(), CrashReportingTree()) // plant several at once
Cedar.treeCount // how many are planted
val tree = ConsoleTree()
Cedar.plant(tree)
Cedar.uproot(tree) // remove one
Cedar.clearForest() // remove allAll planted trees receive every log; each decides via isLoggable what to keep.
A Compose Multiplatform sample (Android / iOS / JVM / wasmJs) shows live logging, tagging, scoped timing, and tree management:
./gradlew :sample:run # JVM desktopLogTree.log / isLoggable now take tag: String?; untagged logs pass null (previously the sentinel "AppLogger").Throwable — use the message-first overloads when there is no throwable.platformLogTree { } factory replaces the old PlatformLogTree() class.LogPriority.compareTo(Int) removed — compare against other LogPriority values (or use isAtLeast).Apache License, Version 2.0. See LICENSE.
Inspired by Timber, built on Kotlin Multiplatform.
Made with ❤️ for the Kotlin Multiplatform community