Идиоматический способ логирования в Kotlin
Идиоматический подход: делегированные свойства и kotlin-logging
В Kotlin лучший способ — использовать библиотеку kotlin-logging в сочетании с делегированными свойствами. Это дает минимальный бойлерплейт и полную безопасность типов.
1. Подключение зависимости
Добавьте в build.gradle.kts:
implementation("io.github.microutils:kotlin-logging-jvm:3.0.5")
2. Базовое использование в классе
import mu.KotlinLogging
class MyService {
private val logger = KotlinLogging.logger {}
fun doSomething() {
logger.info { "Doing something" }
}
}
Здесь logger {} — это делегированное свойство, которое автоматически определяет имя класса. Никакого companion object не нужно.
3. Лямбда-выражения для производительности
Обратите внимание на синтаксис logger.info { ... } с фигурными скобками. Это лямбда, которая вычисляется только если уровень логирования включен. Аналог SLF4J logger.info("msg {}", expensive()):
logger.debug { "Сложное вычисление: ${expensiveCalculation()}" }
Если уровень DEBUG отключен, expensiveCalculation() не вызовется.
4. Для объектов (singleton)
В объектах Kotlin (например, для утилитных классов) логирование работает так же:
object MyUtils {
private val logger = KotlinLogging.logger {}
fun process() {
logger.warn { "Это предупреждение из объекта" }
}
}
5. Для функций верхнего уровня
Если вы используете функции вне классов, нужно указать имя вручную:
import mu.KLogger
import mu.KotlinLogging
private val logger: KLogger = KotlinLogging.logger("MyTopLevelFunctions")
fun topLevelFunction() {
logger.error { "Ошибка в функции верхнего уровня" }
}
6. Альтернатива: делегированное свойство через lazy
Если вы не хотите использовать kotlin-logging, можно сделать свой делегат:
import org.slf4j.LoggerFactory
class MyService {
private val logger by lazy { LoggerFactory.getLogger(this::class.java) }
fun doSomething() {
logger.info("Doing something")
}
}
Но это не дает лямбда-синтаксиса для отложенного вычисления.
7. Что выбрать?
- kotlin-logging — рекомендуемый идиоматический способ. Минимум кода, поддержка лямбд, автоматическое определение имени.
- Чистый SLF4J — если вы не хотите добавлять зависимость, но тогда нужно использовать
lazyилиcompanion object.
8. Полный пример с разными уровнями
import mu.KotlinLogging
class OrderService {
private val logger = KotlinLogging.logger {}
fun processOrder(orderId: String) {
logger.info { "Начало обработки заказа $orderId" }
try {
// бизнес-логика
logger.debug { "Детали заказа: $orderId" }
} catch (e: Exception) {
logger.error(e) { "Ошибка при обработке заказа $orderId" }
}
}
}
Итог
Используйте kotlin-logging с KotlinLogging.logger {}. Это коротко, производительно и по-настоящему идиоматично для Kotlin.
Альтернативный подход: inline-функции и макросы
Если вы хотите минимизировать зависимости и используете Kotlin 1.9+, можно создать свою утилиту на основе inline функций и reified типов. Это дает полный контроль без внешних библиотек.
import org.slf4j.LoggerFactory
inline fun T.logger(): org.slf4j.Logger =
LoggerFactory.getLogger(T::class.java)
class MyService {
fun doSomething() {
logger().info("Сообщение")
}
}
Недостаток: каждый вызов logger() создает новый экземпляр логгера (хотя SLF4J кэширует их внутри). Для повышения производительности можно кэшировать через lazy:
class MyService {
private val log by lazy { logger() }
fun doSomething() {
log.info("Сообщение")
}
}
Этот подход хорош, если вы не хотите добавлять kotlin-logging, но все равно получаете чистый код. Однако он не поддерживает лямбда-синтаксис для отложенного вычисления (нужно использовать isDebugEnabled вручную).