{ "index": 156, "slug": "editorial-2023-09-practice-telemetry-signals", "title": "Trace ID, метрика и лог: как связать сигналы без высокой cardinality", "excerpt": "Практический договор для распределённого запроса: trace ID связывает путь и событие, метрика считает малый набор классов, а лог сохраняет контекст отказа.", "contentHtml": "
На графике выросли ошибки авторизации. В журнале есть сообщения об отказе. В трассировке виден тот же endpoint, но инженер не может доказать, что три наблюдения относятся к одному запросу. Он тратит время на ручной поиск и может увеличить timeout или retry, не устранив причину. Цена ошибки — лишняя нагрузка, задержка расследования и решение по несвязанным данным.
\nБыстрый ремонт — добавить trace ID во все метрики, а в лог оставить длинный текст. Связь одного запроса с графиком станет возможной, но метрика перестанет быть хорошим агрегатом. Число уникальных series будет расти вместе с числом запросов. Корреляция должна жить в trace и логах, а метрика должна отвечать на вопрос о классе событий.
\nTrace описывает путь операции через компоненты. Metric показывает число, долю или распределение во времени. Log фиксирует отдельное событие и его контекст. OpenTelemetry называет их разными сигналами, потому что у них разные модели данных и способы поиска.
\nОбщий trace ID связывает span одного распределённого пути. Span ID уточняет конкретный шаг. Лог может содержать оба значения и имя события. Метрика должна использовать поля с небольшим заранее известным набором значений: шаблон маршрута, результат и имя сервиса. Идентификатор запроса, пользователя, заказа и необработанный URL в этот набор обычно не входят.
\nКлиент отправляет запрос в gateway. Gateway принимает или создаёт trace context и передаёт его дальше. Сервис оплаты создаёт дочерний span. При отказе сервис записывает событие в лог с trace ID и span ID шага оплаты. Отдельно он увеличивает счётчик отказов с labels service, route и outcome. По метрике видно, что класс отказа растёт. По trace ID можно найти конкретный путь. По записи события можно понять, что произошло внутри шага.
Сигналы не связываются автоматически. Пропагатор может быть не настроен, лог может потерять контекст, sampling может не сохранить нужный trace, а индекс может скрыть поле поиска. Договор задаёт ожидаемую связь. Проверка должна показать, где она рвётся.
\n| Сигнал | Вопрос | Допустимые поля | Не следует добавлять |
|---|---|---|---|
| Trace | Какой путь прошёл запрос? | trace ID, span ID, service, operation | Свободный текст вместо структуры |
| Metric | Как меняется класс событий? | service, route template, outcome | trace ID, request ID, user ID, order ID |
| Log | Что произошло на одном шаге? | trace ID, span ID, event name, безопасные attributes | Секреты, токены и лишние персональные данные |
Ниже показана форма данных для одного учебного отказа. Имена с префиксом demo- не представляют реальные запросы, пользователей, задержки или результаты работы сервиса. Пример проверяет только границы между сигналами.
const trace = { traceId: 'demo-trace-001', spans: [{ spanId: 'demo-span-gateway', service: 'gateway', operation: 'checkout' }, { spanId: 'demo-span-payment', service: 'payment', operation: 'authorize' }] }; const logEvent = { traceId: trace.traceId, spanId: 'demo-span-payment', eventName: 'payment.authorization.rejected', attributes: { reasonClass: 'demo-limit', retryable: false } }; const metric = { name: 'payment_authorization_total', value: 1, labels: { service: 'payment', route: 'checkout', outcome: 'rejected' } };\nВ примере trace ID повторяется в trace и log. Он не попадает в labels метрики. Поля reasonClass и retryable объясняют событие, но не становятся dimensions автоматически. В рабочей системе имена и состав полей нужно согласовать с владельцами сервиса, требованиями безопасности и возможностями хранилища. Этот фрагмент не создаёт telemetry и не доказывает работу экспортера.
| Симптом | Причина | Проверка | Действие |
|---|---|---|---|
| График показывает всплеск, но запрос не найти | Нет устойчивого перехода от metric к trace | Выбрать один отказ и проверить его trace ID в логах | Оставить labels агрегируемыми, а корреляцию добавить в log/span |
| Число series растёт почти с каждым запросом | В labels попал trace ID или другой уникальный идентификатор | Посчитать словари значений по каждой label и сравнить с числом запросов | Удалить request-like label и заменить его малым классом |
| Trace есть, но событие не объясняет отказ | Лог содержит только текст или другой span ID | Сверить trace ID, span ID, имя события и обязательные attributes | Записывать структурированное событие на том же шаге |
| Связь работает только иногда | Контекст теряется на границе сервиса или при sampling | Проверить propagation на входе и выходе | Исправить границу передачи; не маскировать пробел новой label |
Если gateway передал trace context, а payment создал новый trace вместо дочернего span, оба сигнала выглядят корректно по отдельности. Поиск по одному ID ничего не даст. Если лог записал trace ID, но указывает span gateway вместо span с отказом, инженер попадёт в начало пути и пропустит причину. Если метрика получила user_id, она может показать нужный случай, но ценой неконтролируемого числа комбинаций и лишнего раскрытия данных.
Проверяйте эти случаи специально. Сравните входной и исходящий context на каждой границе. Сопоставьте span ID события с операцией, где произошёл отказ. Отдельно проверьте отказ без trace: пустая строка не должна смешивать разные случаи. Если связь потеряна, исправьте propagation. Не маскируйте пробел новой label.
\nНебольшой набор labels не гарантирует низкую стоимость хранения. Итог зависит от числа сервисов, маршрутов, окружений, времени хранения и запросов к backend. Удаление trace ID из метрики не решает проблему, если в labels остаются сырые URL, тексты ошибок или идентификаторы заказов. Нужен отдельный обзор cardinality и доступа к данным.
\nTrace sampling может сохранить не каждый запрос. Логирование тоже может быть ограничено уровнем, фильтрами или политикой персональных данных. Поэтому отсутствие trace по графику не доказывает отсутствие ошибки. В критичном потоке метрика должна фиксировать класс отказа, а лог — безопасный контекст для следующей проверки.
\nДоговор готов, когда для выбранного пути выполнены четыре условия: один запрос сохраняет общий trace ID через нужные границы; событие отказа содержит тот же trace ID и span ID правильного шага; метрика группируется только по описанным labels; отрицательная проверка обнаруживает mismatch и уникальные идентификаторы в labels. Результат должен воспроизводиться по ссылкам на конкретный trace, лог и график в разрешённой среде. Если условие не выполнено, связь сигналов ещё не доказана.
\n