Привет! Меня зовут - Евгений, работаю в финтехе и проектирую системы, которые обрабатывают миллионы запросов, интегрируются с десятками внешних сервисов и живут в Kubernetes. А еще я преподаю Java/Spring Boot и рассказываю студентам, как не наступать на чужие грабли, а создавать свои и прыгать на них.

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

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

Почему наблюдаемость — это культура, а не тулза

Часто наблюдаемость воспринимают:
«Ща подключим Micrometer, закинем в Prometheus — и все будет». Нет, не будет.

Мы часто думаем про наблюдаемость как про «еще одну зависимость в pom.xml». В реальном мире наблюдаемость — это часть инженерной культуры, которая будет формировать наш быт везде, где мы будем работать:

  • Логи нужны не только для «посмотреть ошибку», но и чтобы можно было восстановить бизнес-сценарий.

  • Метрики важны не только для Grafana, но и чтобы команда понимала нагрузку и узкие места.

  • Трейсы — это не игрушка для SRE, а способ увидеть, что реально происходит в распределенной системе.

Если это не встроено в рутину — мы обречены прыгать на одни и те же грабли, каждый раз надеясь, что в этот раз будет иначе. Кто-то назвал бы это безумием...

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

Немного травмирующего опыта

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

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

Kibana:
...в одном сервисе поле applicationId, в другом appId, а в третьем aId...
...у половины логов нет traceId...
...кто-то пишет исключения в WARN, кто-то в ERROR

...кто-то в формате "Request with: $object", а кто-то "Call service".
...в Grafana все "зеленое", но бизнес жалуется...

Инженеры:
— А у вас видно что-то по этой заявке?
— Есть только время вызова, а у вас?
— А может исключение у адаптера?
— ...

A few hours later…

В итоге — 403 на интеграции из-за "scope", нашли в одном из приложений, где stackTrace не отливались.

Теперь зададим себе вопросы:

  • Кто вызвал кого и сколько это заняло?

  • Где именно разорвался процесс?

  • Как быстро мы найдем точку, где вызвано исключение?

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

Квартет наблюдаемости

Вот мои киты наблюдаемости — я собрал их, опираясь на собственный опыт.

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

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

Безопасность и порядок — маскируем и шифруем чувствительные данные. Следим за порядком событий в многопоточном и реактивном коде: рваные трейсы хуже, чем их отсутствие.

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

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


На стадии проектирования и аналитики:

  • Сквозные идентификаторы согласованы заранее.

  • Форматы для метрик, логов, аудита и DWH стандартизированы.

  • Маскирование или шифрование персональных данных включено.

  • Тела запросов и ответов исключены из логов, где это возможно и необходимо.

  • Логи, метрики, трейсы и аудит связаны через traceId или другие ключи.

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

  • Все логи в едином формате.

  • Минимизирован overhead от инструментов мониторинга (аспекты, маппинг, выгрузки).

  • Добавлены тесты на наличие метрик.

  • Подключено централизованное хранилище (ELK, Loki, ClickHouse).

  • Созданы дашборды для happy path и проблемных сценариев.

Я приверженец философии профилактики, а не борьбы за живучесть.

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

В 1854 году в Лондоне свирепствовала холера. Причина считалась неочевидной (все винили «дурной воздух»), а масштаб — почти привычным злом. 

Врач Джон Сноу вопреки общепринятому мнению выяснил, что источник заразы — водяная колонка на Брод-стрит. Его профилактическая мера — снять с насоса ручку — казалась современникам странной и недостаточной для столь крупной проблемы.

Такое простое действие не только немедленно остановило эпидемию в районе, но и положило начало современной эпидемиологии и системам санитарного контроля. Это классический пример того, как точечная и умная профилактика, основанная на поиске коренной причины, спасает жизни и огромные ресурсы, которые пришлось бы тратить на исправление последствий.

Примеры улучшения кодовой базы

На примере логирования покажу, как можно улучшить код-базу, централизовать отливку и в целом использовать инструменты observability более опрятно.

