{ "index": 36, "slug": "editorial-2027-01-practice-debugging-decade", "title": "502, пустой экран и 403: диагностика по различающим сигналам", "excerpt": "Практический маршрут от web-симптома к проверяемой причине: как сохранить конверт запроса, связать границы и отличить исправление от удачного повтора.", "contentHtml": "

Пользователь открывает страницу и получает 502. Или видит пустой экран после ответа 200. Или форма возвращает 403, хотя доступ должен быть разрешён. Команда быстро называет причину: «упал сервис», «сломался фронтенд», «протух токен». Если первая версия неверна, инженер меняет не тот слой, стирает исходный сигнал и получает новый симптом. Для пользователя это недоступная операция. Для команды — лишний релиз и повторный отказ.

Диагностика должна идти от наблюдаемого эффекта к различающему сигналу. Сначала сохраните конверт запроса. Затем назовите две причины, совместимые с фактами. Для каждой запишите проверку, которая может её опровергнуть. Только после этого меняйте конфигурацию или код. Такой порядок превращает «похоже на» в последовательность, которую может повторить другой инженер.

Сначала отделите статус от причины

HTTP-статус описывает ответ на одной границе. RFC 9110 определяет 502 как ответ gateway или proxy, который получил недействительный ответ от входного сервера, и 504 как ситуацию, в которой gateway не получил своевременный ответ от upstream. Это полезные факты о границе, но не диагноз: за ними могут стоять несовместимый формат, закрытое соединение, неверный маршрут или исчерпанный таймаут.

403 тоже не равен фразе «у пользователя нет доступа». По RFC 9110 сервер понял запрос, но отказался его выполнить. Причина может находиться в claims, scope, ресурсе или версии политики. Если отправитель получил валидные credentials, но они недостаточны для доступа, 403 соответствует этому результату; выяснить, почему credentials оказались недостаточны, можно только по контексту решения.

Пустой экран требует отдельного разбиения. Браузер мог получить пустой HTML. JavaScript мог завершиться с ошибкой до рендера. API мог вернуть пустой массив по корректному условию. Компонент мог скрыть ошибку и оставить контейнер без содержимого. Все случаи похожи на скриншоте, но различаются телом ответа, сетевыми событиями, ошибками консоли и фактически построенным DOM.

Маршрут диагностики web-запроса от симптома через идентификатор к проверяемому действию
На каждой границе нужен свой наблюдаемый сигнал. Если ключа связи нет, цепочку нельзя честно восстановить по одному времени.

Конверт запроса: что сохранить до повтора

Сохраните один пример сбоя и один успешный пример с тем же маршрутом до изменения системы. Минимальный конверт содержит метод, путь, статус, время с часовым поясом, длительность, размер ответа, content-type, request-id или traceparent и границу наблюдения. Для браузерного симптома добавьте URL документа, статус API, ошибку консоли и результат проверки DOM.

Не записывайте в такой конверт cookies, токены, полный пользовательский ввод и другие секреты. Для корреляции достаточно технического идентификатора и безопасной части контекста. Поле boundary должно отвечать на вопрос «где это наблюдали»: browser, gateway или application. Без этой пометки одинаковый статус легко принять за одно и то же событие.

{
  'request_id': 'req-7f31',
  'traceparent': '00-4bf92f3577b34da6a3ce929d0e0e4736-00f067aa0ba902b7-01',
  'method': 'GET',
  'route': '/reports',
  'status': 502,
  'duration_ms': 184,
  'response_bytes': 11,
  'content_type': 'text/plain',
  'boundary': 'gateway'
}

Это не универсальный формат логирования. Он задаёт точку сравнения для одного маршрута и одной границы. Названия полей, формат времени и срок хранения принадлежат вашему проекту. Требование к результату проще: gateway и приложение должны уметь найти относящиеся к одному запросу записи, а разные операции не должны случайно получать один и тот же идентификатор.

Гипотеза должна разделять причины

Полезная гипотеза имеет форму «если X, то при проверке Y увидим Z». В ней есть ожидаемый признак и результат, который заставит отказаться от объяснения. Например: если gateway не получил ответ приложения, в его access log будет 502, а в журнале приложения не появится событие с тем же идентификатором. Если приложение сформировало ошибочный ответ, записи будут на обеих границах, а статусы, размеры или длительности могут различаться.

Идентификатор запроса связывает записи, но не доказывает причинность. Заголовок мог потеряться на прокси, трасса могла быть отобрана sampling-механизмом, а часы узлов могут быть настроены с разным смещением. При отсутствии ключа вывод звучит так: «граница не наблюдаема», а не «виновата ближайшая строка журнала». Это отрицательное знание подсказывает, какой сигнал добавить перед следующим повтором.

Разделяйте три типа телеметрии. Лог — запись отдельного события и его контекста. Span — отрезок операции внутри трассы, с родителем и длительностью. Метрика — измерение, собранное во времени для множества операций. Трасса показывает путь конкретного запроса, но не гарантирует, что каждый участок был записан. Метрика показывает масштаб проблемы, но не выбирает виновный запрос. Лог даёт детали, но без общего ключа плохо связывается с соседними слоями.

Воспроизводимый локальный разбор

Ниже — чистая функция, которая выбирает следующий шаг из зафиксированных наблюдений. Она не делает сетевых запросов и не называет первопричину. Входные события известны, результат каждой ветки можно сравнить с ожидаемым, а отсутствие сигнала остаётся отдельным исходом.

function nextCheck(event) { const status = Number(event.status); const hasTrace = typeof event.traceparent === 'string' && event.traceparent.length > 0; const appSeen = event.application_seen === true; const domReady = event.dom_ready === true; if ((status === 502 || status === 504) && !hasTrace) return 'добавить корреляцию на границе gateway'; if ((status === 502 || status === 504) && hasTrace && !appSeen) return 'проверить маршрут gateway, DNS и соединение до upstream'; if ((status === 502 || status === 504) && hasTrace && appSeen) return 'сравнить ответ приложения, длительности и таймауты границ'; if (status === 200 && !domReady) return 'сравнить HTML, ошибки консоли, API-response и DOM'; if (status === 403) return 'сверить контекст авторизации и решение политики доступа'; return 'зафиксировать следующий сигнал для этой границы'; } const cases = [{ status: 502, application_seen: false }, { status: 502, traceparent: '00-abc-123-01', application_seen: true }, { status: 200, dom_ready: false }, { status: 403, application_seen: true }]; console.log(cases.map(nextCheck));

Первый объект показывает, почему отсутствие идентификатора — самостоятельный дефект наблюдаемости. Второй переводит проверку к сравнению ответа и таймаутов, но не доказывает неисправность приложения. Третий разделяет «HTTP 200» и «страница готова». Четвёртый отправляет расследование к контексту доступа, а не к случайному перезапуску. Функция полезна как тест маршрута диагностики; её нельзя использовать как готовое правило прокси или авторизации.

Матрица симптомов и проверок

Первый проход по четырём web-симптомам
НаблюдениеДве рабочие гипотезыРазличающий сигналБезопасное действие
502 на gatewayНекорректный ответ upstream или отказ до приложенияtraceparent/request-id на gateway и запись приложения в том же контекстеСначала найти границу разрыва; затем менять маршрут или код
504 через близкий интервалИстёк таймаут gateway или зависла зависимостьДлительности span-ов и значения таймаутов на каждой границеСопоставить бюджет времени; не увеличивать таймаут без измерения
200 и пустой экранПустые данные или ошибка рендераТело HTML/API, console error и фактический DOMПовторить с теми же входами и проверить отрицательный путь
403 для ожидаемого пользователяНеверный контекст или отказ политикиТокен, claims, scope, ресурс и версия правилаИзменить конкретное условие; не ослаблять всю политику
Событие есть только на одной границеПотерян заголовок или отсутствует записьФормат переноса идентификатора и временное окноСчитать цепочку несвязанной и добавить сигнал

Порядок расследования

  1. Опишите эффект без объяснения: кто его увидел, какой метод и маршрут использовал, какой статус и размер ответа получил.
  2. Сохраните время с часовым поясом, длительность, content-type, request-id или traceparent и границу наблюдения.
  3. Назовите две причины, совместимые с фактами. Для каждой запишите ожидаемый признак и результат, который её опровергнет.
  4. Проверьте gateway и следующий слой в одном временном окне. Приоритет у общего идентификатора; совпадение пути и секунды недостаточно.
  5. Для пустой страницы отдельно проверьте HTML документа, сетевой ответ API, ошибки JavaScript и DOM после выполнения кода.
  6. Для 401/403 повторите безопасный сценарий с валидным, просроченным и отсутствующим контекстом доступа, не публикуя секреты в логе.
  7. Проверьте отрицательный путь: таймаут, потерянный parent span, пустой ответ, отсутствие права и повторную попытку после отказа.
  8. Внесите одно изменение в найденном слое, повторите исходный сценарий и сравните старый конверт с новым. Если одновременно изменились прокси, приложение и клиент, результат нельзя приписать одному изменению.
  9. Оставьте проверку рядом с исправлением: тест, запрос для воспроизведения, структурированный лог или короткую операционную инструкцию.

Как отличить исправление от удачного повтора

Один успешный запрос не закрывает расследование. Сравните минимум четыре поля: статус, длительность, размер ответа и наличие связанной записи на следующей границе. Для пользовательской страницы добавьте DOM и console error. Успешный результат должен повторяться на том же входе, а отрицательный сценарий должен по-прежнему завершаться ожидаемым отказом.

Если исправили маршрут, отдельно проверьте старый и новый upstream. Если изменили таймаут, измерьте время до отказа и нагрузку на зависимость. Если добавили retry, проверьте число попыток и суммарную стоимость запроса: повтор может увеличить нагрузку и скрыть первичный сбой. Если изменили политику доступа, проверьте соседние роли, чтобы разрешение одному контексту не стало разрешением всем.

Ограничения применимости

Этот метод работает только с доступными наблюдениями. Он не восстанавливает событие, которое система не записала, и не превращает близкое время в доказательство связи. Если gateway удаляет traceparent, нужно исправлять перенос или добавлять собственный безопасный request-id. Если sampling отбросил span, отсутствие span не означает отсутствие операции.

Семантика статуса зависит от границы. Согласно RFC 9110, 502 относится к недействительному ответу от входного сервера, к которому обращался gateway, а 504 — к отсутствию своевременного ответа. Конкретная реализация может вернуть 502 из-за формата ответа, соединения или настройки маршрута. Поэтому статус задаёт направление поиска, но не выбирает единственную причину.

Учебная функция намеренно грубая: она не учитывает CDN, кеш, редиректы, несколько upstream, фоновые очереди, разные протоколы и локальные правила безопасности. В рабочем окружении нельзя копировать её ветки в firewall, балансировщик или middleware без отдельной проверки контракта. Не помещайте в корреляционные поля токены, персональные данные и тело запроса.

Критерий готовности

Расследование можно передать другому инженеру, когда он получает исходный конверт, видит границу отказа и может повторить различающую проверку без устного контекста. Исправление готово, если тот же сценарий проходит по ожидаемому пути, отрицательный вариант получает правильный отказ, а связанные записи сохраняются на всех заявленных границах.

Если после изменения «стало зелёным», но нет объясняющего сигнала, честный итог — «симптом исчез, причина не доказана». В таком случае следующий шаг — улучшить наблюдаемость, повторить сценарий и только потом закреплять решение.

Проверяемые источники

" }