Sharing something I've been building - *KMP Logger...
# feed
s
Sharing something I've been building - KMP Logger, a lightweight, structured logging library for Kotlin Multiplatform. One dependency, one logging API across Android, iOS, macOS, JVM, JS (Node & Browser), Wasm/JS, Linux, and MinGW - each writing to its platform-native output (Logcat, NSLog, console, stdout). What it gives you: • Two APIs : quick
Log.d { }
for debugging, structured
Logger
with typed key-value attributes for production • Coroutine-aware context :
withLogContext { }
propagates
LogContext
safely across suspension points and thread hops (optional
logger-coroutines
module) • Pluggable pipeline : per-tag level overrides, multiple sinks, custom formatters (Default / JSON / Compact / Pretty) • Lazy message evaluation, real epoch-ms timestamps, thread names •
TestSink
for asserting log output in unit tests • Extensible
LogSink
: ship logs to Crashlytics, a log aggregator, a file, anywhere Install (Maven Central):
Copy code
commonMain.dependencies {
    implementation("io.github.shivathapaa:logger:1.5.0")
    implementation("io.github.shivathapaa:logger-coroutines:1.5.0") // optional, coroutine context
}
Docs include real-world patterns - a multiplatform Crashlytics sink (expect/actual) and a secret-sanitizing sink: github.com/shivathapaa/KMP-Logger Feedback and issues are very welcome.
yay 1
s
My 2 cents: I don't like
Log.i()
as this is a clean code violation in my perspective. It should be
<http://Logger.info|Logger.info>()
as known from SLF4J.
s
Thanks, appreciate the feedback! Just to clarify, the SLF4J-style API is actually the primary one and is already there:
LoggerFactory.get("Auth").info { "..." }
It supports full-word levels (
verbose
,
debug
,
info
,
warn
,
error
,
fatal
), lazy message lambdas, structured attributes, and context.
Log.i()
is just a lightweight, optional facade that mirrors
android.util.Log
(`v`/`d`/`i`/`w`/`e`) for quick debugging. Both APIs go through the same pipeline (filtering, sinks, context, etc.), so it's simply a different entry point, not a separate logging system. So if you prefer the SLF4J style,
<http://logger.info|logger.info> { ... }
has you fully covered.
Log.*
is there purely as a convenience for those who prefer the Android-style API.
👍 1
✅ 1
a
When you build another logging library, the first question is "Why do you need yet another logging library?". The second: "How will it work if different part of the application uses different logging library. Without those answers, the value is hard to estimate.
☝🏻 1
☝️ 1
s
Fair questions - here's the straight answer to both. Why another one? Not because the ecosystem has a hole in it. There are solid KMP loggers already, and if one fits your app, use it. I built this for my own KMP app around a specific constraint, and published it because the constraint isn't unusual. I wanted structured context attached to log events -
requestId
,
userId
, that kind of thing - without making the logging API
suspend
. Ambient context in KMP generally leans on thread state, and that only really works on JVM/Android:
ThreadContextElement
doesn't exist on Native or JS, so nothing reinstalls the context when a coroutine resumes, and thread pools reuse threads. So the context is attached to the logger object instead:
Copy code
val log = LoggerFactory.get("Api").withContext("requestId" to id)
log.info { "Starting" }     // carries requestId - any platform, any dispatcher
It's a field on the logger, so there's no thread-local to lose and nothing to reinstall on resumption. Correct on Native and JS, from ordinary non-suspending code. Around that:
attributes: Map<String, Any?>
and a
LogContext
live on the event itself, so sinks consume them as data instead of parsing them back out of a message string; sinks and formatters are pluggable; and the core artifact has exactly one dependency, kotlin-stdlib. That's the whole pitch. It's young and I'm the only maintainer - worth knowing when you weigh it. Mixed logging libraries? This is the sharper question, and the honest answer is that no KMP logger solves it today, mine included. There's no SLF4J for KMP - no cross-platform service loader, so no facade and no convergence point on Native/JS/Wasm. Every KMP logger is a leaf, not a hub. Adopting mine doesn't unify your logging: your own code goes through it, and your dependencies' logs keep going wherever they already went. I ship no bridges today. What it does give you is a seam.
LogSink
receives the whole
LogEvent
- level, logger, message, throwable, attributes, context, thread, timestamp - so you can route its output into whatever pipeline you already run (e.g. a JVM sink mapping attributes and context onto SLF4J's MDC), and map a foreign SDK's events in through
Log.*
. That's glue you write per library, per target. I'd rather say that plainly than pretend the fragmentation goes away. Both are the right questions to ask - thanks for pushing on it.