Структурированное логирование: JSON вместо grok

Лучший grok — это его отсутствие: если приложение пишет логи сразу в JSON, парсить нечего.

Структурированное логирование — практика, при которой приложение пишет каждое событие сразу в машиночитаемом формате (обычно JSON) с уже выделенными полями, а не как человекочитаемую строку.

Корень проблемы grok

Весь предыдущий раздел мы учились распутывать текстовые логи регулярками. Но задумайтесь: приложение сначала взяло структурированные данные (объект ошибки, id заказа, статус) и склеило их в строку "payment failed for order 8842: timeout". А потом мы героически разбираем эту строку обратно на поля grok'ом. Это лишняя работа в обе стороны, и она хрупкая: чуть изменился формат строки — grok сломался.

Решение: писать сразу JSON

Если приложение логирует структурированно, событие уже приходит готовым:

{
  "@timestamp": "2026-06-24T14:32:07Z",
  "level": "ERROR",
  "service": "billing",
  "message": "payment failed",
  "order_id": 8842,
  "reason": "gateway timeout",
  "trace_id": "a1b2c3d4"
}

Здесь нечего парсить: order_id уже число, level уже отдельное поле, trace_id на месте. Filebeat прочитает строку, а разбор — это тривиальный JSON-декодер, а не хрупкая регулярка.

Как включить

В большинстве языков есть логгеры для JSON-вывода: structlog или python-json-logger в Python, Logback с JSON-энкодером в Java, pino/winston в Node, zap в Go. На стороне сбора JSON разбирается одной строкой — либо в Filebeat (parsers: - ndjson), либо фильтром Logstash:

filter {
  json {
    source => "message"
  }
}

Почему это лучше: ECS и единый стандарт

Elastic продвигает ECS (Elastic Common Schema) — стандарт имён полей: source.ip, http.response.status_code, service.name, log.level. Когда все сервисы пишут JSON по ECS, поля во всех логах называются одинаково. Тогда один дашборд «ошибки по сервисам» работает сразу для всех, а не требует подгонки под каждый формат. Это и есть переход от «каждый сервис логирует как хочет» к единому языку логов в компании.

Как работает под капотом: где экономия

Сравним два пути обработки одной ошибки:

  ТЕКСТ + grok:
  объект --склейка--> строка --grok(regex+backtracking)--> поля
         (в приложении)          (дорого, хрупко, в Logstash)

  СТРУКТУРИРОВАННО (JSON):
  объект --JSON-encode--> строка --JSON-decode--> поля
         (в приложении)          (дёшево, надёжно)

JSON-декодирование на порядок дешевле grok-регулярки и не ломается от перестановки слов в сообщении. На больших объёмах это заметная экономия CPU на узлах обработки и отсутствие класса проблем «логи перестали парситься после релиза».

Что считать хорошим набором полей

Перейдя на структурированные логи, команда сталкивается с новым вопросом: какие именно поля логировать? Полезный базовый набор для каждого события: точное время (@timestamp), уровень (log.level), имя сервиса и его версия (service.name, service.version), идентификатор для корреляции (trace_id), краткое человекочитаемое сообщение (message) и — для ошибок — тип исключения и стектрейс в отдельных полях. К этому добавляют доменный контекст события: для веб-запроса это метод, путь, статус, длительность; для фоновой задачи — её имя и параметры. Ключевой принцип — контекст кладут в отдельные поля, а не зашивают в текст message, иначе теряется весь смысл структурирования.

Есть и тонкость с самим полем message. В структурированном мире оно должно быть стабильным шаблоном без переменных частей: не «user 8842 logged in», а message со значением «user logged in» плюс отдельное поле user_id со значением 8842. Стабильное сообщение позволяет агрегировать одинаковые по смыслу события вместе («сколько раз был тип события user logged in»), тогда как сообщение с вкраплённым id уникально для каждого случая и не группируется. Это маленькое правило радикально повышает аналитическую ценность логов.

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

  • Взрыв полей через JSON. Свобода JSON опасна: если логировать произвольные ключи (например, user_42_action), Elasticsearch заведёт новое поле под каждый — это «mapping explosion». Держите набор ключей фиксированным.
  • Дублировать @timestamp и message. Если в JSON есть свои @timestamp/message, согласуйте их с тем, что добавляет Filebeat, иначе будет путаница полей.
  • Логировать секреты. Структурированный лог легко превращается в утечку: пароль или токен в поле JSON попадёт в индекс. Маскируйте чувствительные поля до отправки.

Итоги

  • Структурированное (JSON) логирование избавляет от хрупкого grok: поля приходят готовыми.
  • JSON-декодирование дешевле и надёжнее регулярок и не ломается от смены формата строки.
  • Единый стандарт имён полей (ECS) делает дашборды переносимыми между всеми сервисами.
  • Осторожно с произвольными ключами (взрыв полей) и секретами в логах.
Проверьте себя
1. В чём главное преимущество структурированного (JSON) логирования перед текстовыми логами с grok?
AJSON-логи занимают меньше места
BПоля приходят уже готовыми — парсинг сводится к дешёвому JSON-декодированию и не ломается от смены формата строки
CJSON быстрее пишется на диск
Dgrok вообще не работает с JSON
2. Что такое ECS (Elastic Common Schema)?
AФормат сжатия логов
BСтандарт единых имён полей (source.ip, log.level и т. д.), благодаря которому дашборды работают для всех сервисов одинаково
CЯзык запросов Kibana
DТип индекса в Elasticsearch
3. Какой риск несёт свобода JSON-логирования с произвольными ключами?
AЛоги становятся нечитаемыми
BВзрыв полей (mapping explosion): Elasticsearch заводит новое поле под каждый уникальный ключ
CJSON перестаёт парситься
DKibana не сможет открыть индекс