Por qué el registro en Kotlin es un problema (y cómo solucionarlo)
Si alguna vez has escrito en Java, conoces este ritual: al inicio de cada clase escribes private static final Logger logger = LoggerFactory.getLogger(MyClass.class);. Aburrido, monótono, pero funciona. Al migrar a Kotlin, uno desea algo más elegante — y ahí comienza lo interesante.
En Stack Overflow, la pregunta "Idiomatic way of logging in Kotlin" ha acumulado miles de visitas y decenas de discusiones. Resulta que incluso en el Kotlin moderno no existe una única forma "correcta" de registrar. En este artículo analizaremos tres enfoques principales, compararemos su rendimiento y legibilidad, y elegiremos la forma idiomática de registro en Kotlin que sea adecuada para el 90% de los proyectos.
El problema: Logger en Kotlin no es tan simple
Kotlin es un lenguaje que valora la concisión. Pero SLF4J (la biblioteca de registro más popular en el mundo Java/Kotlin) requiere especificar explícitamente la clase para cada logger. En Java, esto se resuelve con Lombok o plantillas del IDE. En Kotlin, mediante las características del propio lenguaje.
Nos enfrentamos a varios desafíos:
- No duplicar el nombre de la clase (esto es fuente de errores al copiar)
- No sacrificar rendimiento (el registro es una operación frecuente)
- Mantener la legibilidad y concisión del código
- Soportar la inicialización perezosa del logger
Veamos qué soluciones ofrece la comunidad de Kotlin.
Enfoque 1: "Burdo" — Companion Object
La forma más directa es declarar el logger en el companion object de la clase.
class UserService { companion object { private val logger = LoggerFactory.getLogger(UserService::class.java) }
fun createUser(name: String) { logger.info("Creating user: $name") }}Ventajas:
- Simplicidad y claridad
- No requiere dependencias adicionales
Desventajas:
- Indicación manual de la clase — se puede cometer errores
- Mucho código repetitivo
- No es adecuado para interfaces y clases abstractas
Este enfoque es la base de la que parten todos los demás. Funciona, pero no es la forma idiomática de registro en Kotlin — hay demasiado código boilerplate.
Enfoque 2: Funciones de extensión — Estilo Kotlin
Kotlin permite extender clases mediante funciones de extensión. ¿Por qué no crear una función que devuelva un logger para cualquier clase?
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") }}Aquí usamos un parámetro de tipo reified en una función inline. Esto permite obtener la clase en tiempo de ejecución sin reflexión.
Ventajas:
- Conciso:
private val logger = logger() - Seguro: la clase se obtiene automáticamente
- La función inline no crea objetos adicionales
Desventajas:
- El logger se inicializa inmediatamente al crear el objeto, no de forma perezosa
- No es adecuado para funciones de nivel superior (fuera de una clase)
Esto ya se acerca más a una solución idiomática. Pero se puede mejorar aún más.
Enfoque 3: Propiedades delegadas — Elegancia de Kotlin
Kotlin soporta propiedades delegadas. Podemos crear un delegado que inicialice el logger de forma perezosa y lo almacene en caché.
class LoggerDelegate<in T : Any> : ReadOnlyProperty<T, Logger> { private var logger: Logger? = null
override fun getValue(thisRef: T, property: KPropert