8 lines
20 KiB
JSON
8 lines
20 KiB
JSON
{
|
||
"index": 154,
|
||
"slug": "editorial-2023-09-field-telemetry-signals",
|
||
"title": "Ошибка без причины: маршрут диагностики через log, metric и trace",
|
||
"excerpt": "Пошаговый маршрут, который не подменяет один correlation ID новой label-кардинальностью: как разложить симптом, гипотезу и evidence между metric, trace и log/event record.",
|
||
"contentHtml": "<p>Симптом для диагностики звучит знакомо: график ошибок показывает изменение, но инженер не может назвать запрос и этап, на котором оно возникло. В ответ часто начинают искать текст исключения во всех logs или добавляют request ID в metric labels. Первый путь тонет в несвязанных записях, второй смешивает счётчик с идентичностью одного запроса. Причина не становится ближе: у трёх источников нет договора, который превращает один сигнал в вопрос к следующему.</p>\n<p>Цена такого разрыва — решение на основании наиболее громкой витрины. Можно увеличить timeout, включить retry или объявить downstream виновником, хотя связь между error count, span и event не подтверждена. Эта статья не расследует реальный инцидент и не собирает telemetry. Она строит безопасный diagnostic route для одного fixed synthetic сценария, чтобы показать: evidence одного отказа складывается из разных объектов, а не из максимального количества labels.</p>\n<h2>Начните не с поиска, а с вопроса</h2>\n<p>У диагностики есть три уровня. Metric помогает сформулировать, какой класс исходов стоит рассматривать: например, synthetic `outcome=synthetic-rejected` для synthetic checkout route. Trace должен показать предполагаемый причинный путь из gateway к payment шагу. Log/event record должен назвать событие на payment step и сохранить тот же correlation key. Только после этого появляется evidence-card: она говорит, какую гипотезу можно проверить и чего пока нет. Ни один из объектов по отдельности не заменяет остальные.</p>\n<div class=\"table-scroll\"><table><caption>Маршрут вопросов вместо бесконечного поиска</caption><thead><tr><th scope=\"col\">Очередь</th><th scope=\"col\">Вопрос</th><th scope=\"col\">Нужное представление</th><th scope=\"col\">Допустимый результат</th><th scope=\"col\">Что не делать</th></tr></thead><tbody><tr><td>1</td><td>какой класс результата разбираем?</td><td>metric labels</td><td>synthetic route + outcome</td><td>не добавлять request ID ради фильтра</td></tr><tr><td>2</td><td>какой путь должен ему соответствовать?</td><td>trace + span tree</td><td>один synthetic trace ID, два шага</td><td>не считать график доказательством причины</td></tr><tr><td>3</td><td>какое событие произошло на шаге?</td><td>log/event record</td><td>event name + trace ID + span ID</td><td>не искать по свободному тексту без correlation</td></tr><tr><td>4</td><td>какой вывод честен?</td><td>evidence card</td><td>not-a-production-observation</td><td>не объявлять hypothesis подтверждённой fixture-ом</td></tr></tbody></table></div>\n<figure><img src=\"/assets/editorial/2023/telemetry-signals-2023-diagnosis-route.svg\" alt=\"Учебный маршрут диагностики: от synthetic metric класса через общий trace ID к payment span и synthetic event/log record, затем к карточке evidence с явным статусом not-a-production-observation; ветка trace ID как metric label перечёркнута.\" loading=\"lazy\" /><figcaption>Диаграмма показывает порядок вопросов и границы вывода для synthetic записи. Она не изображает реальный alert, dashboard, запрос к backend, trace search, latency или подтверждённую причину production-сбоя.</figcaption></figure>\n<h2>Metric даёт границу разбора, а не виновника</h2>\n<p>В учебном наборе metric record содержит имя `synthetic.checkout.authorization.rejected.total`, значение `1` и три labels. Значение `1` — не измеренный в системе counter, а фиксированная часть fixture. Оно нужно только чтобы показать форму: одна маленькая точка может обозначать класс outcome. По ней нельзя определить user, order, request или span. Такую границу полезно сохранять даже если UI backend позволяет кликнуть на dimensions: возможность фильтра не превращает metric в достоверный журнал событий.</p>\n<p>Если на первом шаге неизвестно, какой вопрос нужно решить, не пополняйте labels «на всякий случай». Сначала назовите route template и outcome class, которые должны быть малым словарём. Затем спросите владельца инструмента, какая реальная единица агрегации поддерживается, какие resource attributes добавляются и где будет измеряться cardinality. Без ответа status должен быть «не проверено», а не «у нас низкая cardinality». Fixture помогает удержать именно эту дисциплину: лишний `trace_id`, `request_id` или `user_id` он отвергает до того, как поле станет привычным.</p>\n<h2>Trace связывает причины, log/event фиксирует контекст</h2>\n<p>Дальше мы идём по `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 возвращает отказ. Это простое правило полезнее длинного списка полей: событие должно либо объяснять конкретный шаг пути, либо честно оставаться несвязанным.</p>\n<p>Event attributes нужны для узкой диагностики события. В примере есть `failure.class=synthetic-declined` и `retry.advice=synthetic-do-not-retry`. Они не говорят, как надо обрабатывать настоящие платежи, и не являются production error message. Их роль — показать разницу между типом отказа и точной идентичностью запроса. В реальном проекте перед добавлением любых attributes нужно отдельно решить privacy, возможность redaction, retention, доступ к поиску и стабильность названий. Нельзя прятать эти решения под словом «контекст».</p>\n<h2>Прогоните одну контролируемую модель</h2>\n<p>Код ниже создаёт 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 можно найти.</p>\n<pre><code>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.</code></pre>\n<p>После локального прогона полезно записать evidence без переобобщения. Корректная формулировка: «модель ожидает, что один correlation key соединяет заданный payment event и заданный trace; metric использует только три fixed dimensions». Некорректная: «причина ошибки найдена» или «metric безопасна для production». Разница кажется формальной только пока первое решение не затронуло retry, alert policy или пользовательские данные. В инженерном разборе неизвестное — это тоже результат, который должен пережить передачу задачи.</p>\n<h2>Маршрут: симптом → причина → проверка → действие</h2>\n<ol><li><strong>Симптом.</strong> Есть числовой признак класса ошибок, но нет понятного перехода к одному пути запроса и событию, которое его объясняет.</li><li><strong>Причина.</strong> Metric, trace и log/event живут без общего contract: метрика получила per-request labels, а log не несёт trace/span correlation.</li><li><strong>Проверка.</strong> Выберите один synthetic outcome. Проверьте, что metric содержит только service/route/outcome, trace имеет один ID и два шага, а log/event повторяет trace ID и downstream span ID. Запустите fixture с отрицательными ветками.</li><li><strong>Действие.</strong> Зафиксируйте маршрут metric → trace → log/event → evidence. Поставьте trace ID в correlation fields, а не в labels метрики; детали оставьте в атрибутах события только после отдельного policy review.</li><li><strong>Проверка вывода.</strong> В реальном контуре заранее назовите, какой query или безопасная выборка может подтвердить каждую стрелку. Пока она не выполнена, conclusion остаётся не подтверждённым.</li><li><strong>Следующий шаг.</strong> Добавьте в runbook одну ветку mismatch: что делать, если metric есть, но trace или log не связываются. Это отдельная проблема instrumentation, а не приглашение добавить новый ID в счётчик.</li></ol>\n<h2>Когда останавливать, а когда откатывать</h2>\n<p>Если на review обнаружился label с высокой кардинальностью или несвязанный event, первое действие — остановить распространение нового контракта. Это не равно удалить все данные: нельзя обещать удаление, не зная платформы, retention, доступа и состоявшегося rollout. Затем нужно отделить два вопроса: какие новые записи могут продолжать возникать и какие потребители уже зависят от этого поля. Только после этого владелец выбирает обратимое действие для конкретной конфигурации.</p>\n<p>Fixture умеет только вернуть snapshot synthetic полей и пометить `telemetry=not-created-or-deleted`. Он не выключает instrumentation, не меняет sampling, alert, dashboard или access policy. Такой rollback не декоративен: он ставит границу между проверкой модели и операционным изменением. В реальном runbook точка возврата должна быть названа точнее: версия конфигурации, набор approved fields, способ проверить отсутствие дальнейшего потока и владелец подтверждения.</p>\n<h2>Ограничения и следующий шаг</h2>\n<p>Здесь нет реальных 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 или маршруты допустимы для хранения. Он только различает роли полей и показывает, какую связь надо проверить позднее.</p>\n<p>Следующий шаг — провести ограниченное design review одного изменения инструментирования. Договоритесь о: одном вопросе к метрике, одном route template, малом outcome vocabulary, одном correlation key и минимальном event schema. Затем выберите реальную разрешённую среду и способ проверить путь без публикации чувствительных значений. Если итогом окажется, что trace context не проходит конкретную границу, это не поражение модели: это точная задача для следующего изменения, а не основание расширять cardinality метрики.</p>\n<h2>Историческая граница сентября 2023</h2>\n<p>Материал использует только OpenTelemetry Specification v1.20.0, опубликованную 7 апреля 2023 и доступную к сентябрю того года. Она уже описывала tracing API, metrics data model и logs data model, на которые опирается различение сигналов. Статья не утверждает, что конкретная SDK, transport, collector или backend имеют одинаковую зрелость, и не переносит в 2023 год более поздние договорённости команды или инструмента.</p>\n<h2>Проверяемые источники</h2>\n<ul><li><a href=\"https://github.com/open-telemetry/opentelemetry-specification/releases/tag/v1.20.0\" target=\"_blank\" rel=\"noopener noreferrer\">OpenTelemetry Specification: Release v1.20.0, 7 апреля 2023</a> — официальный versioned release, доступный к сентябрю 2023. Версия фиксирует историческую рамку статьи, но не подтверждает, что конкретная система экспортирует или хранит telemetry.</li><li><a href=\"https://github.com/open-telemetry/opentelemetry-specification/blob/v1.20.0/specification/overview.md\" target=\"_blank\" rel=\"noopener noreferrer\">OpenTelemetry Specification v1.20.0: Overview</a> — первичный обзор разделяет tracing, metrics, logs, resources и context propagation. Он не предписывает один backend, dashboard или retention policy.</li><li><a href=\"https://github.com/open-telemetry/opentelemetry-specification/blob/v1.20.0/specification/trace/api.md\" target=\"_blank\" rel=\"noopener noreferrer\">OpenTelemetry Specification v1.20.0: Tracing API</a> — описывает SpanContext, TraceId и SpanId как данные, которые могут передаваться в distributed context. Само наличие поля в учебной записи не означает, что контекст дошёл через реальный transport.</li><li><a href=\"https://github.com/open-telemetry/opentelemetry-specification/blob/v1.20.0/specification/metrics/data-model.md\" target=\"_blank\" rel=\"noopener noreferrer\">OpenTelemetry Specification v1.20.0: Metrics Data Model</a> — различает события, streams, time series и attributes. В статье слово label применяется к маленькому договору dimension values; он не измеряет фактическую cardinality какого-либо backend.</li><li><a href=\"https://github.com/open-telemetry/opentelemetry-specification/blob/v1.20.0/specification/logs/data-model.md\" target=\"_blank\" rel=\"noopener noreferrer\">OpenTelemetry Specification v1.20.0: Logs Data Model</a> — описывает LogRecord, включая TraceId, SpanId и Attributes. Он не доказывает, что event/log запись конкретного сервиса доставлена, индексирована или доступна в поиске.</li></ul>"
|
||
}
|