{ "index": 154, "slug": "editorial-2023-09-field-telemetry-signals", "title": "Ошибка без причины: маршрут диагностики через log, metric и trace", "excerpt": "Пошаговый маршрут, который не подменяет один correlation ID новой label-кардинальностью: как разложить симптом, гипотезу и evidence между metric, trace и log/event record.", "contentHtml": "

Симптом для диагностики звучит знакомо: график ошибок показывает изменение, но инженер не может назвать запрос и этап, на котором оно возникло. В ответ часто начинают искать текст исключения во всех logs или добавляют request ID в metric labels. Первый путь тонет в несвязанных записях, второй смешивает счётчик с идентичностью одного запроса. Причина не становится ближе: у трёх источников нет договора, который превращает один сигнал в вопрос к следующему.

\n

Цена такого разрыва — решение на основании наиболее громкой витрины. Можно увеличить timeout, включить retry или объявить downstream виновником, хотя связь между error count, span и event не подтверждена. Эта статья не расследует реальный инцидент и не собирает telemetry. Она строит безопасный diagnostic route для одного fixed synthetic сценария, чтобы показать: evidence одного отказа складывается из разных объектов, а не из максимального количества labels.

\n

Начните не с поиска, а с вопроса

\n

У диагностики есть три уровня. Metric помогает сформулировать, какой класс исходов стоит рассматривать: например, synthetic `outcome=synthetic-rejected` для synthetic checkout route. Trace должен показать предполагаемый причинный путь из gateway к payment шагу. Log/event record должен назвать событие на payment step и сохранить тот же correlation key. Только после этого появляется evidence-card: она говорит, какую гипотезу можно проверить и чего пока нет. Ни один из объектов по отдельности не заменяет остальные.

\n
Маршрут вопросов вместо бесконечного поиска
ОчередьВопросНужное представлениеДопустимый результатЧто не делать
1какой класс результата разбираем?metric labelssynthetic route + outcomeне добавлять request ID ради фильтра
2какой путь должен ему соответствовать?trace + span treeодин synthetic trace ID, два шагане считать график доказательством причины
3какое событие произошло на шаге?log/event recordevent name + trace ID + span IDне искать по свободному тексту без correlation
4какой вывод честен?evidence cardnot-a-production-observationне объявлять hypothesis подтверждённой fixture-ом
\n
\"Учебный
Диаграмма показывает порядок вопросов и границы вывода для synthetic записи. Она не изображает реальный alert, dashboard, запрос к backend, trace search, latency или подтверждённую причину production-сбоя.
\n

Metric даёт границу разбора, а не виновника

\n

В учебном наборе metric record содержит имя `synthetic.checkout.authorization.rejected.total`, значение `1` и три labels. Значение `1` — не измеренный в системе counter, а фиксированная часть fixture. Оно нужно только чтобы показать форму: одна маленькая точка может обозначать класс outcome. По ней нельзя определить user, order, request или span. Такую границу полезно сохранять даже если UI backend позволяет кликнуть на dimensions: возможность фильтра не превращает metric в достоверный журнал событий.

\n

Если на первом шаге неизвестно, какой вопрос нужно решить, не пополняйте labels «на всякий случай». Сначала назовите route template и outcome class, которые должны быть малым словарём. Затем спросите владельца инструмента, какая реальная единица агрегации поддерживается, какие resource attributes добавляются и где будет измеряться cardinality. Без ответа status должен быть «не проверено», а не «у нас низкая cardinality». Fixture помогает удержать именно эту дисциплину: лишний `trace_id`, `request_id` или `user_id` он отвергает до того, как поле станет привычным.

\n

Trace связывает причины, log/event фиксирует контекст

\n

Дальше мы идём по `synthetic-trace-2023-09-A`. В trace object есть root span gateway и дочерний payment span; оба названия и состояния synthetic. Связь потомка с родителем — модель причинного маршрута, а не свидетельство выполнения вызова. Log/event record ссылается на payment span, имеет тот же trace ID и event name отказа. Если trace ID или span ID в log отличаются, fixture возвращает отказ. Это простое правило полезнее длинного списка полей: событие должно либо объяснять конкретный шаг пути, либо честно оставаться несвязанным.

\n

Event attributes нужны для узкой диагностики события. В примере есть `failure.class=synthetic-declined` и `retry.advice=synthetic-do-not-retry`. Они не говорят, как надо обрабатывать настоящие платежи, и не являются production error message. Их роль — показать разницу между типом отказа и точной идентичностью запроса. В реальном проекте перед добавлением любых attributes нужно отдельно решить privacy, возможность redaction, retention, доступ к поиску и стабильность названий. Нельзя прятать эти решения под словом «контекст».

\n

Прогоните одну контролируемую модель

\n

Код ниже создаёт fixed synthetic scenario в памяти. Он не может обратиться к приложению или telemetry backend, не читает clock и не создаёт telemetry. `runTelemetryFixture()` проверяет девятнадцать assertions: общий trace ID для trace и log, правильный span, три разрешённых labels, отсутствие trace ID в labels, закрытый список входных полей, отрицательные ветки mismatch и предел rollback. Это упражнение для review контракта. Его PASS не подтверждает, что downstream отказал, что metric выросла, что span записался или что log можно найти.

\n
import {\n  assembleSyntheticTelemetryScenario,\n  runTelemetryFixture,\n} from './upgrade-2023-09.mjs';\n\nconst scenario = assembleSyntheticTelemetryScenario({\n  synthetic: true,\n  traceId: 'synthetic-trace-2023-09-A',\n  rootSpanId: 'synthetic-span-gateway-A',\n  downstreamSpanId: 'synthetic-span-payment-A',\n  logTraceId: 'synthetic-trace-2023-09-A',\n  logSpanId: 'synthetic-span-payment-A',\n  eventName: 'synthetic.payment.authorization-rejected',\n  metricLabels: {\n    service: 'synthetic-checkout-api',\n    route: 'synthetic-checkout',\n    outcome: 'synthetic-rejected',\n  },\n});\n\nconst report = runTelemetryFixture();\nif (!Object.values(report.assertions).every(Boolean)) throw new Error('fixture failed');\nconsole.log(scenario.evidence.conclusion); // not-a-production-observation\n\n// Не создаёт trace/log/metric, не запускает SDK и не отправляет данные.\n\nnode web/scripts/upgrade-2023-09.mjs --verify-fixture\n\n# PASS проверяет только fixed synthetic records in memory.\n# Не доказывает incident, production latency, trace export или cardinality.
\n

После локального прогона полезно записать evidence без переобобщения. Корректная формулировка: «модель ожидает, что один correlation key соединяет заданный payment event и заданный trace; metric использует только три fixed dimensions». Некорректная: «причина ошибки найдена» или «metric безопасна для production». Разница кажется формальной только пока первое решение не затронуло retry, alert policy или пользовательские данные. В инженерном разборе неизвестное — это тоже результат, который должен пережить передачу задачи.

\n

Маршрут: симптом → причина → проверка → действие

\n
  1. Симптом. Есть числовой признак класса ошибок, но нет понятного перехода к одному пути запроса и событию, которое его объясняет.
  2. Причина. Metric, trace и log/event живут без общего contract: метрика получила per-request labels, а log не несёт trace/span correlation.
  3. Проверка. Выберите один synthetic outcome. Проверьте, что metric содержит только service/route/outcome, trace имеет один ID и два шага, а log/event повторяет trace ID и downstream span ID. Запустите fixture с отрицательными ветками.
  4. Действие. Зафиксируйте маршрут metric → trace → log/event → evidence. Поставьте trace ID в correlation fields, а не в labels метрики; детали оставьте в атрибутах события только после отдельного policy review.
  5. Проверка вывода. В реальном контуре заранее назовите, какой query или безопасная выборка может подтвердить каждую стрелку. Пока она не выполнена, conclusion остаётся не подтверждённым.
  6. Следующий шаг. Добавьте в runbook одну ветку mismatch: что делать, если metric есть, но trace или log не связываются. Это отдельная проблема instrumentation, а не приглашение добавить новый ID в счётчик.
\n

Когда останавливать, а когда откатывать

\n

Если на review обнаружился label с высокой кардинальностью или несвязанный event, первое действие — остановить распространение нового контракта. Это не равно удалить все данные: нельзя обещать удаление, не зная платформы, retention, доступа и состоявшегося rollout. Затем нужно отделить два вопроса: какие новые записи могут продолжать возникать и какие потребители уже зависят от этого поля. Только после этого владелец выбирает обратимое действие для конкретной конфигурации.

\n

Fixture умеет только вернуть snapshot synthetic полей и пометить `telemetry=not-created-or-deleted`. Он не выключает instrumentation, не меняет sampling, alert, dashboard или access policy. Такой rollback не декоративен: он ставит границу между проверкой модели и операционным изменением. В реальном runbook точка возврата должна быть названа точнее: версия конфигурации, набор approved fields, способ проверить отсутствие дальнейшего потока и владелец подтверждения.

\n

Ограничения и следующий шаг

\n

Здесь нет реальных logs, metrics, traces, latency, cardinality, traffic, backend records или incident data. Нет отправки данных, запроса, collector, exporter, storage, sampling, alerting, query, dashboard или эффекта в production. Synthetic `value: 1` не является измерением; synthetic IDs не являются request IDs. Пакет также не утверждает, что реальные error messages, user fields или маршруты допустимы для хранения. Он только различает роли полей и показывает, какую связь надо проверить позднее.

\n

Следующий шаг — провести ограниченное design review одного изменения инструментирования. Договоритесь о: одном вопросе к метрике, одном route template, малом outcome vocabulary, одном correlation key и минимальном event schema. Затем выберите реальную разрешённую среду и способ проверить путь без публикации чувствительных значений. Если итогом окажется, что trace context не проходит конкретную границу, это не поражение модели: это точная задача для следующего изменения, а не основание расширять cardinality метрики.

\n

Историческая граница сентября 2023

\n

Материал использует только OpenTelemetry Specification v1.20.0, опубликованную 7 апреля 2023 и доступную к сентябрю того года. Она уже описывала tracing API, metrics data model и logs data model, на которые опирается различение сигналов. Статья не утверждает, что конкретная SDK, transport, collector или backend имеют одинаковую зрелость, и не переносит в 2023 год более поздние договорённости команды или инструмента.

\n

Проверяемые источники

\n" }