8 lines
17 KiB
JSON
8 lines
17 KiB
JSON
{
|
||
"index": 154,
|
||
"slug": "editorial-2023-09-field-telemetry-signals",
|
||
"title": "Ошибка без причины: как связать метрику, trace и log",
|
||
"excerpt": "График показывает класс ошибки, но не объясняет один запрос. Разбираем маршрут metric → trace → log/event, границу cardinality и проверку, которая не выдаёт учебный пример за production-доказательство.",
|
||
"contentHtml": "<p>График ошибок растёт, но инженер не может назвать запрос и этап, на котором возник отказ. В журналах много похожих сообщений, а в trace-поиске нет понятного ключа. Самая дорогая ошибка в этот момент — принять громкий сигнал за причину: увеличить timeout, добавить retry или обвинить downstream. Сбой может остаться, а новые записи и задержки вырастут.</p>\n<p>Проблема возникает, когда metric, trace и log описывают один путь разными словами. Метрика считает класс исходов. Trace показывает путь запроса через операции. Log или event фиксирует событие и его контекст. Если между ними нет общего договора, команда видит три витрины, а не одну проверяемую цепочку.</p>\n<h2>Тезис: каждый сигнал отвечает на свой вопрос</h2>\n<p>Начинайте с вопроса, а не с поиска текста ошибки. Metric отвечает: «какой класс исходов изменился?». Trace отвечает: «через какие операции прошёл один путь?». Log/event отвечает: «какое событие произошло на конкретном шаге?». Общий trace ID или другой разрешённый correlation key связывает записи. Он не превращает metric в журнал запросов.</p>\n<p>Идентификатор одного запроса нельзя бездумно добавлять в labels метрики. Каждый новый идентификатор может создавать отдельный time series. График станет дороже, агрегация — менее полезной, а проблема поиска не исчезнет. Для метрики оставляют небольшой словарь: service, route и outcome. Подробный контекст отправляют в trace или log после проверки политики доступа и хранения.</p>\n<h2>Механизм маршрута</h2>\n<p>Представим учебный checkout-сценарий. Metric сообщает: для маршрута authorization вырос класс <code>rejected</code>. Эта запись не знает пользователя, заказа и конкретного trace. Она только выбирает поле поиска. Далее trace с тем же synthetic correlation key показывает gateway span и дочерний payment span. Затем log/event указывает, что отказ произошёл на payment span, и повторяет trace ID и span ID.</p>\n<p>Каждая стрелка требует отдельной проверки. Наличие метрики не доказывает существование trace. Наличие trace не доказывает, что log экспортирован и доступен. Совпавший ID не доказывает причину отказа, если событие записалось после ошибки или относится к другому шагу. Доказательство должно состоять из наблюдаемых объектов и честного статуса каждой связи.</p>\n<table><caption>Симптом → причина → проверка → действие</caption><thead><tr><th scope=\"col\">Симптом</th><th scope=\"col\">Причина</th><th scope=\"col\">Проверка</th><th scope=\"col\">Действие</th></tr></thead><tbody><tr><td>Счётчик ошибок изменился, trace не находится</td><td>Нет перехода от route/outcome к trace или trace не экспортируется</td><td>Взять один разрешённый outcome и проверить correlation в реальной среде</td><td>Починить передачу контекста или назвать путь неподтверждённым</td></tr><tr><td>В metric появились request ID</td><td>Идентичность запроса использовали как label</td><td>Посчитать набор labels и рост series на выбранном окне</td><td>Остановить изменение, вернуть малый словарь labels, ID оставить в trace/log</td></tr><tr><td>Trace есть, событие не объясняет отказ</td><td>Log не содержит span ID, событие относится к другому шагу или потеряно при sampling</td><td>Сверить trace ID, span ID, имя события и время</td><td>Исправить корреляцию; не объявлять downstream причиной</td></tr><tr><td>Свободный текст не ищется стабильно</td><td>Сообщение меняется между версиями и не имеет event name</td><td>Проверить структурированные поля и стабильное имя события</td><td>Добавить минимальную схему и сохранить текст как дополнительный context</td></tr><tr><td>Учебный тест зелёный, production неизвестен</td><td>Проверили форму записей в памяти, а не экспорт и поиск</td><td>Отделить fixture от реальной выборки и явно отметить границу</td><td>Назначить проверку в разрешённой среде; не публиковать результат как incident evidence</td></tr></tbody></table>\n<h2>Конкретный пример</h2>\n<p>Ниже — классификатор учебных записей. Он проверяет только договор между объектами в памяти. Значения <code>synthetic-*</code> выдуманы для примера. Код не обращается к приложению, не создаёт telemetry и не подтверждает, что downstream действительно вернул ошибку.</p>\n<pre><code>function checkRoute({ metric, trace, event }) {\n const labels = Object.keys(metric.labels);\n const allowed = ['service', 'route', 'outcome'];\n const metricShape = labels.length === 3\n && labels.every((name) => allowed.includes(name))\n && !labels.includes('trace_id');\n const traceShape = trace.root.traceId === trace.payment.traceId;\n const eventShape = event.traceId === trace.payment.traceId\n && event.spanId === trace.payment.spanId;\n return {\n metricShape,\n traceShape,\n eventShape,\n readyForRealCheck: metricShape && traceShape && eventShape,\n };\n}\n\nconst result = checkRoute({\n metric: { labels: { service: 'checkout', route: 'authorization', outcome: 'rejected' } },\n trace: {\n root: { traceId: 'synthetic-trace-1' },\n payment: { traceId: 'synthetic-trace-1', spanId: 'synthetic-span-payment' },\n },\n event: { traceId: 'synthetic-trace-1', spanId: 'synthetic-span-payment' },\n});\n\nconsole.log(result);\n// readyForRealCheck: true — только договор synthetic-записей.</code></pre>\n<p>Отрицательный путь важнее зелёного результата. Если event получит другой trace ID, <code>eventShape</code> станет false. Если в metric появится <code>trace_id</code>, <code>metricShape</code> станет false. Код не угадывает причину и не исправляет систему. Он останавливает вывод: сначала нужно восстановить связь или признать, что её нет.</p>\n<figure><img src=\"/assets/editorial/2023/telemetry-signals-2023-diagnosis-route.svg\" alt=\"Учебный маршрут диагностики от metric через trace к log/event и evidence с перечёркнутым trace ID в labels метрики\" loading=\"lazy\" /><figcaption>Учебная схема разделяет вопросы сигналов. Она не изображает реальный alert, запрос к backend, trace search или подтверждённую production-причину.</figcaption></figure>\n<h2>Как читать три сигнала вместе</h2>\n<p>Metric полезна на первом шаге, потому что сжимает поток в устойчивые классы. Используйте route template и outcome, а не полный URL, user ID, order ID или текст ошибки. Набор dimensions должен быть заранее ограничен. Точное число допустимых series зависит от платформы, окна и числа значений, поэтому его нельзя объявлять безопасным без расчёта и проверки владельца backend.</p>\n<p>Trace нужен, когда вопрос перешёл от класса к пути. Найдите один разрешённый trace и проверьте дерево spans: gateway должен вести к payment operation, а не просто соседствовать с ней по времени. Сверьте parent-child связь, статус, длительность и границы sampling. Даже полный trace показывает путь инструментирования, а не автоматически истинную причину бизнес-ошибки.</p>\n<p>Log/event нужен для контекста шага. Структурированное событие должно иметь стабильное имя, время, trace ID и, если событие связано с конкретной операцией, span ID. Дополнительные attributes должны пройти review на чувствительные данные, redaction, retention и права доступа. «Добавим весь request на всякий случай» — плохая стратегия: она увеличивает риск и не делает гипотезу точнее.</p>\n<h2>Порядок действий</h2>\n<ol><li>Опишите симптом одним предложением: какой класс исходов изменился, в каком route и за какое окно.</li><li>Назовите ожидаемый переход metric → trace. Проверьте, что metric не содержит per-request labels и использует малый словарь service, route, outcome.</li><li>Выберите один разрешённый trace. Сверьте trace ID, root span, дочерний span и время операции. Не делайте вывод по одному графику.</li><li>Найдите log/event на конкретном span. Проверьте event name, trace ID, span ID и отсутствие лишних чувствительных полей.</li><li>Прогоните отрицательные проверки: mismatch trace ID, mismatch span ID, лишний label и отсутствие event. Каждый случай должен останавливать вывод.</li><li>Сформулируйте действие только после проверки связи. Если trace или event отсутствует, исправляйте instrumentation и экспорт, а не таймаут downstream.</li><li>Повторите проверку тем же route, окном и правилом выборки. Сравните стоимость series, доступность поиска и соседние сигналы.</li><li>Запишите результат как подтверждённый, неподтверждённый или неполный. Не называйте synthetic PASS наблюдением production.</li></ol>\n<h2>Когда остановиться и что откатывать</h2>\n<p>Если новый label резко расширяет cardinality или event содержит запрещённое поле, остановите распространение изменения. Сначала определите, какие записи ещё могут появляться и какие потребители уже зависят от схемы. Затем выберите обратимое действие для конкретной конфигурации: отключить добавленный label, ограничить event attributes или вернуть предыдущую версию instrumentation. Нельзя обещать удаление уже сохранённых данных, пока не известны storage, retention и политика доступа.</p>\n<p>Если metric уже есть, а trace не связывается, не добавляйте ещё один ID в счётчик. Проверьте propagation на границе сервиса, sampling, exporter и возможность поиска. Если log не содержит span ID, назовите это дефектом корреляции. Если настоящая система не позволяет безопасно проверить путь, остановите расследование на статусе «не подтверждено» и не заменяйте evidence догадкой.</p>\n<h2>Ограничения и критерий готовности</h2>\n<p>Пример не содержит реальных logs, metrics, traces, latency, traffic, backend records или incident data. Synthetic value и IDs не являются измерениями. Статья не утверждает, что конкретная SDK, collector, exporter или backend поддерживает одинаковые поля и поиск. Sampling может скрыть часть trace. Асинхронная очередь может разорвать контекст. Событие может прийти позже операции. Эти условия нужно проверять в выбранном контуре.</p>\n<p>Критерий готовности проверяемый: для одного разрешённого route есть metric с заранее названными dimensions; для выбранного outcome найден trace с тем же correlation key; trace содержит ожидаемый span; log/event имеет тот же trace ID и корректный span ID; отрицательные ветки дают отказ; после изменения не выросли запрещённые labels и не появились чувствительные поля. Если хотя бы одна связь не доказана, итог — неполный.</p>\n<h2>Проверяемые источники</h2><ul><li><a href=\"https://opentelemetry.io/docs/concepts/signals/\" target=\"_blank\" rel=\"noopener noreferrer\">OpenTelemetry: Signals</a> — официальное описание ролей traces, metrics и logs. Страница не подтверждает вашу instrumentation, sampling или backend-поиск.</li><li><a href=\"https://opentelemetry.io/docs/specs/otel/metrics/data-model/\" target=\"_blank\" rel=\"noopener noreferrer\">OpenTelemetry Metrics Data Model</a> — официальная модель metric streams и aggregation. Она не задаёт безопасный cardinality-бюджет для конкретной системы.</li><li><a href=\"https://opentelemetry.io/docs/specs/otel/logs/data-model/\" target=\"_blank\" rel=\"noopener noreferrer\">OpenTelemetry Logs Data Model</a> — официальные поля log record, включая TraceId и SpanId. Она не гарантирует, что конкретный exporter сохранит или покажет каждое поле.</li></ul>"
|
||
}
|