Логи, по которым можно искать

Разница между «мы пишем логи» и «мы находим причину за минуту» — это структура, уровни и request id.

Лог — это данные, а не текст

Строка Ошибка при обработке заказа! бесполезна ровно в тот момент, когда нужна: по ней нельзя ни сгруппировать, ни отфильтровать, ни найти соседние события того же запроса.

Структурированный лог — это событие с полями:

{"ts":"2026-07-24T12:03:11Z","level":"error","msg":"payment failed",
 "requestId":"a1f3","userId":42,"orderId":911,"err":"card_declined","durationMs":840}

Тот же объём информации, но теперь «все ошибки этого пользователя за час» — один запрос, а не чтение глазами.

Request id — нить через всю систему

Запрос проходит nginx, приложение, воркер и базу. Без общего идентификатора их логи — четыре несвязанные кучи. Правило: id генерируется на входе (или берётся из заголовка X-Request-Id) и пишется в каждую строку, включая фоновые задачи, которые запрос породил. Найти один инцидент = отфильтровать один id.

Уровни, которые что-то значат

Если error'ов сотни в час и «это нормально» — уровни мертвы: настоящая ошибка утонет.

Что не писать

Пароли, токены, полные номера карт, персональные данные — логи хранятся долго, копируются в сторонние системы и читаются людьми, которым эти данные не нужны. Утечка через логи — классика инцидентов.

Хранилище

Логи всех контейнеров должны стекаться в одно место (в этом проекте — Loki, читается из Grafana). Один важный нюанс индексирования: метки должны быть с малой кардинальностью (сервис, уровень), а не userId — иначе индекс раздувается на каждое значение. Высококардинальное — в тело лога, поиск по нему всё равно работает.