Files
progcode/editorial/agent-rewrites/154.json
T
huncode 2d914b543f
Build and deploy / deploy (push) Failing after 15s
Publish rewritten technical article archive
2026-08-02 22:19:34 +03:00

8 lines
17 KiB
JSON
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
{
"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 &amp;&amp; labels.every((name) =&gt; allowed.includes(name))\n &amp;&amp; !labels.includes('trace_id');\n const traceShape = trace.root.traceId === trace.payment.traceId;\n const eventShape = event.traceId === trace.payment.traceId\n &amp;&amp; event.spanId === trace.payment.spanId;\n return {\n metricShape,\n traceShape,\n eventShape,\n readyForRealCheck: metricShape &amp;&amp; traceShape &amp;&amp; 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>"
}