Series Posts

· 4 min read Posted by Paul Hawke

The many faces of Kermit

Exploring how the flexible API of Kermit allows us to integrate with other platform logging frameworks.

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.

There is nothing wrong with all of your code living in a platform source-set like 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.

Touchlab has extensive experience introducing KMP to existing codebases. We can work with your team expand your shared code while maintaining clean bridges to your legacy code. Reach out here to learn more!

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!