Перейти к содержанию
Innovialabs

Структурированные логи: как писать логи, по которым легко искать

Практика3 мин

Коротко

Пишите логи в JSON с постоянным набором полей — время, уровень, сервис, сообщение и request ID; тогда любую ошибку можно найти фильтром, а не чтением тысяч строк.

Что такое структурированный лог

Обычный лог — это строка текста: User 42 failed to pay order 981. Человеку понятно, но машине нужно её разбирать регулярками, которые ломаются при любой правке формулировки.

Структурированный лог — это запись с именованными полями, чаще всего в JSON:

{"ts":"2026-03-14T10:22:05Z","level":"error","service":"billing","msg":"payment failed","user_id":42,"order_id":981,"request_id":"a1b2c3"}

Такую запись система сбора логов понимает сразу: можно отфильтровать все ошибки сервиса billing, посчитать их по часам или найти все события одного запроса.

Обязательные поля

Договоритесь о едином наборе полей для всех сервисов:

  • ts — время в UTC в формате ISO 8601.
  • level — уровень записи.
  • service и env — какой сервис и в каком окружении.
  • msg — короткое постоянное описание события, без подстановки переменных.
  • request_id / trace_id — идентификатор, связывающий записи одного запроса.
  • error — тип и текст ошибки, stack trace — отдельным полем.

Ключевая привычка: переменные — в поля, а не в текст сообщения. msg: "payment failed" одинаков для всех случаев, поэтому его легко сгруппировать.

Уровни логирования

УровеньКогда использовать
debugДетали для разработки; в продакшене обычно выключен
infoЗначимые штатные события: запуск, завершение задачи, вход пользователя
warnЧто-то пошло не так, но система справилась: повтор запроса, fallback
errorОперация не выполнена, нужна реакция
fatalПроцесс не может продолжать работу

Если error-записи появляются при нормальной работе, на них перестают реагировать. Уровень должен означать действие, а не настроение разработчика.

Request ID и correlation ID

Когда запрос проходит через фронтенд, API, очередь и воркер, его нужно отследить целиком.

  1. На входе (балансировщик или первый сервис) генерируется уникальный ID, если его ещё нет в заголовке.
  2. ID кладётся в контекст логгера, чтобы попадать в каждую запись автоматически.
  3. При вызове других сервисов ID передаётся дальше в заголовке, например X-Request-ID или стандартном traceparent из W3C Trace Context.
  4. ID возвращается клиенту в ответе — пользователь может назвать его поддержке.

Если вы используете OpenTelemetry, берите trace_id оттуда: логи сразу свяжутся с трейсами.

Что никогда не писать в логи

  • Пароли, токены, API-ключи, cookie сессий, заголовок Authorization.
  • Номера карт, паспортные данные и другие чувствительные персональные данные.
  • Полные тела запросов и ответов «на всякий случай».
  • Содержимое файлов и большие бинарные данные.

Настройте маскирование (redaction) на уровне логгера, а не надейтесь на внимательность каждого разработчика.

Примеры

Node.js (pino):

import pino from "pino";

const logger = pino({
  base: { service: "billing" },
  redact: ["req.headers.authorization", "password"],
});

const log = logger.child({ request_id: "a1b2c3" });
log.error({ user_id: 42, order_id: 981 }, "payment failed");

Python (structlog):

import structlog

structlog.configure(processors=[
    structlog.processors.add_log_level,
    structlog.processors.TimeStamper(fmt="iso", utc=True),
    structlog.processors.JSONRenderer(),
])

log = structlog.get_logger().bind(service="billing", request_id="a1b2c3")
log.error("payment failed", user_id=42, order_id=981)

Go (log/slog):

logger := slog.New(slog.NewJSONHandler(os.Stdout, nil)).
    With("service", "billing", "request_id", "a1b2c3")
logger.Error("payment failed", "user_id", 42, "order_id", 981)

Частые ошибки

  • Разные имена одного поля в разных сервисах: userId, user_id, uid.
  • Логирование в цикле на каждой итерации — шум и лишние расходы.
  • Многострочные stack trace в обычном тексте, которые разрываются на отдельные записи.
  • Запись в файлы внутри контейнера вместо stdout.

Частые вопросы

В локальной разработке включите «красивый» вывод (например, pino-pretty или консольный рендерер structlog), а в продакшене оставьте JSON — его читает система сбора логов.

Поможем с этим

Если хотите не разбираться самим, а получить результат — этим мы занимаемся каждый день.

Обсудить проект

Читайте также

Есть идея или задача? Расскажите нам

Ответим в течение рабочего дня и предложим решение. Консультация бесплатная.