Проблема редко выглядит как «у нас нет observability». Обычно есть три несвязанных окна: график показывает рост ошибок, поиск показывает отдельные сообщения, а трасса либо не находится, либо не объясняет тот же запрос. В такой схеме инженер получает три правдоподобных, но несопоставимых факта. Цена — не только лишние минуты поиска. Команда может изменить retry, timeout или маршрут, не доказав, что относится к причине исходного сбоя.
Вторая ошибка появляется как быстрый ремонт: положить request ID, user ID или trace ID в labels каждой метрики, чтобы график стал поиском. Тогда граница между счётчиком и записью одного события исчезает. Она создаёт проектный риск неограниченного числа сочетаний dimensions; но эта статья не измеряет реальную cardinality, storage или цену какого-либо backend. Здесь важнее сначала назвать, какой сигнал отвечает на какой вопрос, и оставить один общий ключ только там, где он нужен.
Один сценарий, четыре разных предмета
Возьмём не production-инцидент, а фиксированный учебный сценарий `synthetic-checkout-authorization-rejected`. Он содержит два шага пути: gateway принял запрос, payment отказал в авторизации. У обоих шагов общий `synthetic-trace-2023-09-A`; у каждого — свой span ID. Рядом лежат одна synthetic metric record, одна synthetic log/event record и короткая карточка evidence. Их не следует склеивать в универсальный JSON: у каждого предмета своя задача и свой допустимый объём контекста.
| Представление | Вопрос | Ключ и поля | Чего не утверждает |
|---|---|---|---|
| Trace | какой причинный путь предусмотрен? | trace ID + два span ID | что trace был создан, передан или сохранён |
| Metric | какой счётчик допустимо группировать? | service, route template, outcome | какой пользователь, заказ или конкретный request вызвал точку |
| Log/event | что случилось на одном шаге? | trace ID, span ID, event name, event attributes | что запись доставлена, индексирована или найдена |
| Evidence | какой вывод разрешён из модели? | сценарий + вопрос к каждому сигналу | что production уже наблюдался или исправление сработало |
Trace ID — ключ маршрута, не label счётчика
Trace ID нужен, когда надо связать записи, относящиеся к одному распределённому пути. В OpenTelemetry `SpanContext` выделяет TraceId и SpanId; tagged specification v1.20.0 уже описывала их перенос как часть distributed context. Это не правило «добавляйте ID во все поля». Для metric record общий ID одного запроса почти всегда слишком детален для вопроса «сколько раз произошёл тип отказа по маршруту». Его место — trace и связанный log/event record, где поиск одного сценария имеет смысл.
У метрики другой договор. В этой учебной модели есть ровно три labels: `service=synthetic-checkout-api`, `route=synthetic-checkout` и `outcome=synthetic-rejected`. Они описывают небольшой фиксированный набор вариантов. Нельзя считать набор универсальным: реальные service name, route template, environment и outcome надо обсуждать с владельцем backend и budget. Но уже на проектировании можно отделить label с малым словарём от значения, которое почти меняется на каждый запрос: trace ID, request ID, user ID, order ID, текст ошибки или сырая строка URL.
Log attributes несут объяснение события
Log/event record в модели хранит `eventName=synthetic.payment.authorization-rejected`, тот же trace ID, span ID payment-шага и три event attributes: класс synthetic отказа, домен события и совет по retry. Эти поля не обязаны стать labels метрики. Они нужны, чтобы при переходе от причины к одному событию не потерять смысл. В реальном проекте часть таких полей может быть чувствительной, слишком подробной или нестабильной; тогда набор нужно сократить или изменить. Учебный пример не утверждает, что его имена являются semantic conventions или что они доступны в поиске.
Evidence — четвёртый предмет, который часто пропускают. Он не копия log и не вывод из одной точки графика. Это карточка «мы хотим проверить именно такой сценарий; trace должен объяснять путь, metric — считать небольшой класс, log — описывать отказ». Пока нет наблюдения в разрешённой среде, conclusion остаётся `not-a-production-observation`. Такая запись делает неизвестность видимой и не позволяет превратить fixture в отчёт о production.
Исполнимый fixture проверяет только форму договора
Ниже — весь учебный вход. Он создаётся при импорте в памяти, принимает ровно зафиксированный список полей со строками с префиксом `synthetic-` и не вызывает SDK, exporter, сеть, collector, storage или clock. В нём нет настоящих request ID, пользователей, latency, telemetry records и реального trace context. Положительная ветка собирает две synthetic span-записи; отрицательные ветки отвергают разный trace ID в log, иной span ID, лишнее top-level поле и попытку положить trace ID или user ID в labels метрики.
import {
assembleSyntheticTelemetryScenario,
runTelemetryFixture,
} from './upgrade-2023-09.mjs';
const scenario = assembleSyntheticTelemetryScenario({
synthetic: true,
traceId: 'synthetic-trace-2023-09-A',
rootSpanId: 'synthetic-span-gateway-A',
downstreamSpanId: 'synthetic-span-payment-A',
logTraceId: 'synthetic-trace-2023-09-A',
logSpanId: 'synthetic-span-payment-A',
eventName: 'synthetic.payment.authorization-rejected',
metricLabels: {
service: 'synthetic-checkout-api',
route: 'synthetic-checkout',
outcome: 'synthetic-rejected',
},
});
const report = runTelemetryFixture();
if (!Object.values(report.assertions).every(Boolean)) throw new Error('fixture failed');
console.log(scenario.evidence.conclusion); // not-a-production-observation
// Не создаёт trace/log/metric, не запускает SDK и не отправляет данные.
node web/scripts/upgrade-2023-09.mjs --verify-fixture
# PASS означает только: synthetic contract непротиворечив.
# PASS не означает: telemetry собрана, связи видны или cardinality приемлема.
Полезная деталь fixture — metric никогда не получает `trace_id`. Вместо этого объект содержит строку `intentionally-absent-from-labels`. Это не скрытая проверка backend и не запрет для всех существующих систем. Это явное решение маленького контракта. Если в review появится новый label, нужно назвать его словарь, владельца, потребителя и причину, по которой он не является идентификатором отдельного запроса. Если ответа нет, поле остаётся log attribute или вовсе не попадает в этот сценарий.
Маршрут: симптом → причина → проверка → действие
- Симптом. Error-count можно увидеть отдельно, но нельзя объяснить один запрос: log не содержит общего trace ID или trace не связан с событием.
- Причина. Три сигнала проектировали как независимые поля; точный identifier перенесли в labels метрики, а смысл события остался в свободном тексте.
- Проверка. На одном synthetic сценарии заполните таблицу: два span, один trace ID, один log/event с тем же trace ID и маленький набор metric labels. Прогоните fixture и убедитесь, что он отвергает mismatch и request-like labels.
- Действие. Зафиксируйте correlation contract: trace ID связывает маршрут и событие, labels отвечают только на вопрос агрегации, event attributes объясняют конкретный отказ.
- Проверка границы. Для каждого нового поля спросите: это словарь из заранее известных значений или значение отдельного запроса? Второе не добавляйте в metric labels без отдельного обоснования и измерения.
- Следующий шаг. В разрешённой среде выбрать один реальный маршрут и заранее определить, какой безопасный запрос или dashboard подтвердит каждую связь. Этот fixture такого подтверждения не делает.
Rollback возвращает договор, а не данные
Rollback в коде восстанавливает только snapshot four synthetic fields: trace ID, два span ID и metric labels. Он не удаляет trace, log или metric, потому что их не создаёт. Он не отменяет экспорт, retention, alert, dashboard, изменение sampling или production-release. Это важно проговорить до того, как слово «откат» попадёт в runbook. В реальном контуре сначала надо узнать, какое изменение данных или конфигурации сделано, какие записи уже существуют и какой владелец отвечает за обратимое действие.
Если correlation contract оказался плохим, безопаснее сначала прекратить расширение полей и вернуться к последней понятной схеме. Не следует массово копировать user ID в logs, чтобы «компенсировать» отсутствие связи: это другое решение с отдельными privacy, retention и access условиями. Маленький rollback здесь полезен как напоминание: отмена учебной модели не решает операционный вопрос и не доказывает, что какая-либо telemetry исчезла.
Ограничения и следующий шаг
Статья и fixture не собирают реальные telemetry, latency, cardinality, trace, logs или metrics. Они не читают приложение и не показывают рост ошибок. Они не посылают данные, не создают SDK provider, exporter, collector, index, alert или dashboard и не доказывают observability либо production effect. OpenTelemetry sources объясняют термины и версии на сентябрь 2023, но не дают проекту готовый набор labels или схему хранения.
Следующий рабочий шаг — не «включить всё». Выберите один владелец маршрута, один сценарий отказа и один вопрос к каждому представлению. Запишите допустимые metric labels в маленький contract, а event attributes отделите от них. Затем согласуйте минимальный безопасный способ проверить эту схему в реальной среде. Если общий trace ID не доходит до границы процесса, зафиксируйте это как пробел: не заменяйте отсутствующую связь новым высоко-кардинальным label.
Историческая граница сентября 2023
К сентябрю 2023 уже был доступен OpenTelemetry Specification release v1.20.0 от 7 апреля 2023. Для исторической честности здесь используются только его термины trace, SpanContext, metric data model и log data model. Текст не предполагает, что любая конкретная language SDK, collector, backend или поздняя semantic convention уже присутствовала у автора. М6-автор строит договор и путь проверки, а не объявляет зрелость системы по названию инструмента.
Проверяемые источники
- OpenTelemetry Specification: Release v1.20.0, 7 апреля 2023 — официальный versioned release, доступный к сентябрю 2023. Версия фиксирует историческую рамку статьи, но не подтверждает, что конкретная система экспортирует или хранит telemetry.
- OpenTelemetry Specification v1.20.0: Overview — первичный обзор разделяет tracing, metrics, logs, resources и context propagation. Он не предписывает один backend, dashboard или retention policy.
- OpenTelemetry Specification v1.20.0: Tracing API — описывает SpanContext, TraceId и SpanId как данные, которые могут передаваться в distributed context. Само наличие поля в учебной записи не означает, что контекст дошёл через реальный transport.
- OpenTelemetry Specification v1.20.0: Metrics Data Model — различает события, streams, time series и attributes. В статье слово label применяется к маленькому договору dimension values; он не измеряет фактическую cardinality какого-либо backend.
- OpenTelemetry Specification v1.20.0: Logs Data Model — описывает LogRecord, включая TraceId, SpanId и Attributes. Он не доказывает, что event/log запись конкретного сервиса доставлена, индексирована или доступна в поиске.