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'importklog.klogclassMyClass {
privateval log = klog() // Simple extension functionfundoWork() {
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")
}
}
}importklog.klogval 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")importklog.klogimportklog.KLoggersimportklog.WithLoggingimportklog.KLoggerHolder// Extension function (recommended)classMyClass {
privateval log = klog()
}
// Explicit factory callclassMyClass {
privateval log =KLoggers.logger(this)
}
// Delegation patternclassMyClass : WithLogging by KLoggerHolder() {
funwork() { log.info("Using delegated logger") }
}
// String-based logger nameval 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:
importklog.KLoggerRegistryimportklog.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)classFileLogWriter(privatevalfile:File) : LogWriter {
overridefunwrite(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]