Files
progcode/editorial/agent-rewrites/007.json
T
huncode 2d914b543f
Build and deploy / deploy (push) Failing after 15s
Publish rewritten technical article archive
2026-08-02 22:19:34 +03:00

2 lines
12 KiB
JSON
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
{"index":7,"slug":"editorial-2027-10-field-long-form-interview","title":"Trace Context в HTTP: как сохранить запрос на границе proxy","excerpt":"Если traceparent исчезает или не проходит проверку, gateway и сервис перестают видеть один запрос. Разбираем формат, безопасный fallback и проверку через реальный proxy.","contentHtml":"<p>Симптом появляется во время сбоя: gateway сообщает timeout, backend пишет 500, а записи нельзя связать одним запросом. Инженер видит несколько похожих событий и не понимает, относятся ли они к одной операции. Цена ошибки — лишний поиск по логам, неверная гипотеза о медленном сервисе и более долгий разбор пользовательской проблемы.</p><p>Часто ломается не сама трассировка, а граница между компонентами. Proxy удаляет неизвестный заголовок, middleware создаёт новый trace вместо продолжения, либо сервис принимает строку с неверным форматом. Исправление должно проверять весь путь: что отправили, что пропустил proxy, что разобрал сервис и что записал logger.</p><h2>Тезис: traceparent — транспортный контракт, а не доказательство доступа</h2><p>W3C Trace Context задаёт переносимый HTTP-заголовок <code>traceparent</code>. Он связывает участки обработки одного запроса, но не гарантирует наличие span, корректность логирования или сохранность заголовка на каждом proxy. Поэтому проверять нужно не только парсер. Нужен контракт между клиентом, gateway, middleware и сервисом.</p><p>Валидный внешний контекст можно продолжить как родительский. После этого локальная библиотека создаёт новый span для текущего сервиса. Невалидный контекст нельзя молча чинить: сервис отбрасывает его, создаёт новый локальный trace и фиксирует короткую причину отказа. Trace-id не заменяет авторизацию и не должен содержать пользовательские данные.</p><h2>Механизм передачи</h2><p><code>traceparent</code> состоит из четырёх полей: версии, trace-id, parent-id и flags. Для базового формата важны точные длины, lowercase hexadecimal и ненулевые идентификаторы. Ошибка в одном поле делает вход непригодным для продолжения. Парсер должен вернуть результат проверки, а не угадывать намерение отправителя.</p><p>Proxy — часть этого контракта. Он может удалить неизвестный заголовок, нормализовать имя, ограничить размер или создать собственный контекст. Локальный тест функции парсинга не доказывает, что значение дошло до приложения. Проверка должна проходить через ту же пару gateway и service, с которой работает запрос.</p><p>Даже сохранённый trace-id не объясняет задержку сам по себе. Для поиска места ожидания нужны границы span, временные отметки и статус ошибки. Если компоненты пишут только общий идентификатор, связь событий появится, но длительность отдельных участков останется неизвестной.</p><figure><img src=\"/assets/editorial/2027/long-form-interview-2027-editorial-handoff-loop.svg\" alt=\"Клиент передаёт traceparent через gateway, gateway сохраняет контекст, а сервис проверяет формат перед созданием локального span\"/><figcaption>Проверяйте контекст на границе сервиса после proxy. Заголовок связывает события, но не является авторизацией и не заменяет хранение telemetry.</figcaption></figure><h2>Минимальный рабочий пример</h2><p>Ниже парсер получает одну строку и возвращает разобранные поля только после проверки формата. Для плохого входа он отдаёт короткую причину, которую можно считать в метрике без записи полного заголовка. Пример не создаёт span и не отправляет telemetry: он показывает только проверяемую границу.</p><pre><code>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</code></pre><p>В примере uppercase trace-id отклоняется. Это controlled fallback, а не 500: сервис не принимает повреждённую строку как родительский контекст и не использует её как право доступа. Конкретную политику для version и flags нужно закрепить интеграционным тестом выбранной библиотеки.</p><h2>Диагностика по симптому</h2><table><thead><tr><th>Симптом</th><th>Причина</th><th>Проверка</th><th>Действие</th></tr></thead><tbody><tr><td>В gateway и service разные trace-id</td><td>Proxy удалил заголовок или middleware начал новый trace</td><td>Сравнить raw-заголовок до и после proxy</td><td>Разрешить передачу traceparent и проверить продолжение валидного контекста</td></tr><tr><td>Контекст есть только в локальном запуске</td><td>Реальный proxy применяет другой allow-list или лимит размера</td><td>Повторить запрос через gateway с теми же правилами</td><td>Добавить integration test для gateway + service</td></tr><tr><td>Валидный на вид заголовок отклонён</td><td>Uppercase, неверная длина, нулевой id или неподдерживаемая версия</td><td>Прогнать parser fixture на каждое поле</td><td>Отбросить вход и записать безопасную причину</td></tr><tr><td>По trace-id не находится событие</td><td>Logger пишет другой ключ или sampling удалил event</td><td>Проверить структурное поле и политику sampling</td><td>Унифицировать JSON key и сохранить ошибку парсинга как счётчик</td></tr><tr><td>После HTTP-запроса теряется связь с очередью</td><td>HTTP-контекст не перенесён в механизм брокера</td><td>Проверить envelope или headers сообщения</td><td>Использовать механизм контекста очереди и отдельный link между участками</td></tr></tbody></table><h2>Порядок проверки</h2><ol><li>Выберите один запрос и выпишите его границы: клиент, gateway, service и downstream. Для каждой границы определите, где должен появиться trace-id.</li><li>Снимите заголовок до proxy и после proxy. Если значение исчезло на этом участке, не начинайте поиск с backend-библиотеки.</li><li>Добавьте тестовые случаи для нулевых идентификаторов, uppercase, неверной длины и запрещённой версии. Для каждого случая зафиксируйте ожидаемый controlled fallback.</li><li>Проверьте middleware на валидном входе: trace-id должен продолжиться, а текущий сервис должен получить новый локальный parent-id для своего span.</li><li>Сведите записи к одному структурному ключу. Причину отказа парсера считайте отдельно и не сохраняйте полный внешний заголовок без необходимости.</li><li>Прогоните end-to-end проверку через реальный proxy. Затем отдельно проверьте sampling и задержку доставки telemetry, чтобы отсутствие записи не принять за потерю контекста.</li></ol><h2>Ограничения и критерий готовности</h2><p>Этот подход проверяет HTTP-заголовок и передачу контекста между компонентами. Он не проверяет подпись, доверие к отправителю, backend sampling, полноту collector и форматы контекста Kafka или другой очереди. Он также не делает trace-id бизнес-идентификатором: повтор операции и асинхронная обработка требуют отдельного безопасного operation-id.</p><p>Готовность можно считать доказанной, если интеграционный тест через реальный proxy сохраняет один trace-id на границе gateway и service, валидный контекст получает локальный span, а повреждённый вход приводит к новому локальному trace без 500. Логи должны содержать единый trace-id key, а проверка не должна превращать внешний идентификатор в секрет или разрешение на действие.</p><h2>Проверяемые источники</h2><ul><li><a href=\"https://www.w3.org/TR/2021/REC-trace-context-1-20211123/\" target=\"_blank\" rel=\"noopener\">W3C Trace Context Level 1</a> — формат и правила передачи traceparent.</li><li><a href=\"https://opentelemetry.io/docs/concepts/signals/\" target=\"_blank\" rel=\"noopener\">OpenTelemetry: signals</a> — роли сигналов наблюдаемости и ограничения одного trace-id.</li></ul>"}