Logging in Kotlin: Idiomatic Approach and Method Comparison

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

Why Logging in Kotlin is a Pain (and How to Fix It)



If you've ever written in Java, you know the ritual: at the beginning of each class you write private static final Logger logger = LoggerFactory.getLogger(MyClass.class);. Boring, repetitive, but it works. When switching to Kotlin, you want something more elegant — and that's where the fun begins.



On Stack Overflow, the question "Idiomatic way of logging in Kotlin" has garnered thousands of views and dozens of discussions. It turns out that even in modern Kotlin, there is no single "correct" way to log. In this article, we'll break down three main approaches, compare their performance and readability, and choose the idiomatic way of logging in Kotlin that will work for 90% of projects.



The Problem: Logger in Kotlin Isn't So Simple



Kotlin is a language that values conciseness. But SLF4J (the most popular logging library in the Java/Kotlin world) requires explicitly specifying the class for each logger. In Java, this is solved via Lombok or IDE templates. In Kotlin — through the language's own features.



We face several challenges:



  • Don't duplicate the class name (this is a source of errors when copying)
  • Don't pay a performance penalty (logging is a frequent operation)
  • Maintain code readability and conciseness
  • Support lazy initialization of the logger


Let's look at what solutions the Kotlin community offers.



Approach 1: "Blunt" — Companion Object



The most straightforward way is to declare the logger in the class's companion object.



class UserService {    companion object {        private val logger = LoggerFactory.getLogger(UserService::class.java)    }

fun createUser(name: String) { logger.info("Creating user: $name") }}


Pros:

  • Simplicity and clarity
  • No additional dependencies required


Cons:

  • Manual class specification — you can make a mistake
  • Lots of boilerplate code
  • Not suitable for interfaces and abstract classes


This approach is the baseline from which all others start. It works, but it's not the idiomatic way of logging in Kotlin — too much boilerplate.



Approach 2: Extension Functions — Kotlin Style



Kotlin allows extending classes via extension functions. Why not create a function that returns a logger for any class?



inline fun <reified T : Any> T.logger(): Logger {    return LoggerFactory.getLogger(T::class.java)}

class OrderService { private val logger = logger()

fun processOrder(id: String) { logger.debug("Processing order: $id") }}


Here we use a reified type parameter in an inline function. This allows us to get the class at runtime without reflection.



Pros:

  • Concise: private val logger = logger()
  • Safe: the class is taken automatically
  • Inline function doesn't create extra objects


Cons:

  • The logger is initialized immediately when the object is created, not lazily
  • Not suitable for top-level functions (outside a class)


This is already closer to an idiomatic solution. But we can do even better.



Approach 3: Delegated Properties — Kotlin Elegance



Kotlin supports delegated properties. We can create a delegate that lazily initializes the logger and caches it.



class LoggerDelegate<in T : Any> : ReadOnlyProperty<T, Logger> {    private var logger: Logger? = null

override fun getValue(thisRef: T, property: KPropert

Blogs

Book Recommendations