A minimalistic Kotlin Multiplatform logging library providing unified API across JVM, JavaScript, and Native platforms.
Philosophy: klog is a "dumb box" that just works. Zero dependencies, zero configuration, zero complexity. Out of the box it simply uses println() on all platforms with lambda support for performance. You can easily swap the output mechanism if needed, but the defaults are intentionally simple and fast.
| Platform | Implementation | Output |
|---|---|---|
| JVM | println() | Standard output (simple and fast) |
| JavaScript | Console API | Browser console / Node.js console |
| Native | println() | Standard output (macOS, Linux, Windows) |
The "dumb box" approach:
- No external dependencies (not even SLF4J!)
- No configuration files or complex setup
- Just
println()by default - works everywhere - Lazy lambda evaluation for performance:
log.debug { "expensive $calculation" } - Easy customization through simple writer interface when needed
Native targets:
macosX64- Intel MacmacosArm64- Apple Silicon Mac- More platforms coming soon
klog supports Java 8+ to ensure compatibility with the widest range of production environments. 29% of production applications still use Java 8 (New Relic 2024), representing a significant portion of the Java ecosystem that deserves first-class support.
Tested on: Java 8, 11, 17, 21 with comprehensive CI/CD pipeline.
Use https://jitpack.io repository
allprojects {
repositories {
...
maven { url 'https://jitpack.io' }
}
}
implementation 'com.github.lewik.klog:klog-metadata:2.1.0'import klog.klog
class MyClass {
private val log = klog() // Simple extension function
fun doWork() {
log.info("Starting work")
log.debug { "Lazy evaluation: expensive calculation only if debug enabled" }
try {
// ... some work
} catch (e: Exception) {
log.error(e, "Work failed")
}
}
}import klog.klog
val log = klog()
// Basic logging
log.trace("trace message")
log.debug("debug message")
log.info("info message")
log.warn("warning message")
log.error("error message")
// Lazy evaluation (only computed if level enabled)
log.debug { "Expensive calculation: ${expensiveFunction()}" }
// With exceptions
log.error(exception, "Something went wrong")
log.error(exception) { "Error in ${getCurrentContext()}" }
// Shorthand (defaults to info level)
log("Quick info message")import klog.klog
import klog.KLoggers
import klog.WithLogging
import klog.KLoggerHolder
// Extension function (recommended)
class MyClass {
private val log = klog()
}
// Explicit factory call
class MyClass {
private val log = KLoggers.logger(this)
}
// Delegation pattern
class MyClass : WithLogging by KLoggerHolder() {
fun work() {
log.info("Using delegated logger")
}
}
// String-based logger name
val log = KLoggers.logger("CustomName")
The "dumb box" philosophy means klog just works out of the box with println(), but you can easily customize the output when needed:
import klog.KLoggerRegistry
import klog.LogWriter
// Lambda syntax (simple cases)
KLoggerRegistry.currentWriter = LogWriter { level, tag, message, throwable ->
writeToFile("$level [$tag]: $message")
if (throwable != null) {
writeToFile(throwable.stackTraceToString())
}
}
// Class syntax (complex cases)
class FileLogWriter(private val file: File) : LogWriter {
override fun write(level: LogLevel, tag: String, message: String, throwable: Throwable?) {
file.appendText("$level [$tag]: $message\n")
throwable?.printStackTrace()
}
}
KLoggerRegistry.currentWriter = FileLogWriter(File("app.log"))Why this works:
- Default behavior: simple
println()- no setup needed - Custom behavior: swap the writer when you need something different
- No complex configuration - just a single function to override
This project is actively developed with assistance from Claude Code AI assistant.
- code at [https://github.com/shafirov/klogging]
- code at [https://github.com/MicroUtils/kotlin-logging]
- and discussion at [http://stackoverflow.com/questions/34416869/idiomatic-way-of-logging-in-kotlin]