OpenTelemetry — всё не так, как представляется

OpenTelemetry — всё не так, как представляется

Здравствуйте, я Евгений, инженер в сфере финтеха. Я проектирую масштабируемые системы с миллионами запросов, интегрирую их с разнообразными внешними сервисами и разворачиваю в Kubernetes. Кроме того, я преподаю Java и Spring Boot и помогаю студентам избегать чужих ошибок, опираясь на собственный опыт.

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

В этой статье я поделюсь нашим подходом: как мы выстраиваем наблюдаемость в распределённых системах и почему OpenTelemetry — это не просто инструменты, а философия, которой мы следуем.

Наблюдаемость как часть культуры, а не набор утилит

Распространённая ошибка: «добавим Micrometer, отправим метрики в Prometheus — и дело сделано». На практике этого недостаточно.

Часто наблюдаемость воспринимают как ещё одну зависимость в pom.xml, однако на самом деле это инженерная привычка, пронизывающая все этапы разработки и эксплуатации:

  • Логи нужны не только для отладки ошибок, но и для воссоздания последовательности бизнес-сценариев.
  • Метрики важны не только для дашбордов в Grafana, но и для понимания нагрузки и выявления узких мест.
  • Трассировки — это не игрушка SRE, а ключ к пониманию работы распределённой системы в реальном времени.

Если эти практики не встроены в повседневную работу, мы обречены натыкаться на одни и те же проблемы снова и снова.

OpenTelemetry служит основой нашего подхода к наблюдаемости. Это не просто библиотека для метрик, логов и трассировок, а единая концепция, лежащая в основе Micrometer, Prometheus, Grafana и Jaeger. Важно не просто подключить зависимость, а вплетать практики наблюдаемости в процессы разработки, проектирования и code review.

Травмирующий случай из практики

Представим упрощённую архитектуру платформы.

Упрощённая схема платформы

Бизнес: Клиенты не могут оформить заявку — срочно разберитесь!

Мониторинг: В одном сервисе поле называется applicationId, в другом — appId, в третьем — aId. Половина логов не содержит traceId. Регистр ошибок не единообразен: что-то уходит в WARN, что-то в ERROR. Форматы сообщений разбросаны. В Grafana все зелёное, а жалобы продолжаются…

Инженеры: У кого есть traceId? Что возвращает адаптер? В каком сервисе произошла ошибка? Почему нет stackTrace?

Спустя несколько часов…

Оказалось, из-за некорректного scope при интеграции возвращался 403. Найти источник помогла лишь частично настроенная трассировка в одном сервисе.

Задайте себе вопросы:

  • Кто вызвал сервис и сколько времени это заняло?
  • В каком месте цепочки произошёл сбой?
  • Как быстро мы найдём точку возникновения исключения?

Без согласованных сквозных идентификаторов и единых форматов логов и метрик мы утонем в хаосе.

Четыре столпа наблюдаемости

Основные принципы нашего подхода:

1. Сквозные идентификаторы и единые соглашения. Все события связываются через единый traceId и бизнес-ключи (applicationId, productId). Названия полей стандартизированы во всех сервисах.

2. Упорядоченный и централизованный формат хранения. Логи в JSON с обязательными полями: timestamp, component, event, traceId, applicationId, productId. Метрики и аудит выгружаются в DWH или через очередь сообщений.

3. Безопасность и консистентность. Маскировка и шифрование чувствительных данных. Контроль порядка событий в многопоточном и реактивном коде: «рваные» трассировки хуже их полного отсутствия.

4. Прозрачность для разработчиков. Наблюдаемость работает «из коробки». Разработчик пишет бизнес-логику, а все остальное на себя берут фильтры, аспекты и библиотеки.

Чек-лист для самопроверки

На этапе проектирования:

  • Сквозные идентификаторы и ключи согласованы заранее.
  • Форматы логов, метрик, аудита и DWH утверждены.
  • Маскирование или шифрование персональных данных настроено.
  • Тела запросов и ответов исключены из логов, где это нужно.
  • Логи, метрики, трассировки и аудит связаны сквозными ключами.

