Структурированные логи: ловим баги за минуты, а не за ночь

Andrew_M

New member
Привет, коллеги. Хочу поделиться опытом, который изменил мой подход к отладке сильнее, чем любой новый фреймворк. Долгое время я жил в мире классических логов: пишу строчку текста, потом лезу на сервер, открываю файл и мучаю grep. «Пользователь не смог оформить заказ» — и всё, дальше начинается археология. Кто этот пользователь, какой у него запрос, что было за минуту до ошибки — приходилось выяснять перепиской с поддержкой и чтением кучи соседних строк. На один баг уходила ночь, а то и две.

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

Переход был проще, чем казалось. Я начал писать логи не строками, а объектами: JSON с фиксированными полями, а не «склеенной» фразой. Минимальный набор получился такой: время, уровень, имя сервиса, имя события, идентификатор запроса и идентификатор пользователя. Всё остальное — переменные данные, которые добавляются по ситуации. Как только это появилось, я смог фильтровать не по подстроке, а по конкретному полю, и шум исчез. Знаете это чувство, когда запрос к логам возвращает ровно три события вместо трёх тысяч строк? Ради него стоит потратить вечер на настройку.

Главная магия — сквозной идентификатор запроса, который тянется через все сервисы. Я генерирую его на входе, передаю в заголовках между сервисами и добавляю во все записи. Теперь, если клиент говорит «у меня в 14:32 всё сломалось», я беру его идентификатор и вижу полную картину: как запрос пришёл, где задержался, какой сервис ответил ошибкой. Это буквально превращает ночной квест в поиск по одной метке. Отдельно советую добавить идентификатор пользователя и версию сборки — тогда сразу понятно, у кого проблема и не началась ли она после последнего релиза.

Второй важный урок — логировать контекст, а не эмоции. Раньше я писал «что-то пошло не так», и это было бесполезно. Теперь в каждой ошибке есть код, тип операции, входные параметры (без чувствительных данных) и причина отказа. Зато я почти перестал писать логи ради логов: никаких подробных сообщений на каждый чих, только значимые события. Уровни тоже стоит разводить честно: отладочные записи не должны попадать в продакшн-поток, иначе вы утонете в данных и пропустите действительно важное. Правило простое: если запись не поможет ответить на вопрос «что и почему сломалось», она лишняя.

Ошибок я тоже наделал немало, и о них стоит рассказать честно. Сначала мы случайно логировали токены и персональные данные — повезло, что заметили быстро. Потом писали целые ответы API в логи, и объём данных вырос так, что сборщик начал задыхаться. Ещё был период, когда половина сервисов писала JSON, а половина — обычный текст, и единая фильтрация развалилась. Выводы: маскируйте чувствительные поля на уровне библиотеки логирования, ограничивайте размер записей и договоритесь о единой схеме полей для всех команд. Это скучная дисциплина, но именно она даёт результат.

Если хотите повторить этот путь, начните с малого. Выберите один сервис, введите пять обязательных полей и сквозной идентификатор, отправьте логи в централизованный сборщик — подойдут и Loki, и Elasticsearch, и любое облачное решение. Настройте поиск по полям и пару полезных дашбордов, а потом расширяйте практику на остальные сервисы. Через месяц вы будете находить баги за минуты, и это не преувеличение. А теперь вопрос к вам, форумчане: какие поля в своих логах вы считаете обязательными и случалось ли вам ловить сложный баг буквально за пару минут благодаря структурированным записям? Делитесь историями, очень интересно почитать.
 
Назад
Вверх