Por que o logging em Kotlin é um problema (e como resolvê-lo)
Se você já programou em Java, conhece o ritual: no início de cada classe, você escreve private static final Logger logger = LoggerFactory.getLogger(MyClass.class);. Entediante, repetitivo, mas funciona. Ao migrar para Kotlin, você deseja algo mais elegante — e é aí que a coisa fica interessante.
No Stack Overflow, a pergunta "Idiomatic way of logging in Kotlin" acumulou milhares de visualizações e dezenas de discussões. Acontece que, mesmo no Kotlin moderno, não existe uma única maneira "correta" de fazer logging. Neste artigo, vamos analisar três abordagens principais, comparar seu desempenho e legibilidade, e escolher a forma idiomática de logging em Kotlin que servirá para 90% dos projetos.
O problema: Logger em Kotlin não é tão simples
Kotlin é uma linguagem que valoriza a concisão. Mas o SLF4J (a biblioteca de logging mais popular no mundo Java/Kotlin) exige a especificação explícita da classe para cada logger. Em Java, isso é resolvido com Lombok ou templates de IDE. Em Kotlin, com os recursos da própria linguagem.
Temos vários desafios pela frente:
- Não duplicar o nome da classe (isso é fonte de erros ao copiar)
- Não comprometer o desempenho (logging é uma operação frequente)
- Manter a legibilidade e concisão do código
- Suportar a inicialização preguiçosa do logger
Vamos ver quais soluções a comunidade Kotlin oferece.
Abordagem 1: "Bruta" — Companion Object
A maneira mais direta é declarar o logger no companion object da classe.
class UserService { companion object { private val logger = LoggerFactory.getLogger(UserService::class.java) }
fun createUser(name: String) { logger.info("Creating user: $name") }}Vantagens:
- Simplicidade e clareza
- Não requer dependências adicionais
Desvantagens:
- Indicação manual da classe — pode haver erros
- Muito código boilerplate
- Não funciona para interfaces e classes abstratas
Esta abordagem é a base da qual todas as outras partem. Funciona, mas não é a forma idiomática de logging em Kotlin — há boilerplate demais.
Abordagem 2: Funções Extension — Estilo Kotlin
Kotlin permite estender classes com funções extension. Por que não criar uma função que retorna um logger para qualquer classe?
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") }}Aqui usamos um parâmetro de tipo reified em uma função inline. Isso permite obter a classe em tempo de execução sem reflexão.
Vantagens:
- Conciso:
private val logger = logger() - Seguro: a classe é obtida automaticamente
- A função inline não cria objetos desnecessários
Desvantagens:
- O logger é inicializado imediatamente na criação do objeto, e não de forma preguiçosa
- Não funciona para funções de nível superior (fora de uma classe)
Isso já está mais próximo de uma solução idiomática. Mas podemos melhorar ainda mais.
Abordagem 3: Propriedades Delegadas — Elegância do Kotlin
Kotlin suporta propriedades delegadas. Podemos criar um delegate que inicializa o logger de forma preguiçosa e o armazena em cache.
class LoggerDelegate<in T : Any> : ReadOnlyProperty<T, Logger> { private var logger: Logger? = null
override fun getValue(thisRef: T, property: KPropert