На этапе реализации и поддержки:

  • Единый формат логов во всех сервисах.
  • Минимизация overhead от инструментов мониторинга.
  • Тесты на корректность метрик.
  • Подключены централизованные хранилища (ELK, Loki, ClickHouse).
  • Настроены дашборды для основных и проблемных сценариев.

Я убеждён в превентивном подходе, а не в постоянной борьбе с последствиями.

Исторический пример: ручка насоса Джона Сноу

В 1854 году в Лондоне свирепствовала холера. Врачи того времени винят «дурной воздух», но Джон Сноу установил: эпицентром заражения стала колонка на Брод-стрит. Его превентивная мера — снятие рукояти насоса — сразу же остановила эпидемию и заложила основы современной эпидемиологии.

Пример упорядочения телеметрии в коде

Рассмотрим, как упростить и централизовать логирование через AOP.

Типичный «плохой» пример:

fun badRestExample(userId: String): String {
    logger.info("Calling external for $userId")
    return RestTemplate()
        .getForObject("https://ext/api/user/$userId", String::class.java) ?: ""
}
object LoggerHolder {
    val logger = KotlinLogging.logger {}
}

Улучшаем с помощью аспекта и аннотации:

@ObservedEvent(log = true, audit = true, metrics = true, tags = ["integration:RestTemplate"])
fun getUserData(userId: String): String {
    return restTemplate.getForObject("https://ext/api/user/$userId", String::class.java) ?: ""
}

При централизованном логировании запись приобретает единый вид:

{"timestamp":"2025-08-12T10:00:00Z","event":"getUserData.start","integration":"RestTemplate","userId":"123","traceId":"abc-123"}
{"timestamp":"2025-08-12T10:00:01Z","event":"getUserData.completed","integration":"RestTemplate","userId":"123","traceId":"abc-123"}

Ключевая цель — стандартизировать телеметрию, чтобы она не превращалась в беспорядок.

Простейшая мини-библиотека для «сквозного» наблюдения на основе AOP:

@Target(AnnotationTarget.FUNCTION)
@Retention(AnnotationRetention.RUNTIME)
annotation class ObservedEvent(
    val log: Boolean = true,
    val audit: Boolean = false,
    val metrics: Boolean = true,
    val tags: Array = []
)

Далее объединяем логику телеметрии внутри аспекта:

@Aspect
@Component
class ObservedEventAspect(private val observability: ObservabilityService) {

    @Around("@annotation(observed)")
    fun around(joinPoint: ProceedingJoinPoint, observed: ObservedEvent): Any? {
        val span = observability.startTrace(observed.tags)
        val timer = if (observed.metrics) observability.startTimer(joinPoint.signature.name) else null
        observability.logInfo("${joinPoint.signature.name}.start")

        return try {
            joinPoint.proceed()
        } catch (ex: Throwable) {
            observability.logInfo("${joinPoint.signature.name}.error", "error" to ex.message)
            throw ex
        } finally {
            if (observed.audit) observability.audit("${joinPoint.signature.name}.completed")
            timer?.let { observability.endTimer(it, joinPoint.signature.name) }
            observability.endTrace(span)
            observability.logInfo("${joinPoint.signature.name}.end")
        }
    }
}

Разработчик пишет чистый бизнес-код, а аспекты и библиотеки берут на себя всю телеметрию.

Выводы

Наблюдаемость — это не просто пункт из чек-листа, а неотъёмлемая часть инженерной культуры. OpenTelemetry даёт инструменты, но без правильно выстроенных процессов мы вновь столкнёмся с хаосом.

Основные выводы:

  • Наблюдаемость закладывается на этапе проектирования — чем раньше, тем дешевле исправлять.
  • Она должна быть прозрачной и удобной для всех разработчиков, независимо от стажа.
  • Стандартизация — единственный способ избежать хаоса. Начинайте с метрик.
  • Тесты нужны не только для кода, но и для метрик и логов.

Так в пятницу вечером вам не придётся охотиться за traceId в логах, а можно спокойно уйти домой.

Как в вашей компании организована проверка метрик и аудит?

 

Источник

Читайте также