Yandex Metrika
Перейти к содержимому
Backend
| 9 мин

Request ID и структурированные логи в Go: минимум, который делает дебаг быстрым

Евгений Петрович
Евгений Петрович
Backend разработчик, автор симулятора

Содержание

Есть два типа дебага.

Первый: “в 12:41 что-то упало… у кого есть логи?”

Второй: “вот request_id, вот цепочка событий, вот участок, где запрос потерял 2.7 секунды”.

Разница — не в таланте. Разница в дисциплине: Request ID + структурированные логи.

Почему “просто строка в лог” перестает работать на второй неделе

Пока сервис один, можно жить на “ошибка при оплате”.

Когда запрос проходит через несколько слоев (хендлер → сервис → клиент → БД), появляются вопросы:

  • это тот же запрос или другой?
  • это повтор (retry) или первый вызов?
  • где именно потеряли время?
  • какой пользователь/клиент страдает?

Если логи не связаны, ты собираешь пазл по цвету. Долго и нервно.

Request ID: “номер дела” для одного запроса

Request ID (correlation id) — это идентификатор, который:

  • появляется на входе в запрос (или генерируется),
  • прокидывается дальше (через context.Context, заголовки, сообщения),
  • и попадает в каждый лог, который относится к обработке.

Тогда ты не ищешь “примерно где-то около 12:40”. Ты ищешь request_id=... и получаешь связную историю.

Минимальный набор полей, который реально спасает

Если ты не знаешь, какие поля нужны — начни с этих. Они окупаются почти всегда:

  • request_id
  • method, path
  • status
  • duration_ms
  • remote_ip или client
  • user_id/account_id (если есть)
  • error (как значение/объект, а не “что-то пошло не так”)
  • component/handler (кто именно логирует)

Осторожно с данными: не логируй секреты и PII по умолчанию. У тебя должна быть политика: что можно, что нельзя.

Где лучше ставить Request ID

Правило: максимально близко к входу.

Для HTTP:

  • если клиент прислал X-Request-Id (или ваш стандарт) — используй;
  • если нет — сгенерируй;
  • положи в context.Context;
  • верни в ответе (чтобы support/QA могли прислать его тебе);
  • добавь во все логи в рамках запроса.

Где request_id чаще всего “умирает” (и как его не потерять)

1) goroutine

Ты ушел(ла) в горутину — и внезапно “контекст где-то остался”.

Причина банальная: контекст не телепортируется.

Решение тоже банальное: передавай ctx явно. Если запускаешь фоновые задачи — создай отдельный ctx и перенеси туда нужные поля.

2) Внешние запросы (HTTP/gRPC)

Ты делаешь вызов в другой сервис и забываешь прокинуть заголовок. В результате у “одного запроса” появляется две независимые истории.

Решение: клиентский слой должен автоматически прокидывать request id.

3) Очереди/воркеры

Событие улетело в очередь, а request_id не попал в payload. Потом воркер логирует, но ты не можешь связать это с исходным запросом.

Решение: клади correlation id в сообщение (и переименуй в что-то нейтральное типа correlation_id, если это уже не “request”).

4) Логи “внутри хелперов”

Ты пишешь лог в утилите, где нет контекста, и он превращается в сироту.

Решение: либо утилита принимает ctx, либо ты передаешь в нее logger/поля.

Мини-паттерн: “одна точка измерения”

У запроса должна быть хотя бы одна запись в конце обработки:

  • статус
  • длительность
  • request_id

Это тот самый лог, который потом показывает: “мы деградировали”, даже если ошибок нет.

Быстрый тест “у нас всё ок?”

Сделай один запрос и ответь на три вопроса:

  1. В ответе есть request_id?
  2. По этому request_id можно собрать ключевые логи (вход, ошибки, завершение)?
  3. В логах видно длительность и статус?

Если хоть одно “нет” — это дешевле поправить сегодня, чем в 03:12 ночи.

Backend Go Разработка
Поделиться статьей:
Хочешь проверить это на практике?
Запусти демо-сценарий в браузере: чаты, логи, терминал и задачи как в реальной команде.
~10 минут • без регистрации • можно выбрать сценарий