Logging in Kotlin Applications: Best Practices and Tools

Online Python Trainer for Beginners

Learn Python easily without overwhelming theory. Solve practical tasks with automatic checking, get hints in Russian, and write code directly in your browser — no installation required.

Start Course

Logging in Kotlin Applications: Best Practices



Logging is not just about recording events, but a key tool for debugging, monitoring, and analyzing application performance. In the Kotlin ecosystem, thanks to full compatibility with Java, all popular libraries such as SLF4J and Logback are available. However, Kotlin offers its own idiomatic approaches that make code cleaner and safer. In this article, we will cover the best practices for logging in Kotlin: from choosing a library to configuring structured logs for production.



1. Choosing a Logging Library for Kotlin



The most popular stack for Kotlin applications is SLF4J (facade) + Logback (implementation). However, for Kotlin, it is recommended to use the kotlin-logging wrapper, which adds inline functions and avoids unnecessary overhead.



// build.gradle.ktsdependencies {    implementation("io.github.microutils:kotlin-logging:3.0.5")    implementation("ch.qos.logback:logback-classic:1.4.14")}


Advantages of kotlin-logging:

  • Lazy logger initialization — the logger is created only on first access.
  • Inline functions — the logging level check occurs at compile time, reducing overhead.
  • Support for suspend functions — correct operation in coroutines.


Example of logger declaration:



import mu.KotlinLogging

private val logger = KotlinLogging.logger {}


2. Correct Logging Levels



Using the correct level is the foundation of readable logs. In Kotlin, as in Java, 5 levels are available: TRACE, DEBUG, INFO, WARN, ERROR.



Recommendations:

  • TRACE — only for detailed debugging (e.g., method entry/exit).
  • DEBUG — information for the developer (variable values, object state).
  • INFO — business events (request processed, user authorized).
  • WARN — abnormal situations that do not break the application (connection retry).
  • ERROR — exceptions and failures requiring attention.


Example with different levels:



logger.debug { "Processing request: $requestId" }logger.info { "User ${user.name} successfully registered" }logger.warn { "Connection pool is 90% full, current size: $poolSize" }logger.error(ex) { "Error saving order ${orderId}" }


Important: In Kotlin, to pass an exception, use logger.error(throwable) { "message" }, not logger.error("message", throwable) — this is more idiomatic.



3. Structured Logging (JSON)



In modern microservice architectures, text logs are inconvenient for analysis. It is better to use structured logs in JSON format. This makes it easy to filter logs in ELK, Grafana, or Splunk.



Configuring Logback for JSON logs:



// logback.xml<configuration>    <appender name="JSON" class="ch.qos.logback.core.ConsoleAppender">        <encoder class="net.logstash.logback.encoder.LogstashEncoder"/>    </appender>

<root level="INFO"> <appender-ref ref="JSON"/> </root></configuration>


To add custom fields, use MDC (Mapped Diagnostic Context):



import org.slf4j.MDC

fun processOrder(orderId: String) { MDC.put("orderId", orderId) MDC.put("userId", currentUserId()) logger.info { "Starting order processing" } // ... logic MDC.clear()}

Blogs

Book Recommendations