Есть типовой пример, как чаще всего используют средства наблюдаемости:

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

Как это можно сделать чище и централизованнее: выносим код для логирования как бы за скобки, используя AOP-принципы. Суть метода для нас стала чище и очевиднее:

@ObservedEvent(log = true, audit = true, metrics = true, tags = ["integration:RestTemplate"]) // ObservedEvent - метка для AOP логгера
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"}

Техническая реализация может быть любой, но целью должна быть стандартизация телеметрии. Без стандарта телеметрия превращается просто в растрату ресурсов и рабочего времени. 

На правах автора статьи накидаю простенький и широкий вариант реализации такого инструмента централизации.

Мини-библиотека Observability. Для меня самый простой и очевидный способ «сквозного» наблюдения — это 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<String> = []
)

Когда мы понимаем, что нам нужно и что хотим видеть в наблюдаемости, можем собрать обработку логов в одном месте: 

@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")
        }
    }
}

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

Важно, что во всех частных случаях реализации есть плюсы и минусы. Для AOP, например, есть много проблем с многопоточными обработками и производительностью при большом трафике. Тут не будет «серебряной пули».

Может быть много реализаций централизации — под специфику приложения или интеграции. Но ядро форматирования и поставки итогового текста в агрегирующую систему может быть единым.

Итог

Observability — это не галочка в чек-листе, а часть инженерной культуры. OpenTelemetry помогает, но если культура не выстроена — никто и ничто не спасет, будем обречены ходить по кругу и тонуть в рутине.

Мои выводы:

  • Наблюдаемость должна закладываться на этапе проектирования. Раньше подумаешь — дешевле исправишь.

  • Она должна быть прозрачной и для разработчиков, которые только пришли, и для тех, кто работает 10 лет.

  • Стандартизация — единственный способ избежать хаоса. Прод начинается с метрики.

  • Тесты нужны не только для кода, но и для метрик. Не всегда есть возможность быстро зарелизить две строки кода за 10 минут.

Тогда в пятницу вечером мы не будем «охотиться за traceId» в Kibana, а спокойно пойдем домой.

А у вас в компании как? Как тестируете метрики и стандартизируете аудит?

