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

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

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

Что именно наблюдает клиент

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

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

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

\"Маршрут
Каждый переход от симптома к действию должен добавлять наблюдаемый признак. Иллюстрация показывает маршрут, а не готовый диагноз.

Механизм: гипотеза должна различать причины

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

Идентификатор запроса связывает записи, но не доказывает причинность. Он может потеряться на границе, повториться из-за ошибки интеграции или не попасть в sampled trace. Совпадение времени тоже не является связью: параллельные запросы имеют похожие отметки, а часы узлов могут расходиться. Если ключа нет, это результат проверки — «цепочка не связана», — а не разрешение взять ближайшую запись.

Для распределённого маршрута полезно различать три вида данных. Log показывает сообщение и локальное состояние процесса. Span показывает границу операции, родителя и длительность. Metric показывает агрегат по множеству запросов. Trace ID помогает найти общий контекст. Ни один из этих сигналов в одиночку не отвечает на все вопросы. Длинный span не обязательно является причиной задержки. Ошибка в метрике не доказывает ошибку конкретного запроса.

Учебный пример: классифицировать вход, не объявляя root cause

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

function classifyWebSymptom(input) {
  const status = Number(input?.status);
  const body = String(input?.body ?? '');
  const headers = Object.fromEntries(Object.entries(input?.headers ?? {}).map(([key, value]) => [key.toLowerCase(), String(value)]));
  if (status === 502 || status === 504) return headers.traceparent || headers['x-request-id'] ? 'связать gateway и upstream по идентификатору' : 'включить идентификатор на границе';
  if (status === 401 || status === 403) return 'сверить аутентификацию, авторизацию и политику доступа';
  if (status === 200 && /empty|blank|undefined/i.test(body)) return 'сравнить тело ответа API с фактическим DOM';
  return 'сохранить метод, путь, статус, размер и время';
}
console.log(classifyWebSymptom({ status: 502, headers: { traceparent: '00-abc-123-01' }, body: 'Bad Gateway' }));
// связать gateway и upstream по идентификатору

Вход с 502 и traceparent ведёт к сопоставлению записей на двух границах. Вход с 502 без идентификатора ведёт к исправлению наблюдаемости, а не к перезапуску приложения. Вход с 200 и пустым содержимым ведёт к сравнению ответа, ошибок рендера и DOM. Вход 403 ведёт к проверке схемы аутентификации и решения авторизации. Эти маршруты не заменяют расследование. Они не дают функции права выбрать базу данных, прокси или браузер виновником.

Симптом → причина → проверка → действие

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

Порядок действий

  1. Запишите симптом без объяснения: метод, путь, статус, время, длительность, размер ответа, content-type и границу, на которой увидели ответ.
  2. Сформулируйте минимум две причины. Для каждой запишите наблюдение, которое должно появиться, и наблюдение, которое её опровергнет.
  3. Проверьте внешний access log и журнал следующего слоя в одном временном окне. Сопоставляйте записи по идентификатору, а не только по пути и секунде.
  4. Разделите синхронный и асинхронный путь. Для очереди или фоновой задачи найдите отдельную связь между producer, сообщением и consumer.
  5. Проверьте отрицательный путь: запрос без токена, просроченный токен, отсутствующий parent span, пустой ответ и превышение таймаута.
  6. Внесите одно изменение в найденном слое. Повторите тот же сценарий с теми же входами и сравните исходный конверт с новым.
  7. Сохраните проверку рядом с исправлением: тест, запрос для воспроизведения, структурированный лог или короткую операционную инструкцию.

Ограничения

Этот маршрут не восстанавливает данные, которых система не записала. Если gateway не переносит идентификатор, связь нельзя честно реконструировать по одному времени. Если trace sampling отбросил span, отсутствие span не означает отсутствие работы. Если прокси переписал статус или тело, нужно искать его access log и правила маршрутизации. Если несколько запросов выполняются параллельно, порядок строк в журнале не равен порядку причин.

Учебный классификатор не подходит как готовое правило блокировки или маршрутизации. Его ветки намеренно грубые. В реальной системе нужно учесть редиректы, retries, кеш, CDN, разные схемы авторизации и версию контракта API. Не добавляйте повторные попытки только потому, что ответ медленный: retry может увеличить нагрузку и скрыть первичный отказ. Не меняйте таймаут, пока не измерили бюджет на каждой границе.

Статус 502 или 504 также не доказывает, что downstream был недоступен. Причина может быть в несовместимом формате ответа, неверном DNS, закрытом соединении или ограничении шлюза. Статус 403 не доказывает, что пользователь «не имеет доступа» в бизнес-смысле: решение могло использовать устаревшие claims или другую версию политики. Проверяйте именно тот контекст, который использовал компонент.

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

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

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

" }