
Learn through the super-clean Baeldung Pro experience:
>> Membership and Baeldung Pro.
No ads, dark-mode and 6 months free of IntelliJ Idea Ultimate to start with.
Last updated: May 17, 2025
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 don’t use all the great features that Kotlin provides us.
In this tutorial, we’ll be looking at a Kotlin SFL4J wrapper named kotlin-logging.
Before going into details, here’s a quick usage guide.
To add kotlin-logging to our project, we can use Maven:
<dependency>
<groupId>io.github.oshai</groupId>
<artifactId>kotlin-logging-jvm</artifactId>
<version>7.0.3</version>
</dependency>
We can get the latest version of kotlin-logging.
Alternatively, we can use Gradle:
implementation 'io.github.oshai:kotlin-logging-jvm:5.1.0'
Since kotlin-logging depends on slf4j-api, which itself is a wrapper for different logging backends, we’re going to need a configuration. When we only need to log statements to standard output during development, we include org.slf4j:slf4j-simple. For production environments, however, we use Logback or Log4j2.
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.5.18</version>
</dependency>
Here’s how we add it in Gradle:
implementation group: 'ch.qos.logback', name: 'logback-classic', version: '1.5.18'
We’ll also need some basic Logback configuration:
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 set up, let’s see how we can log something!
To start using the logger, first, we need to import KotlinLogging:
import io.github.oshai.kotlinlogging.KotlinLogging
And then we declare logger as a top-level variable:
private val logger = KotlinLogging.logger {}
Once we’ve done that, we’re good to go:
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" }
}
Let’s see the output:
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 parentheses? Let’s find the answer to those questions (and more!) in the next section.
Basic usage of the kotlin-logging library is simple, but knowledge of some of the details can be helpful in more advanced usage.
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, we use parentheses if we want the log to be evaluated each time, and we use curly braces if we want the log to be evaluated only if required.
As we can see in the original example, we don’t have to name the logger each time we create it. Kotlin-logging does that for us:
val logger = KotlinLogging.logger {}
The above translates 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")
Kotlin-logging is a slf4j wrapper, and sometimes we might want to access it directly:
val slf4j: org.slf4j.Logger = logger.underlyingLogger
From here, we can do everything that we can do with the slf4j 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.
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 we saw earlier.
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. We can note the fragment MDC=%X{user} in the original logback.xml.
By using withLoggingContext(), we can populate the user parameter with data:
withLoggingContext("user" to "Baeldung") {
logger.info { "Log with MDC" }
}
Here’s the log output:
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.
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.