{"index":7,"slug":"editorial-2027-10-field-long-form-interview","title":"Trace Context в HTTP: как сохранить запрос на границе proxy","excerpt":"Если traceparent исчезает или не проходит проверку, gateway и сервис перестают видеть один запрос. Разбираем формат, безопасный fallback и проверку через реальный proxy.","contentHtml":"
Симптом появляется во время сбоя: gateway сообщает timeout, backend пишет 500, а записи нельзя связать одним запросом. Инженер видит несколько похожих событий и не понимает, относятся ли они к одной операции. Цена ошибки — лишний поиск по логам, неверная гипотеза о медленном сервисе и более долгий разбор пользовательской проблемы.
Часто ломается не сама трассировка, а граница между компонентами. Proxy удаляет неизвестный заголовок, middleware создаёт новый trace вместо продолжения, либо сервис принимает строку с неверным форматом. Исправление должно проверять весь путь: что отправили, что пропустил proxy, что разобрал сервис и что записал logger.
W3C Trace Context задаёт переносимый HTTP-заголовок traceparent. Он связывает участки обработки одного запроса, но не гарантирует наличие span, корректность логирования или сохранность заголовка на каждом proxy. Поэтому проверять нужно не только парсер. Нужен контракт между клиентом, gateway, middleware и сервисом.
Валидный внешний контекст можно продолжить как родительский. После этого локальная библиотека создаёт новый span для текущего сервиса. Невалидный контекст нельзя молча чинить: сервис отбрасывает его, создаёт новый локальный trace и фиксирует короткую причину отказа. Trace-id не заменяет авторизацию и не должен содержать пользовательские данные.
traceparent состоит из четырёх полей: версии, trace-id, parent-id и flags. Для базового формата важны точные длины, lowercase hexadecimal и ненулевые идентификаторы. Ошибка в одном поле делает вход непригодным для продолжения. Парсер должен вернуть результат проверки, а не угадывать намерение отправителя.
Proxy — часть этого контракта. Он может удалить неизвестный заголовок, нормализовать имя, ограничить размер или создать собственный контекст. Локальный тест функции парсинга не доказывает, что значение дошло до приложения. Проверка должна проходить через ту же пару gateway и service, с которой работает запрос.
Даже сохранённый trace-id не объясняет задержку сам по себе. Для поиска места ожидания нужны границы span, временные отметки и статус ошибки. Если компоненты пишут только общий идентификатор, связь событий появится, но длительность отдельных участков останется неизвестной.
Ниже парсер получает одну строку и возвращает разобранные поля только после проверки формата. Для плохого входа он отдаёт короткую причину, которую можно считать в метрике без записи полного заголовка. Пример не создаёт span и не отправляет telemetry: он показывает только проверяемую границу.
import { parseTraceparent } from './upgrade-2027-10.mjs'; const valid = parseTraceparent('00-4bf92f3577b34da6a3ce929d0e0e4736-00f067aa0ba902b7-01'); const invalid = parseTraceparent('00-4BF92F3577B34DA6A3CE929D0E0E4736-00f067aa0ba902b7-01'); console.log(valid.ok, valid.traceId.slice(0, 8)); console.log(invalid.ok, invalid.reason); // true 4bf92f35 // false trace-id-invalidВ примере uppercase trace-id отклоняется. Это controlled fallback, а не 500: сервис не принимает повреждённую строку как родительский контекст и не использует её как право доступа. Конкретную политику для version и flags нужно закрепить интеграционным тестом выбранной библиотеки.
| Симптом | Причина | Проверка | Действие |
|---|---|---|---|
| В gateway и service разные trace-id | Proxy удалил заголовок или middleware начал новый trace | Сравнить raw-заголовок до и после proxy | Разрешить передачу traceparent и проверить продолжение валидного контекста |
| Контекст есть только в локальном запуске | Реальный proxy применяет другой allow-list или лимит размера | Повторить запрос через gateway с теми же правилами | Добавить integration test для gateway + service |
| Валидный на вид заголовок отклонён | Uppercase, неверная длина, нулевой id или неподдерживаемая версия | Прогнать parser fixture на каждое поле | Отбросить вход и записать безопасную причину |
| По trace-id не находится событие | Logger пишет другой ключ или sampling удалил event | Проверить структурное поле и политику sampling | Унифицировать JSON key и сохранить ошибку парсинга как счётчик |
| После HTTP-запроса теряется связь с очередью | HTTP-контекст не перенесён в механизм брокера | Проверить envelope или headers сообщения | Использовать механизм контекста очереди и отдельный link между участками |
Этот подход проверяет HTTP-заголовок и передачу контекста между компонентами. Он не проверяет подпись, доверие к отправителю, backend sampling, полноту collector и форматы контекста Kafka или другой очереди. Он также не делает trace-id бизнес-идентификатором: повтор операции и асинхронная обработка требуют отдельного безопасного operation-id.
Готовность можно считать доказанной, если интеграционный тест через реальный proxy сохраняет один trace-id на границе gateway и service, валидный контекст получает локальный span, а повреждённый вход приводит к новому локальному trace без 500. Логи должны содержать единый trace-id key, а проверка не должна превращать внешний идентификатор в секрет или разрешение на действие.