Мониторинг и логирование Kotlin: как не пропустить баг в production

Онлайн-тренажер Python для начинающих

Изучайте Python легко и без перегрузки теорией. Решайте практические задачи с автоматической проверкой, получайте подсказки на русском языке и пишите код прямо в браузере — без необходимости что-либо устанавливать.

Начать курс

Почему «молчаливое» Kotlin-приложение — это мина замедленного действия

Представьте: вы запустили сервис на Kotlin, он работает как часы. Пользователи довольны, метрики зелёные. Но в один прекрасный день нагрузка скачет, latency растёт, а вы понятия не имеете, почему. Без логирования и мониторинга вы — капитан корабля без карты и радара. В этой статье мы разберём, как превратить ваше приложение из «чёрного ящика» в прозрачную систему, где каждый сбой виден невооружённым глазом.

Мы не будем копировать документацию. Вместо этого — реальные кейсы, готовые сниппеты и архитектурные решения, которые работают в production. Поехали.

Слона делим на части: логирование vs мониторинг

Прежде чем писать код, давайте договоримся о терминах. Логирование — это запись событий (ошибок, предупреждений, отладочной информации) в структурированном или текстовом виде. Мониторинг — сбор метрик (время ответа, количество запросов, загрузка CPU) и их визуализация. В идеале они работают в связке: логи нужны для глубокого анализа инцидента, метрики — для быстрого обнаружения аномалий.

Настройка логирования в Kotlin: от простого к сложному

Выбираем библиотеку: SLF4J + Logback — золотой стандарт

Kotlin отлично дружит с Java-экосистемой, поэтому SLF4J (фасад) и Logback (реализация) — выбор №1. Они легковесны, производительны и поддерживают асинхронное логирование.

Добавьте в build.gradle.kts:

implementation("ch.qos.logback:logback-classic:1.4.14")
implementation("io.github.microutils:kotlin-logging-jvm:3.0.5")

kotlin-logging — это тонкая обёртка над SLF4J, которая делает код чище. Сравните:

Без обёртки:

private val logger = LoggerFactory.getLogger(MyService::class.java)
logger.info("User {} logged in", userId)

С kotlin-logging:

private val logger = KotlinLogging.logger {}
logger.info { "User $userId logged in" }

Ленивое вычисление строк — бонус для производительности: строка соберётся только если уровень логирования включён.

Конфигурация Logback: структурированные логи — must have

Создайте logback.xml в src/main/resources:

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

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

Мы используем LogstashEncoder — он превращает логи в JSON. Это критически важно, если вы отправляете логи в Elasticsearch, Loki или любой другой агрегатор. JSON-логи легко парсятся, фильтруются и анализируются.

Не забудьте добавить зависимость:

implementation("net.logstash.logback:logstash-logback-encoder:7.4")

Пишем осмысленные логи: контекст решает всё

Логи вида «Error occurred» бесполезны. Добавляйте MDC (Mapped Diagnostic Context) — это как теги для ваших логов.

import org.slf4j.MDC

class OrderService {
    private val logger = KotlinLogging.logger {}

    fun processOrder(orderId: String, userId: String) {
        MDC.put("orderId", orderId)
        MDC.put("userId", userId)
        try {
            logger.info { "Processing order" }
            // бизнес-логика
        } catch (e: Exception) {
            logger.error(e) { "Order processing failed" }
        } finally {
            MDC.clear()
        }
    }
}

Теперь в агрегаторе логов вы сможете найти все записи по конкретному заказу или пользователю за секунду.

Мониторинг Kotlin-приложений: собираем метрики с Micrometer

Почему Micrometer?

Micrometer — это фасад для метрик, как SLF4J для логов. Он поддерживает все популярные бэкенды: Prometheus, Datadog, Graphite, New Relic. Вы пишете код один раз, а метрики отправляются куда угодно.

Подключите в build.gradle.kts:

implementation("io.micrometer:micrometer-core:1.12.0")
implementation("io.micrometer:micrometer-registry-prometheus:1.12.0")

Счётчики, таймеры и гистограммы на практике

Рассмотрим три самых полезных типа метрик.

1. Счётчик (Counter) — сколько раз что-то произошло.

class PaymentService(private val meterRegistry: MeterRegistry) {
    private val paymentCounter = Counter.builder("payments.total")
        .description("Total number of payments")
        .register(meterRegistry)

    fun processPayment(amount: BigDecimal) {
        paymentCounter.increment()
        // логика оплаты
    }
}

2. Таймер (Timer) — измеряет время выполнения.

class ExternalApiClient(private val meterRegistry: MeterRegistry) {
    private val apiTimer = Timer.builder("external.api.latency")
        .description("API call latency")
        .publishPercentiles(0.5, 0.95, 0.99)
        .register(meterRegistry)

    fun fetchData(): String {
        return apiTimer.recordCallable {
            // HTTP-запрос
            "data"
        }
    }
}

3. Гистограмма (DistributionSummary) — распределение значений (размер ответа, количество элементов и т.д.).

val responseSize = DistributionSummary.builder("http.response.size")
    .baseUnit("bytes")
    .publishPercentiles(0.5, 0.95)
    .register(meterRegistry)

Экспортируем метрики в Prometheus

Добавьте эндпоинт /actuator/prometheus с помощью Spring Boot Actuator или вручную:

import io.ktor.server.routing.*
import io.ktor.server.response.*
import io.micrometer.prometheus.PrometheusMeterRegistry

fun Route.prometheusEndpoint(registry: PrometheusMeterRegistry) {
    get("/metrics") {
        call.respondText(registry.scrape())
    }
}

В Ktor это выглядит естественно. В Spring Boot — просто включите management.endpoints.web.exposure.include=prometheus в application.yml.

Advanced техники: корреляция логов и метрик

Самая частая проблема — вы видите всплеск ошибок на графике, но не знаете, какие именно логи к нему привели. Решение: единый идентификатор запроса (traceId).

Используйте OpenTelemetry

Блоги

Рекомендаций по книгам