Наблюдаемость (Observability)
Назначение: свойства и практики, позволяющие понимать внутреннее состояние распределённой системы по её внешним проявлениям — телеметрии — и диагностировать как известные, так и ранее не встречавшиеся отказы.
Аудитория: архитекторы, техлиды, backend- и platform-разработчики, SRE-инженеры.
Статус: устоявшаяся дисциплина; термин — из теории управления (Рудольф Калман, 1960), современная трактовка — практики cloud-native и Google SRE (2016), Charity Majors и сообщество observability-платформ (2017+); фактический стандарт инструментирования — OpenTelemetry (CNCF, с 2019).
Не путать с: мониторингом (подмножество наблюдаемости — наблюдение за заранее известными режимами отказа), логированием как таковым (один из трёх сигналов), APM-продуктом (инструмент, а не свойство системы).
Общее
Наблюдаемость (observability) — свойство системы, позволяющее понять её внутреннее состояние по внешним проявлениям: не вскрывая систему, по сигналам, которые она испускает, ответить на вопросы о том, что происходит внутри. Термин пришёл из теории управления: в основополагающей работе Рудольфа Калмана (Rudolf Kálmán) On the General Theory of Control Systems (1960) система называется наблюдаемой, если её внутреннее состояние в некоторый момент времени может быть восстановлено по знанию её внешних выходов на конечном интервале. Наблюдаемость там — двойник управляемости (controllability): они определяют вместе, насколько системой вообще можно управлять на основании измерений. Перенос термина в программную инженерию сохраняет суть: внутренние состояния — это работа сервисов, баз данных и очередей, а «внешние проявления» — телеметрия: логи, метрики, трассировки.
Потребность в наблюдаемости резко возрастает с распределённостью системы. В монолите диагностика опирается на инструменты одного процесса: отладчик, стек-трейс, профиль — путь от симптома к причине лежит внутри одного адресного пространства. В микросервисной системе сбой не имеет адреса: отказ проявляется как тайм-аут на внешней границе, а его причина — истощённый пул соединений или медленный запрос — скрыта несколькими сервисами ниже; ни один отдельный журнал не содержит всей картины. Наблюдаемость — это ответ на асимметрию: сложность внутренней структуры растёт быстрее, чем способность человека её непосредственно обозревать, и единственный способ сохранить понимание — сделать так, чтобы система сама достаточно богато рассказывала о себе.
Отсюда — главное различие, которое стоит зафиксировать с самого начала: наблюдаемость против мониторинга. Эти слова часто используют как синонимы, но за ними разные задачи. Мониторинг — наблюдение за системой по заранее определённому набору условий: метрики, дашборды и алерты, настроенные под известные режимы отказа (диск заполнен, ответ 500, задержка выше порога). Мониторинг отвечает на вопросы, которые сформулированы до инцидента. Наблюдаемость — свойство, позволяющее задавать вопросы, которые не удалось предвидеть: исследовать новые, ранее не встречавшиеся отказы (novel failure modes), комбинировать данные произвольными способами и спускаться от симптома к причине без повторного релиза с добавленным логированием. Такое различие — «известные неизвестные против неизвестных неизвестных» — популяризировала Чарити Мейджорс (Charity Majors, Honeycomb, 2017+). Соотношение между понятиями иерархично: мониторинг — подмножество наблюдаемости: алерты и дашборды остаются нужными, но наблюдаемая система позволяет большее, чем реагирование на предписанный список условий.
Важно понимать и то, чем наблюдаемость не является. Она не продукт, который можно купить, и не стек, который достаточно развернуть. Наблюдаемость — свойство дизайна системы: она возникает из инструментирования (сервисы испускают структурированные, связные сигналы), из организации данных (сигналы коррелируют между собой) и из культуры эксплуатации (вопрос «что случилось?» должен решаться запросом, а не гаданием). Система, пишущая неструктурированные логи без идентификаторов, не станет наблюдаемой от развёртывания самого дорогого конвейера. Обратное тоже верно: цена наблюдаемости реальна — хранение телеметрии, эксплуатация конвейера, дисциплина команд, — и зрелая команда выбирает глубину наблюдаемости соразмерно сложности системы (раздел «Плюсы и минусы»).
Полезно держать в уме и лестницу вопросов, на которые отвечает телеметрия. Первый уровень — «что сломалось?»: ответил ли сервис, жив ли процесс, доступен ли эндпоинт; этого достаточно мониторингу. Второй — «где сломалось?»: какой сервис, какой ресурс, какой участок пути запроса; это территория метрик и трассировок. Третий — «почему сломалось?»: какое конкретное событие, с какими параметрами, в каком контексте; это территория логов и связных идентификаторов. Мониторинг закрывает первый уровень уверенно, второй — частично, третий — почти никогда; наблюдаемость — это про всю лестницу целиком. Как нефункциональное требование наблюдаемость закладывается при проектировании: решения о телеметрии, принимаемые после релиза, стоят на порядок дороже решений, заложенных в архитектуру с самого начала — об этом же напоминает и введение в архитектуру в обзоре сквозных задач архитектуры ПО.
Практическая проверка наблюдаемости конкретной системы — не инвентаризация инструментов, а дежурные вопросы. «Какая доля оформлений заказа за последний час завершилась ошибкой и с каким кодом?» — метрики. «Покажи медленные запросы пользователя u_771 за вчера» — трейсы или логи. «Что происходило с заказом 10427 на пути через пять сервисов?» — связные логи или трейс. Если ответ находится запросом за минуты — система наблюдаема; если требует правки кода, релиза и ожидания повторения события — нет, сколько бы телеметрии ни собиралось.
Историческая рамка коротка. Поколение систем мониторинга 2000-х (Nagios, Zabbix) наблюдало преимущественно инфраструктуру — доступность хостов и процессов. Внутри Google в 2000-е сложилась практика SRE — измерение уровня сервиса, а не железа, увековеченная книгой Site Reliability Engineering (2016); там же появилась статья о распределённом трейсинге Dapper (2010), доказавшая, что сквозную трассировку можно собирать на масштабе миллиардов запросов. Cloud-native экосистема вынесла эти идеи в open source (Prometheus, Zipkin, Jaeger, ELK), а слияние инструментационных стандартов в OpenTelemetry (2019) сделало vendor-neutral телеметрию нормой. Сегодня наблюдаемость — одна из сквозных забот распределённой архитектуры наряду с масштабируемостью и отказоустойчивостью: без неё ни та, ни другая не диагностируемы.
Три кита: логи, метрики, трассировки
Классическая модель наблюдаемости опирается на три типа телеметрии — «три кита» (three pillars): метрики, логи и трассировки. Каждый отвечает на собственный класс вопросов, имеет собственную экономику и собственные ограничения.
Метрики — агрегированный пульс системы
Метрики (metrics) — числовые временные ряды: агрегированные измерения с течением времени. Типы базовые:
- счётчик (counter) — монотонно растущая величина: сколько раз событие произошло (запросы, ошибки, сообщения очереди);
- датчик (gauge) — текущее значение, которое может расти и падать: число соединений, длина очереди, размер кэша;
- гистограмма (histogram) — распределение величины по бакетам: задержки, размеры ответов; из бакетов вычисляются квантили (p50, p95, p99).
Так выглядит экспозиция метрик в формате Prometheus:
# HELP http_requests_total Общее число HTTP-запросов
# TYPE http_requests_total counter
http_requests_total{service="orders",code="200",method="POST"} 41803
http_requests_total{service="orders",code="500",method="POST"} 127
# HELP http_request_duration_seconds Время обработки запросов
# TYPE http_request_duration_seconds histogram
http_request_duration_seconds_bucket{service="orders",le="0.1"} 21744
http_request_duration_seconds_bucket{service="orders",le="0.3"} 39022
http_request_duration_seconds_bucket{service="orders",le="+Inf"} 41930
Метрики предагрегированы и компактны: одна серия — один столбец чисел; поэтому они дёшевы в хранении, быстры в запросах и идеальны для алертов, трендов и дашбордов. Доставка бывает двух моделей: pull — сервер метрик сам опрашивает экспозиции приложений (модель Prometheus; опрос по расписанию упрощает обнаружение «молчащих» сервисов) — и push — приложение само отправляет значения (StatsD, OTLP; удобнее для краткоживущих процессов и serverless). Их ограничение общее — кардинальность: каждое уникальное сочетание меток (имя сервиса, эндпоинт, код ответа) образует отдельный временной ряд, и метрика с меткой user_id на десяти тысячах пользователей порождает десять тысяч рядов — хранилища временных рядов этого не прощают. Метрика отвечает «сколько» и «как быстро», но не «почему»: счётчик знает, что ошибок стало больше, — но не знает, каких именно заказов и из-за чего.
Логи — события с контекстом
Логи (logs) — дискретные события с текстом и атрибутами, записываемые в момент происшествия: «заказ 10427 оплачен», «пул соединений исчерпан», «пользователь аутентифицирован». Лог — атомарная единица контекста: в отличие от метрики он может нести произвольные поля — идентификаторы, параметры, стек ошибки, решение бизнес-правила. Именно поэтому расследование любого инцидента рано или поздно упирается в журналы: это единственный сигнал, в котором единичное событие видно целиком.
Плата — объём и цена. Полнотекстовое хранение логов на больших системах — одна из самых дорогих статей инфраструктуры: журналы растут пропорционально трафику, поэтому в дело идут retention-политики (логи ошибок — месяц, отладочные — дни), семплирование отладочных событий и разделение уровней. Лог отвечает «что именно случилось», но по одному событию нельзя понять распределённый путь и нельзя посчитать агрегат.
Трейсы — путь запроса через границы
Трейсы (traces) — путь запроса через систему: дерево сегментов (спанов), показывающее, через какие сервисы, в каком порядке и с какими задержками прошёл один логический запрос. Трейс связывает причинно-следственную цепочку: тот самый «путь от тайм-аута на границе до медленного запроса в базе двумя сервисами ниже». Ни метрика, ни лог через границы процессов не смотрят — это делает только трассировка.
Плата — объём: трейс на каждый запрос порождает гигабайты данных, поэтому трейсы почти всегда семплируются (каким образом — в разделе о трейсинге). Трейс отвечает «где именно», но без логов и метрик беден деталями: спан знает длительность операции, но не её содержание.
Сравнение
| Признак | Метрики | Логи | Трейсы |
|---|---|---|---|
| Характер данных | Числовые временные ряды, агрегаты | Дискретные события с текстом и полями | Дерево спанов одного запроса |
| Кардинальность | Ограничена числом рядов; высокие метки дороги | Произвольная (любые поля события) | Ограничена семплированием |
| Хранение и цена | Самые дёшевы: число на интервал | Самые дорогие: полный текст с retention | Промежуточные; почти всегда семплируются |
| Типовой запрос | «Сколько и как быстро» — тренды, алерты | «Что именно случилось» — поиск по событию | «Где запрос провёл время» — путь и вклад |
| Сила | Дешёвый агрегированный взгляд в реальном времени | Максимальный контекст единичного события | Причинно-следственная связь между сервисами |
| Типовые инструменты | Prometheus, Grafana, StatsD | ELK, Loki, Fluent Bit | Jaeger, Tempo, Zipkin |
Ключевой тезис таблицы: три кита не заменяют друг друга. Попытка «обойтись метриками» лишает систему контекста: алерт сработал, а что случилось — неизвестно. Попытка «писать всё в логи» лишает агрегатов: на вопрос «какая медианная задержка за час» полнотекстовый поиск отвечает ценой полного скана. Третья крайность — «трейсы вместо логов» — обедняет детали: спан знает длительность, но не судьбу конкретного заказа. Зрелый конвейер использует все три сигнала связанными: метрика поднимает алерт, трейс показывает путь, лог раскрывает событие (связь обеспечивает общий идентификатор — о нём в разделе о структурном логировании).
Когда чего достаточно: небольшому сервису с одним процессом и одной базой нередко хватает метрик и логов — путь запроса тривиален. Распределённой системе из десятков сервисов трассировка становится обязательной: без неё расследование распределённого сбоя вырождается в ручную сборку хронологии по журналам разных команд. Обратное неверно и для малых систем: наблюдаемость начинается не с количества инструментов, а со связности и структурированности сигналов.
Распределённый трейсинг и OpenTelemetry
Распределённый трейсинг (distributed tracing) — восстановление пути одного логического запроса через границы процессов и сервисов. Терминология:
- Span (спан) — один сегмент работы: обработка HTTP-запроса, вызов БД, публикация сообщения. У спана есть имя, начало, конец, атрибуты (эндпоинт, код ответа, размер), события и статус.
- Trace (трейс) — дерево спанов, объединённых общим
trace_id: от входа запроса в систему до последнего каскадного вызова. Родительские и дочерние спаны связаныparent_id. - Context propagation (распространение контекста) — механизм, передающий идентичность трейса через границы сервисов: вызывающий сервис кладёт
trace_idтекущего спана в заголовки запроса, вызываемый — извлекает и продолжает трассу как родительский контекст.
Распространение контекста — сердце трейсинга: без него спаны соседних сервисов нельзя сшить в одно дерево. Форматов два основных: W3C Trace Context — стандарт консорциума W3C, заголовок traceparent с trace_id, parent_id и флагами (современный выбор по умолчанию), — и B3 — заголовки X-B3-TraceId/X-B3-SpanId, появившиеся в экосистеме Zipkin и всё ещё распространённые. К ним примыкает W3C Baggage — механизм передачи бизнес-контекста (идентификатор пользователя, A/B-вариант) вдоль той же цепочки.
Заголовок traceparent в HTTP-запросе выглядит так:
GET /orders/10427 HTTP/1.1
Host: orders.internal
traceparent: 00-4bf92f3577b34da6a3ce929d0e0e4736-00f067aa0ba902b7-01
│ │ │ │
версия ────────┘ │ │ └─ флаги (sampled)
trace_id ─────────┘ └─ parent_id (id родительского спана)
Вызывающий сервис берёт trace_id из входящего контекста, создаёт собственный спан и передаёт вниз уже его идентификатор как parent_id; вызываемый повторяет цикл. Так дерево растёт через произвольное число уровней — от шлюза до запроса к базе.
Так выглядит дерево спанов запроса «оформление заказа» (числа — длительности спанов):
trace_id: 4bf92f3577b34da6a3ce929d0e0e4736
[ gateway: POST /checkout ]─────────────────────────── 234 ms
│
├─[ orders: create_order ]────────────────────────── 189 ms
│ ├─[ postgres: INSERT orders ]───────── 12 ms
│ └─[ payments: charge ]─────────────── 152 ms
│ └─[ postgres: UPDATE ledger ]──── 23 ms
├─[ catalog: GET /items ]───────────────────────────── 31 ms
└─[ notifications: publish order_created ]───────────── 8 ms
Дерево мгновенно отвечает на вопрос, недоступный ни логам, ни метрикам по отдельности: из 234 мс оформления заказа 152 мс занимает платёжный сервис, а внутри него — обновление журнала операций. Алерт видел бы только «оформление медленное»; трейс показывает, где именно. Один спан этого дерева в представлении бэкенда:
{
"trace_id": "4bf92f3577b34da6a3ce929d0e0e4736",
"span_id": "00f067aa0ba902b7",
"parent_span_id": "b7ad6b7169203331",
"name": "POST /payments/charge",
"kind": "server",
"start": "2026-09-30T12:04:11.482Z",
"duration_ms": 152,
"attributes": { "http.status_code": 200, "payment.method": "card" },
"status": "OK"
}
Возможность трейсинга на промышленном масштабе доказала статья Google о системе Dapper (2010): сквозная трассировка всех запросов с семплированием оказалась настолько дешёвой, что стала обязательной частью инфраструктуры. Идея Dapper была воспроизведена open source: Zipkin (Twitter, 2012) и Jaeger (Uber, 2017) дали бэкенды хранения и визуализации, а библиотеки инструментирования плодились по языкам и вендорам — пока не стало очевидно, что каждое приложение «пришивается» к конкретному бэкенду намертво. Ответом стала консолидация: в 2019 году проекты OpenTracing (CNCF, API трейсинга) и OpenCensus (Google, трейсинг и метрики) объединились в OpenTelemetry (OTel).
OpenTelemetry — проект CNCF, ставший фактическим стандартом vendor-neutral телеметрии. Его архитектура разделена на слои:
- API — интерфейсы в каждом языке: спаны, метрики, логи, распространение контекста. Прикладной код зависит только от API.
- SDK — реализация API: конфигурация, семплирование, пакетирование, экспорт. Выбор бэкенда — конфигурация SDK, а не код.
- Collector — автономный конвейер: принимает телеметрию (протокол OTLP и десятки legacy-форматов), обрабатывает (батчинг, фильтрация, обогащение, tail-семплирование) и экспортирует в бэкенды — один или несколько одновременно.
- Protocol (OTLP) — единый протокол передачи, на котором сходятся все три сигнала.
Ценность этой схемы — нейтральность к бэкенду: инструментирование пишется один раз, а смена хранилища трейсов или метрик — это переконфигурация Collector’а, не переписывание сервисов. Телеметрия становится инфраструктурой, а не привязкой к вендору. Collector допускает два режима развертывания, которые сочетаются: agent — лёгкий экземпляр рядом с приложением (sidecar-контейнер или daemonset узла), принимающий телеметрию локально, — и gateway — центральный кластер, агрегирующий потоки от агентов перед экспортом; между сервисами и бэкендами никогда не строят прямых зависимостей — всё идёт через конвейер.
Инструментирование при этом бывает двух родов. Ручное — спаны и метрики создаются в коде явно: разработчик оборачивает доменно значимые операции («проверка антифрода», «резервирование товара») и добавляет бизнес-атрибуты; только так в телеметрии появляется предметная семантика. Автоматическое — SDK-агент или инфраструктура создают спаны за разработчика: входящие HTTP-запросы, вызовы БД и очередей, клиенты внешних сервисов инструментируются библиотекой-агентом без правок кода (в JVM-мире — javaagent, в .NET — OpenTelemetry profiler; прокси service mesh трассируют пересылки сами). Практическая норма — автоматическая база плюс ручная доменная надстройка: первая закрывает транспортную трассировку, второй добавляет то, что транспорт не знает.
Типовой конвейер телеметрии на OpenTelemetry:
┌─────────────┐ ┌─────────────┐ ┌─────────────┐
│ Сервис A │ │ Сервис B │ │ Сервис C │
│ OTel SDK │ │ OTel SDK │ │ авто-инстр. │
└──────┬──────┘ └──────┬──────┘ └──────┬──────┘
│ OTLP │ OTLP │ OTLP
└────────┬─────────┴──────────┬──────┘
▼ ▼
┌───────────────────────────────────┐
│ OpenTelemetry Collector │
│ приём · батчинг · фильтрация · │
│ обогащение · tail-семплирование │
└────┬─────────────┬────────────┬───┘
▼ ▼ ▼
┌──────────┐ ┌──────────┐ ┌──────────┐
│ metrics │ │ logs │ │ traces │
│Prometheus│ │ Loki/ES │ │Jaeger/ │
│ │ │ │ │Tempo │
└──────────┘ └──────────┘ └──────────┘
Отдельного слова требует семплирование трейсов — компромисс между полнотой и стоимостью. Head-based семплирование решает в начале трейса (SDK): трассировать каждый сотый запрос; дёшево, но редкие ошибки почти наверняка выпадут из выборки. Tail-based решает в конце (Collector): сохранять все ошибочные и медленные трейсы, из успешных — каждый сотый; дороже в эксплуатации, но существенно полезнее при расследованиях. Практическая норма — хранить 100% ошибочных трейсов и 1–10% успешных.
Связь трейсинга с интеграционными паттернами двусторонняя: service mesh (Envoy, Linkerd) распространяет заголовки трассировки и генерирует спаны на каждой пересылке даже для неинструментированных сервисов, а шлюзы часто выступают точкой зарождения трейсы — первым спаном, которому доверяется trace_id.
Практики: RED, USE, Four Golden Signals
Три кита дают сырьё; методики наблюдаемости дают ответ, какие именно метрики собирать. Три канонические системы отвечают на этот вопрос с двух сторон: RED и Four Golden Signals — для сервисов, USE — для ресурсов.
RED — методика для сервисов и API: на каждый эндпоинт наблюдать три метрики. Название закрепил Том Уилки (Tom Wilkie, Weaveworks, 2015) в связке с Prometheus:
- Rate — интенсивность запросов (запросов в секунду);
- Errors — доля ошибочных ответов (5xx, тайм-ауты, бизнес-отказы);
- Duration — распределение времени обработки (не среднее, а квантили: p50, p95, p99).
USE — методика для ресурсов: для каждого ресурса (CPU, память, диск, сеть, пул соединений) наблюдать:
- Utilization — загрузка (какая доля времени/ёмкости ресурс занят);
- Saturation — насыщенность (длина очереди к ресурсу, ожидание);
- Errors — ошибки самого ресурса (сбросные пакеты, ошибки устройства).
Автор — Брендан Грегг (Brendan Gregg, Netflix/Intel), методика опубликована в 2012 году как систематический чек-лист: «для каждого ресурса проверь использование, насыщение и ошибки». Проход по типовым ресурсам серверного приложения выглядит так:
| Ресурс | Utilization | Saturation | Errors |
|---|---|---|---|
| CPU | Утилизация ядер | Длина очереди планировщика (load average) | — (аппаратные сбои) |
| Память | Занятая ёмкость | Swapping, major page faults | OOM-killer |
| Диск (I/O) | Время занятости устройства | Длина очереди запросов | Ошибки ввода-вывода |
| Сеть | Пропускная способность | Очереди, дропы пакетов | Ретрансмиты, сбросы соединений |
| Пул соединений | Занятые слоты | Очередь ожидания слота | Тайм-ауты получения соединения |
Four Golden Signals — синтез из книги Site Reliability Engineering (Google, 2016): четыре сигнала, достаточные для первичного наблюдения за любым сервисом:
- Latency — задержка (с разделением успешных и ошибочных ответов: медленная ошибка — не то же, что медленный успех);
- Traffic — нагрузка (запросы в секунду, объём данных);
- Errors — ошибки (по протоколу и по доменным правилам);
- Saturation — насыщенность (полнота ресурсов: очереди, утилизация, давление на память).
| Методика | Объект наблюдения | Метрики | Происхождение |
|---|---|---|---|
| RED | Сервисы и API | Rate, Errors, Duration | Том Уилки, Weaveworks, 2015 |
| USE | Ресурсы (CPU, диск, пулы) | Utilization, Saturation, Errors | Брендан Грегг, 2012 |
| Four Golden Signals | Сервис (минимальный набор) | Latency, Traffic, Errors, Saturation | Google SRE Book, 2016 |
Соотношение методик прагматично: Four Golden Signals — это «RED + насыщенность» на уровне всего сервиса (минимальный стандарт алертинга), USE — ортогональный проход по ресурсам, на которых сервис работает. Практическое правило: золотые сигналы на каждый сервис, USE на каждый ресурс — вместе они закрывают и «сервис тормозит», и «почему у него кончился пул соединений». На Prometheus RED-тройка для сервиса выражается тремя запросами:
# Rate: интенсивность запросов (в секунду, окно 5 минут)
rate(http_requests_total{service="orders"}[5m])
# Errors: доля ошибок 5xx среди всех ответов
sum(rate(http_requests_total{service="orders",code=~"5.."}[5m]))
/ sum(rate(http_requests_total{service="orders"}[5m]))
# Duration: p99 задержки обработки
histogram_quantile(0.99,
sum(rate(http_request_duration_seconds_bucket{service="orders"}[5m])) by (le))
Три выражения — и алертная база сервиса готова: скорость падает к нулю — трафика нет; доля ошибок растёт — сервис отказывает; p99 ползёт вверх — деградация под нагрузкой.
Над метриками сервисов строится управление надёжностью: SLI/SLO и бюджет ошибок. SLI (service level indicator) — измеримый индикатор качества: доля успешных ответов, квантиль задержки. SLO (service level objective) — целевой уровень SLI: «99,9% запросов быстрее 300 мс за 30 дней». Бюджет ошибок (error budget) — допустимый объём нарушений: разница между 100% и SLO. Цена каждой «девятки» наглядна в пересчёте на месяц:
| SLO | Бюджет ошибок (на 30 дней) |
|---|---|
| 99% | ~7,2 часа простоя или 1% ошибок |
| 99,9% | ~43 минуты |
| 99,99% | ~4,3 минуты |
Бюджет превращает надёжность из лозунга в ресурс: пока бюджет не истрачен, можно агрессивно поставлять новые функции; исчерпан — разработка замедляется в пользу стабильности. Измеримая надёжность опирается на наблюдаемость напрямую: без честных SLI нет и осмысленных SLO. Глубокий разбор этих механизмов — в статье «Отказоустойчивость и High Availability».
Структурное логирование и correlation IDs
Лог — старейший сигнал наблюдаемости, и его качество определяется форматом. Структурное логирование — запись каждого события как машиночитаемой записи с полями (обычно JSON) вместо свободного текста:
{"ts":"2026-09-30T12:04:11Z","level":"info","service":"orders","msg":"order_paid",
"order_id":10427,"user_id":"u_771","amount":15990,"trace_id":"4bf92f3577b34da6a3ce929d0e0e4736","span_id":"00f067aa0ba902b7"}
Преимущество перед plain text — запрашиваемость: событие с полями находится фильтром (order_id=10427 AND level=error), группируется, считается; строка «Order 10427 paid successfully» находится только полнотекстовым поиском и не объединяется ни с чем. Структурный формат делает логи пригодными к автоматической обработке — от алертов по шаблонам до аналитики. Практические следствия:
- Уровни (TRACE, DEBUG, INFO, WARN, ERROR) — фильтр значимости: ERROR должен означать «требует вмешательства», WARN — «аномалия, но обработана»; система, пишущая всё в ERROR, приучает дежурного игнорировать журнал.
- Семплирование — на высоких нагрузках часть отладочных событий отбрасывается; ошибки семплировать нельзя.
- Поля вместо конкатенации — идентификаторы, суммы, коды — отдельными полями, не вшитыми в текст.
В коде структурное логирование выглядит как явная передача полей и сквозного контекста:
// событие с полями и трассировочным контекстом из текущего span'а
log.info('order_paid', {
order_id: order.id,
user_id: order.userId,
amount: order.total,
trace_id: ctx.traceId, // сквозной идентификатор трейса
span_id: ctx.spanId,
});
Второй столп логирования в распределённой системе — correlation ID (он же request ID): идентификатор, который присваивается запросу на входе в систему (шлюзом или первым сервисом) и сквозным образом передаётся через все сервисы и очереди, записываясь каждым участником обработки. Заголовок X-Request-ID, передача через исходящие вызовы и свойства сообщений очереди, запись в каждый лог — и событие «заказ 10427» собирается из журналов пяти сервисов одним фильтром:
клиент ─────► шлюз ─────► orders ─────► payments ─────► БД
│ │ │
│ X-Request-ID: 7f3a9c... передаётся дальше
│ и пишется в каждое лог-событие каждого сервиса
│
генерирует
X-Request-ID
Без correlation ID журналы распределённой системы — куча несшиваемых осколков; с ним — хронология одного сценария. В асинхронных связях корреляция переносится в тело или заголовки сообщений брокера (подробнее — в готовящейся статье об очередях сообщений); в событийно-ориентированной архитектуре роль correlation ID часто играет само событие с его event_id и причинными ссылками.
Зрелое решение — не заводить отдельный request ID, а использовать trace_id трейсинга как единый correlation ID: записывать его в каждое лог-событие (как в примере выше). Тогда три кита связываются в одну систему координат: алерт по метрике открывает дашборд, трейс показывает путь, логи с тем же trace_id — события; переход от агрегата к событию — один клик, а не ручная сверка времени. Именно эта связность, а не количество собранных байтов, отличает наблюдаемую систему от «просто логирующей». Так выглядит расследование инцидента в связанной системе:
алерт: p99 POST /checkout > 1 s (метрика)
│ дашборд: деградация с 12:04, только prod
▼
трейсы с duration > 1 s (трейс)
│ 152 мс из 234 — payments: charge,
│ внутри — UPDATE ledger: 23 мс → 980 мс
▼
логи с trace_id найденного трейса (логи)
│ lock_timeout на таблице ledger
▼
причина: блокировка от миграции, запущенной в 12:04
Ни один сигнал по отдельности этого пути не проходит: метрика не знает «где», трейс не знает «что», лог без trace_id не находится из первых двух шагов.
Контракт приложения в этой схеме зафиксирован Twelve-Factor App, фактором XI (Logs): приложение пишет события в стандартные потоки stdout/stderr и не управляет их дальнейшей судьбой — сбор, буферизация, доставка и хранение — задача платформы. Разделение ответственности повторяет архитектуру конвейера из раздела о трейсинге: сервис производит структурированные события, инфраструктура (агенты узлов, Fluent Bit, OTel Collector) транспортирует их в централизованное хранилище.
Хранилища логов различаются компромиссом «индекс против меток». Elasticsearch (стек ELK) строит полнотекстовый индекс по всем полям — максимальная гибкость поиска ценой дорогого индексирования и хранения. Loki (Grafana) индексирует только метки событий (сервис, уровень), а содержимое хранит сжатыми чанками и ищет по ним при запросе — на порядок дешевле, но поиск «по любому полю без метки» требует скана. Выбор отражает честный ответ на вопрос «какие вопросы будут задаваться к логам»: если метки известны заранее — Loki экономит бюджет; если вопросы произвольны — платят за индекс.
Плюсы и минусы
Плюсы
Скорость диагностики распределённых сбоев. Связанные сигналы сокращают расследование с часов ручной сверки журналов до минут: метрика локализует симптом, трейс — путь и вклад, лог — событие. Для систем, где микросервисы размазывают отказ по цепочкам вызовов, это не удобство, а способность вообще найти причину.
Готовность к неизвестным отказам. Мониторинг покрывает известные сценарии; наблюдаемая система допускает произвольные вопросы к прошлому — novel-сбои расследуются по уже собранным данным, без релиза с «добавленным логированием» и ожидания повторения.
Управляемая надёжность. SLI/SLO и бюджеты ошибок делают качество измеримым и обсуждаемым — между командой и бизнесом появляется общий язык уровня сервиса, а приоритет работы со стабильностью — не лозунг, а арифметика бюджета.
Слепые поставки становятся зрячими. Канареечные релизы и быстрые откаты — инструменты управления трафиком — опираются на телеметрию: решение «раскатывать дальше или откатывать» — это сравнение метрик новой и старой версий на ограниченном трафике. Без наблюдаемого сигнала поставка возможна только «вслепую», малыми шагами и с ручной проверкой.
Нейтральность к вендору. OpenTelemetry выносит инструментирование в стандарт: смена бэкендов телеметрии — переконфигурация конвейера, а не переписывание сервисов; телеметрия перестаёт быть lock-in’ом.
Выравнивание контекста между командами. Единый correlation ID и структурированные события создают общий язык диагностики: инженер соседней команды читает те же сигналы, что и автор сервиса.
Минусы
Стоимость хранения. Логи и трейсы — самые дорогие сигналы: полнотекстовые хранилища растут быстрее продукта, retention-политики и семплирование — не оптимизация, а необходимость. Метрики дёшевы ровно потому, что агрегированы и ограничены по кардинальности.
Кардинальность метрик. Каждая уникальная метка — новый временной ряд; необдуманная метка user_id или request_path с параметрами способна положить хранилище временных рядов. Работа с кардинальностью — постоянная дисциплина: высококардинальные вопросы уводятся в логи и трейсы, метрики остаются агрегатами.
Семплирование как компромисс. Трейсы и отладочные логи собираются выборочно — значит, конкретное событие могло не попасть в выборку. Head-семплирование жертвует редкими ошибками, tail-семплирование усложняет конвейер; идеала «хранить всё» не выдерживает экономика ни одной крупной системы.
Эксплуатационная цена конвейера. Сборщики, хранилища, алертинг и дашборды — сами по себе распределённые системы: их нужно версионировать, масштабировать, мониторить (ирония, но конвейер наблюдаемости должен быть наблюдаем). Малой команде конвейер полной сложности может стоить дороже пользы.
Безопасность данных телеметрии. Логи — классическое место утечек: токены, пароли, персональные данные, номера карт попадают в события и дальше — в хранилища с широким доступом и длинным retention. Требуются явные правила санитизации полей и разграничение доступа к телеметрии.
«Тонна данных ≠ наблюдаемость». Центральный минус-тезис: наблюдаемость — свойство дизайна системы, а не покупка стека. Система, испускающая несвязанные неструктурированные сигналы, не становится наблюдаемой ни от объёма данных, ни от количества дашбордов; обратно — скромный, но связанный набор сигналов может отвечать на вопросы лучше петабайта шума.
Связанные статьи
- Введение в архитектуру — место наблюдаемости среди сквозных задач архитектуры ПО.
- Микросервисы — распределённость как источник требований к телеметрии; наблюдаемость в блоке «Инфраструктура и сквозные задачи».
- Twelve-Factor App — фактор XI (Logs): логи как поток событий, сбор — задача платформы.
- Интеграционные паттерны (API Gateway, BFF, Service Mesh, Sidecar) — шлюз и mesh как точки зарождения и распространения трассировочного контекста.
- Масштабируемость (Scalability) — поиск узких мест по метрикам и трейсам; наблюдаемость как инструмент масштабирования.
- System Design — наблюдаемость как нефункциональное требование, закладываемое при проектировании.
- Событийно-ориентированная архитектура (EDA) — корреляция в асинхронных потоках: event_id и причинные связи вместо сквозного запроса.
- Отказоустойчивость и High Availability — SLO/SLI, бюджеты ошибок и связь надёжности с измеримостью.
- Очереди сообщений и брокеры — распространение correlation ID через свойства сообщений (статья готовится).
- REST vs RPC vs GraphQL (стили API) — что именно наблюдаемо на границе API: семантика кодов и ошибок (статья готовится).