Настройка структурированного логирования (JSON Logs) в приложениях

Текстовые логи превращаются в хаос: их не разбирает машина, инциденты расследуются часами, а объём хранения растёт без пользы. Мы настраиваем структурированное логирование под ключ — от аудита событий до схемы полей и регламентов для команды. Разбор инцидентов ускоряется в разы, шум в логах падает, расходы на хранение перестают расти.
Часто задаваемые вопросы

Текстовые логи нечитаемы: что решает структурированное логирование

Инцидент выглядит так: часть запросов к сервису отвечает ошибкой, и нужно понять, что отличается у успешных вызовов от неуспешных. Логи есть, но каждая ошибка записана свободным текстом, и одна и та же причина выглядит по-разному — «не удалось обработать заказ пользователя 42, таймаут» и «order 42 failed: timeout». Разбирать такое приходится глазами: инженер поднимает суточный архив, прогоняет его по регулярным выражениям, получает тысячи попаданий и вручную отсеивает соседние события.

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

Структурированное логирование меняет формат записи событий: вместо строки — набор полей с фиксированными именами и типами. Метка времени, уровень, имя сервиса, код ошибки, идентификатор запроса и трассировки лежат отдельными значениями, а произвольный текст остаётся в одном поле-сообщении. Поиск по логам приложения превращается в запрос с фильтром по полю: «все записи этого идентификатора трассировки», «все ответы с кодом 500 за интервал», «доля запросов дольше порога по каждому маршруту». Сборщик читает поля напрямую, строит графики и алерты, а не занимается разбором строк.

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

Эффект в эксплуатации: разбор инцидентов, шум и хранение логов

Структурированное логирование переводит наблюдаемость сервиса из режима «читаем глазами» в режим запросов по полям. Ниже — что именно проверяем на приёмке, чтобы переход дал измеримый эффект, а не просто сменил формат файлов.

  • Время локализации сбоя: по идентификатору запроса собираем всю цепочку вызовов между сервисами одной выборкой — дежурный находит причину за минуты вместо перебора файлов на нескольких хостах.
  • Шум в логах: уровни и обязательный набор полей заданы схемой, отладочные сообщения не уходят в боевой поток, а число записей на один запрос перестаёт расти вместе с нагрузкой.
  • Объём хранения логов: уровень, сервис и срок жизни записи описаны политикой, поэтому рост объёма считается заранее, а не выясняется при заполнении диска.
  • Разбор дежурства: метка времени, имя сервиса, идентификатор запроса и код ответа уже внутри записи — контекст инцидента читается без доступа к серверу.
  • Ушедший класс отказов: потеря логов при перезапуске контейнера и «молчащие» сервисы закрываются обязательной выгрузкой в общий сборщик Grafana Loki с проверкой доступности приёмника.

Каждый пункт подтверждаем на учебном инциденте: гасим один сервис и считаем, сколько шагов и времени занимает путь от сигнала до причины. Если причина находится по полям, без чтения сырого текста, — логирование работает.

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

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

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

Вариант Когда подходит Ограничения
Свободный текст Разовые утилиты, где лог читает только автор Полей нет: выборка строится по подстроке
Текст с шаблоном «ключ=значение» Действующий сервис, который нельзя менять целиком Договорённость о ключах держат вручную, вложенные структуры теряют форму
Фиксированная схема полей (JSON-объект на событие) События разбирает сборщик: поиск, оповещения, разбор инцидентов Нужен реестр полей и правило их изменения между командами
Потоковая передача в сборщик Много экземпляров сервиса, нужна общая картина сразу Обрыв канала требует буфера на диске, иначе события теряются

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

Внедрение по шагам: от аудита событий до передачи регламентов

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

  1. Аудит источников логов. Собираем инвентаризацию: какие сервисы пишут сами, какие отдают поток в stdout, у кого события размазаны по нескольким потокам и файлам. На выходе — таблица с частотой событий и суточным объёмом по каждому источнику.
  2. Проектирование схемы событий. Фиксируем обязательный набор полей: время, уровень, имя сервиса, идентификатор запроса, код ошибки, текст сообщения. Отдельно — словарь значений уровня и правила именования, чтобы error и err не жили в одном индексе.
  3. Настройка приложения. Запись уходит одной строкой на событие, без переносов внутри сообщения — иначе сборщик разорвёт её на части. Контекст запроса прокидывается через общую обёртку и попадает в каждую запись автоматически, включая фоновые задачи.
  4. Настройка сборщика логов. Разбор полей не нужен: маршрутизация идёт по значениям, добавляются буферизация на диск и ограничение скорости. Маскирование токенов и персональных данных выполняется на входе, до записи.
  5. Проверка на реальном сбое. Воспроизводим контролируемую ошибку и проходим цепочку целиком: запись в сервисе, сборщик, хранилище, поиск по идентификатору запроса. Отдельно смотрим, собираются ли в одну цепочку вызовы между сервисами.
  6. Контроль качества и регламенты. Настраиваем регулярные проверки: доля записей без обязательных полей, доля нераспознанных уровней, объём шума от проверок доступности. Передаём документ со схемой полей, правилами добавления новых событий и сроками хранения.

