{ "index": 34, "slug": "editorial-2027-01-field-debugging-decade", "title": "502 без догадок: как восстановить цепочку запроса по логам", "excerpt": "Пошаговый разбор 502 по access и application log: как связать события, отличить отказ до приложения от ошибки в нём и не назвать причину без доказательства.", "contentHtml": "

Клиент получает 502. В access log шлюза видны маршрут, время и внешний статус. В журнале приложения нет записи с тем же запросом. Инженер открывает последний релиз и начинает искать ошибку в коде. Через несколько часов выясняется, что шлюз не дождался upstream или не смог разобрать его ответ. Исправление приложения не меняет ситуацию, а время расследования уже потеряно.

\n

Цена ошибки состоит не только в часах. Команда может откатить исправный релиз, увеличить таймаут без понимания причины или добавить повторные запросы к уже перегруженной зависимости. Следующий дежурный получит уверенную, но неверную запись: «упало приложение». Она направит новое расследование по тому же ложному следу.

\n

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

\n

Что означает внешний статус

\n

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

\n

Поэтому первая запись должна называть субъект результата. Поле edge.status=502 точнее, чем сообщение «сервер вернул 502». Рядом нужны route, method, duration_ms, время события, имя узла и ключ корреляции. Без этих полей access log подтверждает симптом, но не даёт короткого пути к следующему слою.

\n

Как строится доказательная цепочка

\n

Для одной попытки запроса нужны минимум два источника: access log на границе и application log в сервисе. Они связываются по точному request_id или по trace context. Время и путь помогают проверить совпадение, но не должны быть единственным ключом. Два запроса к одному маршруту могут попасть в одно и то же временное окно.

\n

Если application event найден, сравните время, маршрут, статус и длительность. Запись приложения с 500 показывает, что запрос дошёл до приложения и там завершился ошибкой. Она не объясняет, почему произошёл отказ зависимости. Запись приложения с 200 при внешнем 502 показывает расхождение границ: надо проверять retry, кэш, преобразование статуса или другой upstream.

\n

Если application event не найден, не подставляйте приложение в роль виновника. Проверьте timeout до приложения, правила маршрутизации, формат идентификатора, фильтры коллектора и задержку доставки. После этого можно сказать только: «внешний отказ подтверждён, запись приложения не найдена». Это полезный вывод, потому что он отделяет неисправность канала наблюдения от отказа бизнес-операции.

\n
\"Цепочка
Полевой разбор начинается с внешнего события. Разрыв между access и application не позволяет объявить приложение причиной.
\n

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

\n
Матрица первой проверки для 502
СимптомВозможная причинаПроверкаДействие
502 в edge, application event отсутствуетtimeout, маршрут до сервиса или потеря записисверить upstream, окно времени, collector и формат idзафиксировать разрыв; не обвинять приложение
502 в edge, application 500 с тем же idошибка обработки запроса в сервисесравнить время, route, статус и dependency eventисследовать ошибку приложения и её границу
502 в edge, application 200retry, cache или преобразование ответа на proxyпроверить попытки, upstream и mapping статусовразделить результат приложения и результат клиента
В access нет request idнеполная схема structured logпроверить конфигурацию полей и передачу заголовкаисправить корреляцию до следующего разбора
\n

Учебный пример корреляции

\n

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

\n
const edge = [\n  { requestId: 'r-1', at: '12:00:01.100', status: 502, durationMs: 3000 },\n  { requestId: 'r-2', at: '12:00:02.100', status: 502, durationMs: 420 },\n];\n\nconst app = [\n  { requestId: 'r-2', at: '12:00:02.080', status: 500, error: 'db unavailable' },\n];\n\nfunction classify(edgeEvent, appEvents) {\n  const appEvent = appEvents.find((event) => event.requestId === edgeEvent.requestId);\n  if (!appEvent) return 'gateway-failed-before-app-or-event-missing';\n  if (appEvent.status >= 500) return 'app-error-reached-gateway';\n  return 'status-mapping-needs-check';\n}\n\nedge.map((event) => ({\n  requestId: event.requestId,\n  result: classify(event, app),\n}));\n// r-1: gateway-failed-before-app-or-event-missing\n// r-2: app-error-reached-gateway
\n

Для r-1 код видит только отсутствие события. Это не различает timeout, сбой коллектора и неверный идентификатор. Для r-2 найдено согласованное событие приложения. Оно подтверждает путь запроса, но не доказывает, что база была первопричиной. Следующий запрос должен проверить dependency event, лимит соединений и время ожидания.

\n

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

\n

Действия по порядку

\n
  1. Скопируйте одну попытку из edge log: timestamp, route, method, status, duration и request id.
  2. Уточните, какой узел сформировал 502 и какой upstream он выбирал.
  3. Найдите application events по точному id в ограниченном временном окне. Запишите число найденных событий.
  4. Сверьте время, route, status и номер попытки. Отдельно отметьте retry, очередь и возможный clock skew.
  5. Если приложение подтверждено, проверьте dependency event и только затем формулируйте рабочую гипотезу о причине.
  6. Если приложение не найдено, проверьте timeout, маршрутизацию, collector, формат идентификатора и задержку доставки.
  7. Запишите вывод как наблюдение и следующий тест: например, «нет application event; проверить timeout и collector».
  8. После изменения повторите тот же запрос и убедитесь, что цепочка снова собирается по идентификатору.
\n

Где метод перестаёт работать

\n

Лог может быть неполным. Sampling удаляет часть событий. Буферизация меняет порядок доставки. Collector может отбросить запись или обрезать длинное сообщение. Часы сервисов могут расходиться. Поэтому близкое время не заменяет ключ корреляции, а найденный ключ не гарантирует полноту цепочки.

\n

Один request id может пережить retry или быть создан заново на новой попытке. Это надо выяснять по attempt, span id и временным интервалам. Trace context помогает передавать связь между HTTP-границами, но не доказывает, что каждый сервис записал событие или что наблюдаемый участок был причиной сбоя.

\n

Структурированные поля упрощают поиск, но не делают журнал достоверным автоматически. Формат RFC 5424 предусматривает отдельную область для parseable structured data; конкретная система всё равно может неправильно настроить поля, транспорт или collector. Если корреляция часто ломается, сначала исправьте контракт логирования. Новый экран наблюдаемости не компенсирует отсутствующий идентификатор.

\n

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

\n

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

\n

Разбор готов, когда для одной повторной попытки можно показать access event, application event или явно подтверждённый разрыв, согласованные время и маршрут, номер попытки и следующий проверяемый вывод. Исправление готово, когда после него тот же сценарий даёт ожидаемый статус, цепочка событий собирается по идентификатору, а отрицательный путь остаётся различимым. Формулировка «проблема решена» без этих наблюдений недостаточна.

\n

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

\n" }