Комментарии (22)


  1. FlyInRoughtl
    11.11.2025 15:40

    Хорошая статья чётко показано, как продуманная архитектура и культура разработки влияют на скорость и устойчивость системы. Особенно интересно про автоматизацию и контроль качества видно, что у T-Bank инженерный подход.


    1. Shizzza
      11.11.2025 15:40

      Зарегистрирован вчера.

      Один комментарий, хвалебный, под этим постом.

      :)


  1. megadrugo2009
    11.11.2025 15:40

    Не знал про AOP.

    Оказалось что в практике как раз стараюсь использовать такой же подход.


    1. onets
      11.11.2025 15:40

      В крупных системах миллионы методов, на каждый атрибут/аннотацию не повесишь. Лучше делать еще более централизованное решение. Например в .NET через прокси объекты и интерсепторы (библиотеки castle)

      Мало того, в статье это описано сумбурно - логи и телеметрия это же разные вещи.

      В реально работающих продуктах в логах на 146% нужен контекст и собственно запрос/ответ. Иначе ничего непонятно. Но писать вообще все - может быть дорого.

      А вот этот самописный атрибут/аннотация в статье - это больше похоже на именно что телеметрию. Чтобы фиксировать время входа и выхода из методов по стеку.

      Но опять же - не знаю как в Java и других, но в .NET Core это делается через специальные активити. Причем сама платформа их активно использует и ASP.NET и ORM (entity framework) уже пишут их. Остается только добавить их на уровне бизнес слоя для детализации.

      И все равно в конце надо писать в телеметрию текст запроса к базе данных, чтобы узнать какой именно запрос тормозит.


      1. monco83
        11.11.2025 15:40

        Не писать запрос и ответ имеет смысл, чтобы не раздувать индексы логов. Чуть более хитро - писать запрос и ответ только в случае ошибки, ведь ошибки нам наиболее интересны.

        С интерсепторами в dotnet не особо богато. Castle мне приходилось использовать в Autofaq в эпоху Framework. Что из этого живо сейчас? C Microsoft DI можно Scrutor использовать, в целом пашет, хоть на этом спасибо.

        А так то... не надо ни централизованно, ни через атрибуты обкладывать каждый метод "телеметрией". Толку в этом мало. Метрики и трейсы важно снимать при обращении к инфраструктуре (БД, REST etc.), но большинство популярных библиотек и так имеет свой инструментарий для поддержки OpenTelemetry. Для остального немногого можно написать трейсы и метрики кастомно.

        P.S. Когда я сам взялся писать такие метрики и трейсы, то у меня был выбор: использовать EventSource (путь многих библиотек, например AspNetCore и Quartz), использовать интерсепторы или кастомно прописать в нужных местах работу с Activity и счётчиками. OpenTelemetry в каких-то своих документах рекомендовало именно последний вариант. На нём я и остановился.


      1. Payc Автор
        11.11.2025 15:40

        Посыл не в конкретной реализации. Суть в том что-бы закладывать логи, аудит, аналитику метрики и тд еще на стадии проектирования, продумывать структуру решений с учетом "а как мы и что мы должны наблюдать?".
        Что бы все инфо поле пересекалось, через ключи и желательно не в 5 переходов, от одного к другому и тд.

        Наблюдаемость - это НЕ время в 1 запросе куда-то. Это скорость реакции и поиска проблем, в режиме рутины... каждый день


      1. Payc Автор
        11.11.2025 15:40

        промазал....


      1. Payc Автор
        11.11.2025 15:40

        Вы явно не уловили посыл)

        Я не хочу учить писать реализации логирования, я хочу показать, что мы должны думать о наших "соседях" и в целом о продукте, частью которого мы и являемся, что бы от любого фронта до самых глубоких бэков мы могли быстро и вовремя понять что, где, когда произошло.

        Как вы исполните эту парадигму, зависит от языка, локальных практик, экспертизы участников и тд.

        Я призываю не относится к OpenTelemetry - как коду, это комплексная задача с влиянием на "соседей" и систему в целом.


  1. monco83
    11.11.2025 15:40

    Начали вы свой рассказ с многообещающих заявлений об OpenTelemetry, закончили самописным велосипедом, а почему "OpenTelemetry - это больше, чем кажется" и что это вообще такое, так и не рассказали.


    1. borino
      11.11.2025 15:40

      я так понимаю продолжение следует...


    1. Payc Автор
      11.11.2025 15:40

      Вы подтверждаете мой тейк) Для вас это просто "самописный велосипед", а я говорил, что это концептуальный подход к разработке, где код появляется в последнюю очередь.

      OpenTelemetry - нужно проектировать так же как все остальное, а то как вы ее исполните это дело 10-ое.


      1. monco83
        11.11.2025 15:40

        Ну, "подтверждаю", так "подтверждаю", чего же тут спорить. Понятно, что ваш самописный велосипед для вас не "просто самописный велосипед".


  1. remindscope
    11.11.2025 15:40

    Спасибо, посмеялся от души...

    Сначала вы распинаетесь о том, как важно все логировать и ничего не терять, а затем используете аспекты спринга, которые работают только при вызове метода из другого класса. Ну то есть вы дернете метод с такой аннотацией изнутри класса или повесите ее на приватный метод - и все эти вызовы останутся без логирования вообще.

    Действительно, "простой и очевидный" способ, прям то, что нужно для "профилактики"


    1. Payc Автор
      11.11.2025 15:40

      Я вижу вы знакомы со спрингом) Даже изучили аннотации)


      Вы точно адресат этой статьи, тк для вас OpenTelemetry остается "способ чет лить куда-то тд")))

      Именно такие как вы и побудили меня к написанию подобной статьи, для вас диапазон влияния ваших решений заключен в рамках IDE, а я прошу вас взглянуть на картину шире. Ведь суть всех решений в рамках OpenTelemetry не обустроить "свой огород", а создать наблюдаемую систему из компонентов(множества), где вам нужно понимать всю цепочку работы продукта.


      1. remindscope
        11.11.2025 15:40

        Ну то есть кривой код пишете вы, а проблема - во мне. Очень логично.


      1. monco83
        11.11.2025 15:40

        Вы своими наскоками так и нарываетесь на минусы в карму. Ведь OpenTelemetry - это не философия, не подход к разработке, это как раз и есть "способ чет лить куда-то". Это всего лишь стандарт, который приводит разные способы чёто куда-то лить к одному формату. Вы можете обмазаться логами и трейсами хоть с ног до головы и пустить всё это через OpenTelemetry, без должного продумывания и проектирования у вас всё равно будут проблемы с поиском нужной информацией в логах и с получением оповещений именно на то, что надо.

        Так вот, OpenTelemetry - это не философия, не подход к проектированию, а способ писать трейсы, метрики и логи. Это стандарт, о существе которого вы не написали ничего. А о проектировании наблюдаемости системы вы по существу тоже ничего не написали.


        1. Payc Автор
          11.11.2025 15:40

          Да) Да) Пошел я...)
          https://opentelemetry.io/docs/concepts/observability-primer/

          What is Observability?

          Observability lets you understand a system from the outside by letting you ask questions about that system without knowing its inner workings. Furthermore, it allows you to easily troubleshoot and handle novel problems, that is, “unknown unknowns”. It also helps you answer the question “Why is this happening?”

          To ask those questions about your system, your application must be properly instrumented. That is, the application code must emit signals such as traces, metrics, and logs. An application is properly instrumented when developers don’t need to add more instrumentation to troubleshoot an issue, because they have all of the information they need.

          OpenTelemetry is the mechanism by which application code is instrumented to help make a system observable


          1. monco83
            11.11.2025 15:40

            >OpenTelemetry is the mechanism by which application code is instrumented to help make a system observable

            Да, OpenTelemetry инструмент, который может помочь сделать систему наблюдаемой. Спасибо, чо не поленились проиллюстрировать мою мысль примером из документации.


            1. Payc Автор
              11.11.2025 15:40

              ВЫ: "многообещающих заявлений об OpenTelemetry, закончили самописным велосипедом"

              Я: "OpenTelemetry — стал основой идеологии наблюдаемости. Это не просто, некая библиотека или экосистема для трассировок, метрик и логов. Micrometer, Prometheus, Grafana, Jaeger — все инструменты используют эту идеологию как основу для интеграции и стандартизации своих инструментов."

              Вы снова игнорируете Концепт в рамках которого OpenTelemetry существует как решение. Это я и хотел показать цитатой, все решения следствия неких концептов OOP, AOP, REST, SOLID и тд.

              Я вам предлагаю OpenTelemetry рассмотреть шире чем просто "код", я предлагаю смотреть на OpenTelemetry как на инструмент Observability.

              Мне не ясно, что вы хотите вложить в "многообещающих", я не предлагаю вам решений, я предлагаю вам думать шире чем "велосипед".


              1. monco83
                11.11.2025 15:40

                "Решений" вы не предлагаете, нормального рассказа об OpenTelemetry вы не предлагаете, каких-то системных и стоящих обсуждения паттернов построения "наблюдаемого" приложения, кроме довольно банального совета соединить всё трейсами, вы не предлагаете. Всё, что вы предлагаете незнакомым вам людям, это "думать шире". Вряд ли многие сочтут такие рекомендации достойными внимания.


  1. kemsky
    11.11.2025 15:40

    Если уж делать такое, то надо было инcтрументировать RestTemplate, что уже сделано до нас. И проблема с этой готовой телеметрией такая, что хоть данных и пишется много, а все равно для сколько-нибудь хитрых вещей нужна дополнительная информация, без которой баг сходу не выловить.


  1. igorm01
    11.11.2025 15:40

    10 лет в разработке.

    Посмотрим что вы напишите когда будет хотя бы 20. Пока выглядит как попытка нащупать почву под ногами