Суть Прочитай traceparent-заголовок, сломанный сниппет async-propagation, пару producer/consumer с inject и tail-sampling-конфиг — и выбери поведение и фикс с наибольшим рычагом.
Высота — путь к senior
НольJuniorMiddleSenior
Ты на senior-высоте — в орбите
◷ 14 min
Баги propagation живут в заголовках, коде и конфигах — не в прозе. Читай каждый артефакт так, как читал бы в инциденте, и выбирай фикс, к которому senior-инженер потянется первым.
Цель
Отработай диагностический цикл любого инцидента propagation: распарси заголовок на проводе, найди, где context тихо теряется, и прочитай конфиг коллектора, решающий, какие трейсы выживут.
Заголовок корректен. Что говорит хвостовой 00 принимающему сервису делать по умолчанию?
Heads-up Ведущий 00 — это версия; хвостовой 00 — это trace-flags. Версия 00 — просто текущий формат, никакого понижения. Вопрос про sampled-флаг.
Heads-up parent-id здесь 00f067aa0ba902b7, не ноль. Хвостовой 00 — это байт trace-flags: sampled-бит сброшен.
Heads-up traceparent не кодирует число span, а сброшенный sampled-флаг означает, что вышестоящий уже решил дропнуть. Переопределение на 100% дало бы частичный trace.
Span audit-log появляется как собственный корневой trace, оторванный от /checkout. В чём причина и минимальный фикс?
Heads-up Граница — это setTimeout, а не sync/async-природа функции. Даже синхронный writeAuditLog внутри таймера бежит на новом стеке без context. Фикс — context.bind, а не убирание async.
Heads-up inject/extract — для межпроцессных носителей. Работа audit бежит в том же процессе; потерянный context восстанавливается через context.bind, а не инъекцию в заголовки.
Heads-up Порядок ответа не влияет на context отложенного колбэка. Span осиротел, потому что колбэк таймера потерял активный context, независимо от момента отправки ответа.
Producer инъектит корректно, но span консьюмера всё равно orphan'ы. Где баг?
Heads-up Kafka record headers (с 0.11) — стандартный носитель и не срезаются. Сторона producer корректна; консьюмер просто никогда не делает extract.
Heads-up context.bind оживляет context внутри процесса, но процесс консьюмера стартовал без него — context живёт в заголовках сообщения. Его надо extract'ить, а не bind'ить.
Heads-up Обе стороны используют один composite-propagator. Round-trip падает из-за отсутствия extract на консьюмере, а не из-за несовпадения propagator'ов.
Этот tail-sampling-конфиг OOM-нул коллектор при всплеске трафика. Какая одна самая важная отсутствующая строка?
Heads-up Latency-политика желательна, но она фильтрует в момент решения; она не ограничивает буферизуемую память. Отсутствующая защита от OOM — это потолок num_traces.
Heads-up Load-balancing-экспортёр — это exporter на вышестоящем ярусе, а не processor здесь, и он решает маршрутизацию span между репликами — а не одноинстансный OOM, который ограничивает num_traces.
Итог
Любой инцидент propagation читается в артефактах: байт trace-flags говорит, был ли сэмплирован корректный заголовок (а сброшенный флаг означает дроп всего trace, а не экспорт фрагмента); необёрнутый setTimeout осиротит внутрипроцессную работу, пока ты не сделаешь context.bind; Kafka round-trip требует inject на producer и парный extract на consumer; а tail-sampling-конфиг без num_traces буферизует неограниченно до OOM. Распарси заголовок, найди потерянный context, прочитай конфиг — и чини на границе, а не на дашборде.