Артефакты: схемы полей, конфигурации сборщиков, регламенты

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

  • Словарь событий — имена событий с обязательными и необязательными полями, типами и допустимыми значениями. Он же фиксирует, что в лог не попадает: токены, тела запросов целиком, содержимое пользовательских файлов.
  • Контракт полей и правила его изменения — как добавлять и переименовывать поля, не ломая разбор в хранилище и уже построенные отчёты.
  • Правила обезличивания логов — что маскируется, что хешируется, где значение заменяется на идентификатор. Задаются в общей конфигурации, а не в коде каждого сервиса, поэтому применяются одинаково.
  • Конфигурация сбора логов — разбор потока, буферизация на стороне приложения, поведение при недоступности хранилища, ротация файлов и пороги, при которых запись отбрасывается вместо блокировки сервиса.
  • Политика хранения логов — сроки по типам событий, разделение оперативного и архивного хранения, порядок удаления. Отдельно указано, какие события живут дольше из-за разбора инцидентов.
  • Инструкция для дежурного — как собрать все события одного запроса, какие поля фильтровать первыми, готовые запросы под типовые сбои и порядок эскалации.
  • Регламент изменений схемы — кто согласует правку словаря, как проверяется совместимость и что делать со старыми записями после смены полей.

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

Кейс: разбор инцидента сократился с часов до минут

До: шесть форматов, поиск наугад

Платформа заказчика — шесть приложений: заказы, биллинг, уведомления, интеграции, админка и фоновые задачи. Логи каждое писало свободным текстом в свой файл на своём узле: время и уровень уходили внутрь строки сообщения, идентификатор запроса между сервисами не передавался. Разбор инцидента начинался с обхода узлов и поиска по обрывкам фраз — до первой версии причины проходило 3-4 часа, а шесть алертов из десяти оказывались ложными: срабатывали на текстовых совпадениях.

Что изменили и что получили

Ввели единую схему события: уровень, метка времени, сервис, идентификатор запроса, код операции, длительность, результат. Идентификатор запроса генерируется на входе и передаётся по всей цепочке внутренних вызовов — путь «запрос → сервис → хранилище → очередь → ответ» виден одним поиском по полю, а не подбором выражений по строкам. Сбор и поиск идут в OpenSearch.

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

Через три месяца: медианное время поиска причины — 8-10 минут вместо 3-4 часов, ложные срабатывания — один случай из десяти вместо шести, вдвое больше инцидентов дежурная смена закрывает без разработчиков. На стыке сервисов эффект заметнее всего: связка логов по единому идентификатору сразу показывает, какой вызов вернул ошибку и что ему предшествовало.

Чем лог отличается от метрики и трассировки?

Три сигнала отвечают на разные вопросы, и путаница начинается, когда один используют вместо другого. Метрика говорит «сколько и как часто»: это агрегат по числовому ряду — доля отказов, время ответа по хвостовому процентилю. Лог говорит «что произошло»: это событие с контекстом — идентификатор запроса, пользователь, параметры вызова. Трассировка — «где потерялось время»: путь запроса через сервисы с длительностью каждого шага.

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

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

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

Единый стандарт передачи телеметрии — OpenTelemetry — снимает привязку к вендору: бэкенд наблюдаемости приложения меняется без переписывания кода инструментирования.

Ограничения и частые вопросы по внедрению

У структурированного логирования есть цена, и честнее назвать её до внедрения. Запись «ключ — значение» занимает в два-три раза больше места, чем текстовая строка, а сериализация на горячем пути отнимает время у обработки самого запроса. Снимаем это уровнями — отладку на проде не пишем, — буферизацией записи и выносом её в отдельный поток, чтобы журнал не тормозил основной сценарий.

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

Не потеряем ли записи при всплеске нагрузки?

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

Старые сервисы придётся переписывать?

Нет. Приведение к общей схеме делается на стороне сборщика правилами разбора, приложение не трогаем. Переписываем только те сервисы, которые в ближайший год всё равно меняются по бизнес-логике — там новый формат ставим попутно.

Как отделить персональные данные от остальных полей?

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

Обсудим задачу и вернёмся с оценкой и планом

Разговор начинается с исходных данных — без них оценка превращается в предположение. Нам достаточно текущей конфигурации логирования приложений, примеров реальных записей из журналов, схемы развёртывания и того, куда сегодня уходят события: файлы на диске, stdout контейнера, внешний сборщик.

Что мы уточним на созвоне:

  • какие сервисы и среды попадают в объём работ, а какие остаются вне задачи;
  • какие события критичны для разбора сбоев, а какие создают шум и вытесняют полезные записи;
  • нужен ли сквозной идентификатор запроса, чтобы связать записи разных сервисов в одну цепочку;
  • как в компании решается вопрос персональных данных в журналах и кто отвечает за политику маскирования;
  • сроки хранения, объём потока событий и требования к скорости поиска по архиву;
  • кто со стороны заказчика принимает результат и в каком виде ожидает документацию.

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

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

Наши компетенции:

Последние работы

  • Разработка сайта компании B2B ADVANCE
    Разработка сайта компании B2B ADVANCE
    1542
  • Разработка веб-приложения для компании FEEDME
    Разработка веб-приложения для компании FEEDME
    1393
  • Разработка веб-сайта для компании БЕЛФИНГРУПП
    Разработка веб-сайта для компании БЕЛФИНГРУПП
    1081
  • Разработка интернет магазина для компании FURNORO
    Разработка интернет магазина для компании FURNORO
    1368
  • Разработка логотипа компании B2B Advance
    Разработка логотипа компании B2B Advance
    815
  • Разработка веб-приложения для компании Enviok
    Разработка веб-приложения для компании Enviok
    1139