1. Introduction
Logging is an essential part of any production-ready application. The popular choice in Java applications is slf4j – a simple facade for logging frameworks like logback or log4j. That is great, but they do not use all the great features that Kotlin provides us.
In this article, we’ll be looking at a Kotlin sfl4j wrapper named kotlin-logging.
2. Quickstart
Before going into details, here’s a quick usage guide.
2.1. Adding to Project
In order to add kotlin-logging to our project, we can use either Maven:
<dependency>
<groupId>io.github.microutils</groupId>
<artifactId>kotlin-logging-jvm</artifactId>
<version>3.0.5</version>
</dependency>
Make sure to get the latest version of kotlin-logging. Alternatively, we can use also Gradle:
implementation 'io.github.microutils:kotlin-logging-jvm:2.0.11'
Since kotlin-logging is a wrapper for slf4j, which itself is a wrapper for different logging backends, we’re going to need a configuration. In our case, we’ll be using logback, and we’ll add it using either Maven:
<dependency>
<groupId>ch.qos.logback</groupId>
<artifactId>logback-classic</artifactId>
<version>1.2.6</version>
</dependency>
Or Gradle:
implementation group: 'ch.qos.logback', name: 'logback-classic', version: '1.2.6'
In order to use log back, we’ll need some kind of configuration. Here’s a basic one:
src/main/resources/logback.xml
<configuration>
<appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
<encoder>
<pattern>%d{HH:mm:ss.SSS} [%thread] %-5level %logger{36} MDC=%X{user} - %msg%n</pattern>
</encoder>
</appender>
<root level="debug">
<appender-ref ref="STDOUT" />
</root>
</configuration>
There is more to logback configuration, but that should be enough for basic usage. Now that we’re all setup – let’s see how we can actually log something!
2.2. Basic Usage
To start using the logger, first, we need to import kotlin-logging:
import mu.KotlinLogging
And then declare logger as a top-level variable:
private val logger = KotlinLogging.logger {}
Once we’ve done that, we’re good to go. For example, running:
fun main() {
logger.trace { "This is trace log" }
logger.debug { "This is debug log" }
logger.info { "This is info log" }
logger.warn { "This is warn log" }
logger.error { "This is error log" }
}
Will print:
16:29:20.390 [main] DEBUG com.baeldung.logging.BasicUsageMain MDC= - This is debug log
16:29:20.393 [main] INFO com.baeldung.logging.BasicUsageMain MDC= - This is info log
16:29:20.394 [main] WARN com.baeldung.logging.BasicUsageMain MDC= - This is warn log
16:29:20.394 [main] ERROR com.baeldung.logging.BasicUsageMain MDC= - This is error log
And that should do for a quick-start guide. There are some unanswered questions, though. Why was the trace log not printed? Why are we using curly braces instead of parenthesis? Let’s answer those questions (and more!) in the next section.
3. The Details
Basic usage of the kotlin-logging library is simple, but knowledge of some of the details can be helpful in more advanced usage.
3.1. Brace Ourselves
Using curly braces instead of parentheses when logging with kotlin-logging is very useful. Let’s say that we have a big evaluation function:
val bigEvaluationFunction: (String) -> String = {
println("I am a VERY BIG evaluation: $it")
"Big Evaluation"
}
And two logger calls, first using parentheses:
logger.trace("Running big evaluation: ${bigEvaluationFunction("eagerly")}")
Second, using curly braces:
logger.trace { "Running big evaluation: ${bigEvaluationFunction("lazily")}" }
Assuming that we have the logging level above trace configured in our logback.xml (so, debug for example), both of those logs should do nothing, right? Wrong! While nothing will be logged, the trace call using parentheses will get evaluated eagerly and so the output of this code will be:
I am a VERY BIG evaluation: eagerly
To summarize, use parentheses if we want the log to be evaluated each time, and use curly braces if we want the log to be evaluated only if required.
3.2. Logger Naming
As we can see in the original example, we do not have to name the logger each time we create it. Kotlin-logging will do that for us, so this code:
val logger = KotlinLogging.logger {}
Is translated to code like:
val logger = LoggerFactory.getLogger("package.ClassName")
Usually, that’s enough, but in case we’d like to name our logger something very special, kotlin-logging allows us to do so:
private val logger = KotlinLogging.logger("The Name")
3.3. Using Underlying Logger
Kotlin-logging is a slf4j wrapper and sometimes we might want to access it directly. Kotlin-logging, of course, allows us to do that:
val slf4j: org.slf4j.Logger = logger.underlyingLogger
From here, we can do everything that we can do with slf4j logger.
3.4. Different Ways of Creating a Logger
Currently, the recommended way of creating a logger in kotlin-logging is the top-level declaration method:
private val staticLogger = KotlinLogging.logger {}
There are other (less recommended) ways in which we can create it. One of them, which was the previously recommended method, involves creating a companion object for the class in question and extending it with KLogging():
class ClassForImportantWork {
companion object: KLogging()
fun importantWork() {
logger.debug { "I'm logging via companion object" }
}
}
One other way that might be useful is simply extending the KLoggable interface and having logger declared as a field in the class:
class ClassForImportantWork: KLoggable {
override val logger: KLogger = logger()
fun importantWork() {
logger.debug { "I'm logging via non static member" }
}
}
The top-level declaration method should be sufficient for more use cases, but in case we encounter some older code, it might be useful to know the earlier methods as well.
3.5. Using Kotlin-Logging in Android
We can use kotlin-logging in Android applications. All we need to do is add slf4j-android:
implementation group: 'org.slf4j', name: 'slf4j-android', version: '1.7.32'
After adding that dependency, we can use kotlin-logging as shown in previous examples.
3.6. Kotlin-Logging and MDC
MDC plays an important role in improving application logging, but the question here is whether kotlin-logging supports it. The answer is, yes, of course. Note the fragment MDC=%X{user} in the original logback.xml.
By using withLoggingContext, we can populate the user parameter with data. So this code:
withLoggingContext("user" to "Baeldung") {
logger.info { "Log with MDC" }
}
Will produce:
16:34:36.486 [main] INFO com.baeldung.logging.LoggingContext MDC=Baeldung - Log with MDC
The user we provided in the logging context is passed to the logger automatically.
4. Conclusion
Kotlin-logging is a handy tool in making our logging code feel more like Kotlin and less like Java. We’ve learned how to configure and use it, as well as different details that might affect our experience with this library.
As always, code from this article is available over on GitHub.