In Kotlin, as opposed to Java, there is no established standard when it comes to logging. JetBrains didn’t provide any opinionated way. But the community does not disappoint. There are few different approaches that allow developers to do it properly.
Use SLF4J – just like in Java
As we all know, Kotlin’s interoperability with Java is superb. Therefore we don’t need to think about logging in Kotlin for long and just stick to the good ol’ logging facade. Add SLF4J to the classpath (it’s available on Maven Central) and you can create logger with just one line of code:
class MyApp { private val logger = LoggerFactory.getLogger(this.javaClass.name) }
Unfortunately, there’s a drawback to the above snippet: putting this line in the body of a class creates a new logger on every initialization of the object. We can avoid that by moving it to the companion object:
class MyApp { companion object { private val logger = LoggerFactory.getLogger(MyApp::class.java) } }
This solves our issue with new logger each time a class initializes but looks kind of bothersome. Not only it is long but we also have to pass a class (or classname) as the method argument. And what if someone happens to misspell it? Inconsistent feedback from the application guaranteed.
Logging in Kotlin with lazy delegated properties
We can ensure correct class name by creating our own delegated property. Kotlin offers us the lazy() function that basically imitates the behaviour of a singleton class. On the first call of the lazy property, the lambda passed to the lazy() function is executed and the result remembered. Then every subsequent call returns the, already computed, result. Let’s see the implementation:
class MyApp { val logger by logger() } fun <R : Any> R.logger(): Lazy<Logger> { return lazy { LoggerFactory.getLogger(this.javaClass) } }
As you can see, the extension that returns a lazy function is declared on top-level. Because of this we have access to it from every place in our project.
If you want to use this approach also from companion object, you may see a need of adding another function. Calling javaClass on a companion object returns the enclosing class name with the $Companion suffix. There’s no easy way to obtain the actual class name or its reference because companion objects don’t carry anything like that. They are compiled to Java statics. If you want to get rid of the above suffix, simply remove it using a proper function:
fun <R : Any> R.logger(): Lazy<Logger> { return lazy { LoggerFactory.getLogger(getClassName(this.javaClass)) } } fun <T : Any> getClassName(clazz: Class<T>): String { return clazz.name.removeSuffix("\$Companion") }
Although you have to maintain some more of utility code, this looks pretty neat to me and I could use it in the production.
A wrapper of a facade – kotlin-logging
Both of the above approaches are a good solution when it comes to logging in Kotlin. But neither of them is Kotlin native.
Kotlin-logging is a lightweight and convenient library for logging. It’s a wrapper around SLF4J that adds a bunch of extensions in order to remove boilerplate. It also offers lazy evaluation.
After adding it to the classpath of your project (remember that you should also provide SLF4J), Kotlin-logging is ready to use:
class MyApp { fun logTest() { val logMessage = "message" logger.debug { "Debug $logMessage" } } companion object : KLogging() }
The above snippet creates a ready-to-use logger property underneath. It’s already equipped with the proper classname so you don’t have to worry about it. And the extension function checks if debug is enabled, yet another thing taken care of. Let’s execute the above snippet:
And it’s all we need. With the least possible hassle we’ve prepared a working setup.
If you want to avoid inheritance and companion objects, it allows to create a logger instance like this:
private val logger = KotlinLogging.logger {}
You can get this library from the Maven Central repository: kotlin-logging. The source code is available on GitHub.
Conclusion
With a little emphasis on kotlin-logging, in my opinion the above approaches of handling loggers in Kotlin are the best. Even though they are not official ways, you can’t go wrong with any of it. Maybe sometime in the future JetBrains will create a guide (or lib) regarding this issue. For now, we have to stick to what we have.
If you liked this post feel free to share it or follow me on Twitter: @a_mrszlk