Содержание
Есть два типа дебага.
Первый: “в 12:41 что-то упало… у кого есть логи?”
Второй: “вот request_id, вот цепочка событий, вот участок, где запрос потерял 2.7 секунды”.
Разница — не в таланте. Разница в дисциплине: Request ID + структурированные логи.
Почему “просто строка в лог” перестает работать на второй неделе
Пока сервис один, можно жить на “ошибка при оплате”.
Когда запрос проходит через несколько слоев (хендлер → сервис → клиент → БД), появляются вопросы:
- это тот же запрос или другой?
- это повтор (retry) или первый вызов?
- где именно потеряли время?
- какой пользователь/клиент страдает?
Если логи не связаны, ты собираешь пазл по цвету. Долго и нервно.
Request ID: “номер дела” для одного запроса
Request ID (correlation id) — это идентификатор, который:
- появляется на входе в запрос (или генерируется),
- прокидывается дальше (через
context.Context, заголовки, сообщения), - и попадает в каждый лог, который относится к обработке.
Тогда ты не ищешь “примерно где-то около 12:40”. Ты ищешь request_id=... и получаешь связную историю.
Минимальный набор полей, который реально спасает
Если ты не знаешь, какие поля нужны — начни с этих. Они окупаются почти всегда:
request_idmethod,pathstatusduration_msremote_ipилиclientuser_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
Это тот самый лог, который потом показывает: “мы деградировали”, даже если ошибок нет.
Быстрый тест “у нас всё ок?”
Сделай один запрос и ответь на три вопроса:
- В ответе есть request_id?
- По этому request_id можно собрать ключевые логи (вход, ошибки, завершение)?
- В логах видно длительность и статус?
Если хоть одно “нет” — это дешевле поправить сегодня, чем в 03:12 ночи.