Трейс сшивается только тогда, когда контекст доезжает от сервиса к сервису. Здесь живёт 90% проблем «трейсы есть, но разорванные»: чужие прокси, очереди без заголовков, потоки без контекста.
Механика: extract / inject
Библиотеки делают две операции:
- inject: при исходящем вызове сериализовать текущий span context в носитель (заголовки HTTP, свойства сообщения);
- extract: при входящем — прочитать контекст и сделать его родителем нового спана.
Всё автоматическое инструментирование (из урока 2.2) делает это за вас. Проблемы начинаются там, где библиотеки нет:
# Python: вручную для кастомного HTTP-клиента
from opentelemetry.propagate import inject, extract
headers = {}
inject(headers) # положит traceparent (+baggage)
requests.get(url, headers=headers)
# на стороне получателя:
ctx = extract(request.headers)
with tracer.start_as_current_span("process", context=ctx):
Пропагаторы
Формат заголовков задаётся пропагаторами. По умолчанию в OTel — W3C TraceContext + W3C Baggage. Для совместимости с legacy-системами:
- B3 (Zipkin):
X-B3-TraceId,X-B3-SpanId… — встречается повсюду в старых системах; - Jaeger:
uber-trace-id; - Пропагаторы комбинируются:
tracecontext,baggage,b3multi— сервис поймает оба формата. Отправка — в оба тоже (лишние заголовки безвредны).
# Java-агент: W3C + B3 вместе
-Dotel.propagators=tracecontext,baggage,b3multi
Где контекст рвётся: чек-лист
- Обратные прокси/LB: nginx/envoy не вырезают заголовки по умолчанию, но кастомные
proxy_set_headerили очищающие фильтры — запросто; - Сторонние HTTP-клиенты без инструментирования: ручной
urllib,net/httpбез wrapper, экзотические SDK — трейс разорвётся; - Очереди: контекст кладут в свойства сообщения; если продюсер/консюмер без инструментирования — разрыв (подробно — урок 6.2);
- Асинхронность в самом процессе: thread pools, executor'ы, горутины — контекст не передаётся «сам»: Java-агент умеет в Kotlin coroutines/executor'ы частично, в остальных случаях передавайте Context явно;
- Планировщики (cron): у фоновых задач нет входящего запроса — заводите корневой спан
сами (
scheduler.run-report); - gRPC metadata vs HTTP headers: разные носители, но одна идея.
Диагностика разрывов
- Откройте «разорванный» трейс: где именно дерево кончается?;
- Проверьте на границе: доходит ли
traceparent? (логируйте заголовки одного запроса или ловите tcpdump/mitmproxy на деве); - Если доходит, но не срастается — вероятно, получатель не извлекает (нет инструментария фреймворка) или другой пропагатор (B3 vs W3C);
- Если не доходит — ищите очистку заголовков на прокси/клиенте.
Практика
- Разорвите трейс намеренно: сделайте вызов B неинструментированным клиентом (голый requests без instrumentation) — увидите два отдельных трейса.
- Почините вручную через inject/extract из этого урока.
- Переключите пропагатор на b3multi на одном сервисе и убедитесь, что W3C-соседи перестали сшиваться; верните комбинированный список.
- Поставьте nginx перед сервисом B, добавьте
proxy_set_header Traceparent "";— воспроизведите «прокси режет заголовки» и найдите это по UI.
Мини-квиз
1. Инструментированы оба сервиса, трейс всё равно разорван. Первая гипотеза? — Заголовок не доходит: прокси/клиент между ними, либо разные пропагаторы.
2. Можно ли включить два пропагатора одновременно? — Да, список через запятую; чтение попытается всеми, запись — запишет всеми.
3. Как сшить трейс для cron-задачи? — Создать корневой спан с именем задачи — контекста извне нет и не нужно.