· 4 min read Posted by Paul Hawke
The many faces of Kermit
Kermit 2.0 was released recently and with that came a reasonably large change under the hood compared with the 1.x line of code: To better support usage from non-Kotlin code, where default parameters on methods dont make it through the translation process (javascript, Swift, etc), the core and the public API layers of Kermit were split into two so that a “simple” version of the API could be created for the non-Kotlin users.
But why stop there? If the user facing API can drop default parameters, and things still work, what else could you do with it? How far can we push the public Kermit API to bridge into other loggers to better support migrating large (legacy) codebases toward KMP?
In the current Halloween season I have to ask “Who’s afraid of a headless kermit?”
Timber
Timber has been my “go to” logging library in Android for years. In that time I’ve implemented the Timber logging Tree class to write logs to local databases, push them out to Crashlytics or to other cloud-based logging systems.
class CustomLoggingTree : Timber.Tree() {
override fun log(priority: Int, tag: String?, message: String, t: Throwable?) {
// Write the log
}
}
This class aligns pretty closely with Kermit - I can skip calling the public Logger.i()
style methods and simply write directly to the kermit-core BaseLogger
. My Timber Tree then effectively replaces the whole of the public Kermit API to bridge between a legacy codebase and my KMP logging system. Log statements in the wider app are piped through to the exact same log writers as are handling commonMain
in the KMP module.
The only issue was mapping between Timber’s integer-based logging levels and Kermit’s Severity
private fun Int.asSeverity() = when (this) {
Log.ASSERT -> Severity.Assert
Log.ERROR -> Severity.Error
Log.WARN -> Severity.Warn
Log.INFO -> Severity.Info
Log.DEBUG -> Severity.Debug
Log.VERBOSE -> Severity.Verbose
else -> Severity.Verbose
}
Full implementation of the Timber / Kermit integration along with a very bare-bones example app are available on Github if you’re interested in seeing more.
SLF4J
SLF4J is a facade - a unifying API over the myriad of logging libraries available in the JVM ecosystem. One of its defining features is its ability to auto-detect the chosen logging implementation at runtime just by including the dependency on the classpath.
Integration with Timber was relatively easy thanks to the similarity of the Tree and BaseLogger classes. SLF4J integration meant cracking the auto-detect mechanism, and finding the natural integration point to call into Kermit’s BaseLogger
which turned out to follow a similar pattern to Timber in the end - implement AbstractLogger
and map SLF4J log levels over into Kermit Severity
and call through to our own logger.
class Slf4jKermitLogger(private val name: String, config: LoggerConfig) : AbstractLogger() {
private val logger = BaseLogger(config)
override fun getName(): String = "slf4j-over-kermit"
override fun handleNormalizedLoggingCall(
level: Level?,
marker: Marker?,
messagePattern: String?,
arguments: Array<out Any>?,
throwable: Throwable?
) {
val severity = when (level) {
ERROR -> Severity.Error
WARN -> Severity.Warn
INFO -> Severity.Info
DEBUG -> Severity.Debug
else -> Severity.Verbose
}
// ...
}
}
SLF4J manages enabling logging levels a little differently to Kermit (methods to ask isTraceEnabled()
for instance) but a working SLF4J integration just meant more boilerplate integration methods than with Timber.
Full implementation of the SLF4J / Kermit integration along with a very bare-bones example app are available on Github if you’re interested in seeing SLF4J logging API integrated with Kermit.
jvmMain
- if you get used to structuring your projects along KMP lines, it will make a full KMP adoption that much easier in the future.What’s Next?
Kermit 2.0 has such a flexible API that integration with existing logging, in a legacy codebase, was just a matter of working with directly kermit-core
enabling unified logging between code in commonMain
and the existing application.
At present, the Timber and SLF4J integrations are just a showcase of what’s possible with Kermit. Read here about another way Kermit 2.0’s flixible API can be used to customize your logging experience. Let me know if you would like to see them released as full libraries (upvote the Github issue), drop an issue on my repo (or Kermit’s repo) if you have other Kermit integration / enhancement ideas and remember that PR’s are also welcome!