Текстовые логи нечитаемы: что решает структурированное логирование
Инцидент выглядит так: часть запросов к сервису отвечает ошибкой, и нужно понять, что отличается у успешных вызовов от неуспешных. Логи есть, но каждая ошибка записана свободным текстом, и одна и та же причина выглядит по-разному — «не удалось обработать заказ пользователя 42, таймаут» и «order 42 failed: timeout». Разбирать такое приходится глазами: инженер поднимает суточный архив, прогоняет его по регулярным выражениям, получает тысячи попаданий и вручную отсеивает соседние события.
Цена такой работы растёт вместе с объёмом. В архиве — гигабайты строк, шаблон поиска не покрывает все формулировки, а время ответа сервиса и код ошибки приходится вытаскивать из текста отдельными выражениями. Разбор инцидента по логам занимает часы, восстановление затягивается, и часть выводов остаётся догадками: сгруппировать записи по коду ошибки или посчитать долю медленных запросов по такому файлу технически можно, но на практике никто этого не делает.
Структурированное логирование меняет формат записи событий: вместо строки — набор полей с фиксированными именами и типами. Метка времени, уровень, имя сервиса, код ошибки, идентификатор запроса и трассировки лежат отдельными значениями, а произвольный текст остаётся в одном поле-сообщении. Поиск по логам приложения превращается в запрос с фильтром по полю: «все записи этого идентификатора трассировки», «все ответы с кодом 500 за интервал», «доля запросов дольше порога по каждому маршруту». Сборщик читает поля напрямую, строит графики и алерты, а не занимается разбором строк.
Нечитаемые логи перестают быть проблемой отдельного инженера — они становятся данными, по которым считается причина. Ниже разбираем, как это закрывается в приложении и в контуре сбора.
Эффект в эксплуатации: разбор инцидентов, шум и хранение логов
Структурированное логирование переводит наблюдаемость сервиса из режима «читаем глазами» в режим запросов по полям. Ниже — что именно проверяем на приёмке, чтобы переход дал измеримый эффект, а не просто сменил формат файлов.
- Время локализации сбоя: по идентификатору запроса собираем всю цепочку вызовов между сервисами одной выборкой — дежурный находит причину за минуты вместо перебора файлов на нескольких хостах.
- Шум в логах: уровни и обязательный набор полей заданы схемой, отладочные сообщения не уходят в боевой поток, а число записей на один запрос перестаёт расти вместе с нагрузкой.
- Объём хранения логов: уровень, сервис и срок жизни записи описаны политикой, поэтому рост объёма считается заранее, а не выясняется при заполнении диска.
- Разбор дежурства: метка времени, имя сервиса, идентификатор запроса и код ответа уже внутри записи — контекст инцидента читается без доступа к серверу.
- Ушедший класс отказов: потеря логов при перезапуске контейнера и «молчащие» сервисы закрываются обязательной выгрузкой в общий сборщик Grafana Loki с проверкой доступности приёмника.
Каждый пункт подтверждаем на учебном инциденте: гасим один сервис и считаем, сколько шагов и времени занимает путь от сигнала до причины. Если причина находится по полям, без чтения сырого текста, — логирование работает.
Структурированное логирование против текстового: сравнение режимов
Режим записи событий определяет, кто будет потребителем лога — человек или сборщик. Свободный текст читается глазами, но лишь до первой правки формулировки: дальше разбор на стороне сбора перестаёт совпадать с сообщением, и часть событий молча выпадает из выборки. Структурированная запись переносит разбор в приложение — потребитель получает готовые поля и фильтрует по ним, не угадывая формат.
Предсказуемость даёт схема события, единая для всех сервисов: метка времени, уровень логирования, идентификатор запроса, имя сервиса, код события и вложенный контекст. Уровни тогда работают как политика, а не как пометка в строке: отладочные записи уходят в отдельный контур, предупреждения и ошибки идут в общий поток и не тонут в нём.
| Вариант | Когда подходит | Ограничения |
|---|---|---|
| Свободный текст | Разовые утилиты, где лог читает только автор | Полей нет: выборка строится по подстроке |
| Текст с шаблоном «ключ=значение» | Действующий сервис, который нельзя менять целиком | Договорённость о ключах держат вручную, вложенные структуры теряют форму |
| Фиксированная схема полей (JSON-объект на событие) | События разбирает сборщик: поиск, оповещения, разбор инцидентов | Нужен реестр полей и правило их изменения между командами |
| Потоковая передача в сборщик | Много экземпляров сервиса, нужна общая картина сразу | Обрыв канала требует буфера на диске, иначе события теряются |
На практике берут гибрид: человекочитаемая строка в консоль при локальном запуске и структурная запись в сборщик для рабочих контуров. Переход ведут по сервисам — начиная с тех, где разбор инцидента занимает больше всего времени, и с реестром полей, чтобы новое событие не требовало правок на стороне приёма.
Внедрение по шагам: от аудита событий до передачи регламентов
Последовательность выстроена так, чтобы схема событий появилась раньше, чем первый лог уйдёт в хранилище: переделывать формат на работающих сервисах дороже, чем договориться о нём на старте. Ниже — порядок, который проходит каждый проект.
- Аудит источников логов. Собираем инвентаризацию: какие сервисы пишут сами, какие отдают поток в stdout, у кого события размазаны по нескольким потокам и файлам. На выходе — таблица с частотой событий и суточным объёмом по каждому источнику.
- Проектирование схемы событий. Фиксируем обязательный набор полей: время, уровень, имя сервиса, идентификатор запроса, код ошибки, текст сообщения. Отдельно — словарь значений уровня и правила именования, чтобы
errorиerrне жили в одном индексе. - Настройка приложения. Запись уходит одной строкой на событие, без переносов внутри сообщения — иначе сборщик разорвёт её на части. Контекст запроса прокидывается через общую обёртку и попадает в каждую запись автоматически, включая фоновые задачи.
- Настройка сборщика логов. Разбор полей не нужен: маршрутизация идёт по значениям, добавляются буферизация на диск и ограничение скорости. Маскирование токенов и персональных данных выполняется на входе, до записи.
- Проверка на реальном сбое. Воспроизводим контролируемую ошибку и проходим цепочку целиком: запись в сервисе, сборщик, хранилище, поиск по идентификатору запроса. Отдельно смотрим, собираются ли в одну цепочку вызовы между сервисами.
- Контроль качества и регламенты. Настраиваем регулярные проверки: доля записей без обязательных полей, доля нераспознанных уровней, объём шума от проверок доступности. Передаём документ со схемой полей, правилами добавления новых событий и сроками хранения.
Артефакты: схемы полей, конфигурации сборщиков, регламенты
После внедрения у команды остаётся не набор настроек на сервере, а комплект файлов и документов, по которым структурированное логирование можно расширять без нас. Все артефакты версионируются в том же репозитории, что и код сервисов: правка проходит ревью и не живёт в чужой голове.
- Словарь событий — имена событий с обязательными и необязательными полями, типами и допустимыми значениями. Он же фиксирует, что в лог не попадает: токены, тела запросов целиком, содержимое пользовательских файлов.
- Контракт полей и правила его изменения — как добавлять и переименовывать поля, не ломая разбор в хранилище и уже построенные отчёты.
- Правила обезличивания логов — что маскируется, что хешируется, где значение заменяется на идентификатор. Задаются в общей конфигурации, а не в коде каждого сервиса, поэтому применяются одинаково.
- Конфигурация сбора логов — разбор потока, буферизация на стороне приложения, поведение при недоступности хранилища, ротация файлов и пороги, при которых запись отбрасывается вместо блокировки сервиса.
- Политика хранения логов — сроки по типам событий, разделение оперативного и архивного хранения, порядок удаления. Отдельно указано, какие события живут дольше из-за разбора инцидентов.
- Инструкция для дежурного — как собрать все события одного запроса, какие поля фильтровать первыми, готовые запросы под типовые сбои и порядок эскалации.
- Регламент изменений схемы — кто согласует правку словаря, как проверяется совместимость и что делать со старыми записями после смены полей.
Панели по частоте ошибок и задержкам собираются поверх тех же полей — отдельного источника данных для них не нужно.
Кейс: разбор инцидента сократился с часов до минут
До: шесть форматов, поиск наугад
Платформа заказчика — шесть приложений: заказы, биллинг, уведомления, интеграции, админка и фоновые задачи. Логи каждое писало свободным текстом в свой файл на своём узле: время и уровень уходили внутрь строки сообщения, идентификатор запроса между сервисами не передавался. Разбор инцидента начинался с обхода узлов и поиска по обрывкам фраз — до первой версии причины проходило 3-4 часа, а шесть алертов из десяти оказывались ложными: срабатывали на текстовых совпадениях.
Что изменили и что получили
Ввели единую схему события: уровень, метка времени, сервис, идентификатор запроса, код операции, длительность, результат. Идентификатор запроса генерируется на входе и передаётся по всей цепочке внутренних вызовов — путь «запрос → сервис → хранилище → очередь → ответ» виден одним поиском по полю, а не подбором выражений по строкам. Сбор и поиск идут в OpenSearch.
Правила хранения настроили отдельно: проверки доступности и рутинные опросы ушли в отладочный уровень и пишутся не по каждому вызову, горячий контур держит две недели, дальше данные переезжают в холодное хранилище. Объём логов вырос примерно втрое, но без этих правил рост был бы кратно больше, а расходы на хранение перестали расти вместе с трафиком.
Через три месяца: медианное время поиска причины — 8-10 минут вместо 3-4 часов, ложные срабатывания — один случай из десяти вместо шести, вдвое больше инцидентов дежурная смена закрывает без разработчиков. На стыке сервисов эффект заметнее всего: связка логов по единому идентификатору сразу показывает, какой вызов вернул ошибку и что ему предшествовало.
Чем лог отличается от метрики и трассировки?
Три сигнала отвечают на разные вопросы, и путаница начинается, когда один используют вместо другого. Метрика говорит «сколько и как часто»: это агрегат по числовому ряду — доля отказов, время ответа по хвостовому процентилю. Лог говорит «что произошло»: это событие с контекстом — идентификатор запроса, пользователь, параметры вызова. Трассировка — «где потерялось время»: путь запроса через сервисы с длительностью каждого шага.
Разница между логом и метрикой видна при разборе: по графику понятно, что отказы выросли после выката, но не на каком входе. Метрика намеренно теряет контекст ради дешёвого хранения и быстрой агрегации. Поэтому в метрику выносят сигналы, на которых строятся оповещения, а в структурированное логирование — то, что разбирают по конкретному случаю.
Трассировка запроса закрывает третий слой: когда обработка идёт через несколько сервисов и очередь, одна запись в логе не покажет, где накопились почти все задержки. Идентификатор трассировки в каждой записи лога даёт переход от графика к конкретному вызову.
Главная ошибка — дублирование телеметрии: один факт пишут сразу в метрику, лог и трассировку. Это раздувает хранение, ускоряет расхождение систем и запутывает выбор инструмента диагностики: дежурный открывает три панели и не понимает, какая верна. Рабочее правило — у каждого факта один дом, остальные сигналы ссылаются на него идентификатором.
Единый стандарт передачи телеметрии — OpenTelemetry — снимает привязку к вендору: бэкенд наблюдаемости приложения меняется без переписывания кода инструментирования.
Ограничения и частые вопросы по внедрению
У структурированного логирования есть цена, и честнее назвать её до внедрения. Запись «ключ — значение» занимает в два-три раза больше места, чем текстовая строка, а сериализация на горячем пути отнимает время у обработки самого запроса. Снимаем это уровнями — отладку на проде не пишем, — буферизацией записи и выносом её в отдельный поток, чтобы журнал не тормозил основной сценарий.
Второе ограничение — неоднородный контур: часть сервисов пишет в старом текстовом формате, и общая схема по ним не собирается, пока сборщик не разберёт их правилом. Третье — чувствительные данные: в полях запроса регулярно оказываются токены, адреса, идентификаторы и персональные данные. Их маскируем на приёме, до записи в хранилище, иначе общий индекс открывает их каждому, у кого есть право на чтение журнала.
Не потеряем ли записи при всплеске нагрузки?
Доставка — самая тихая точка отказа: буфер сборщика переполняется, и часть записей теряется без единой ошибки в приложении. Поэтому считаем не только объём, но и пропускную способность приёма, включаем дисковый буфер и держим долю отброшенных записей отдельной метрикой с оповещением.
Старые сервисы придётся переписывать?
Нет. Приведение к общей схеме делается на стороне сборщика правилами разбора, приложение не трогаем. Переписываем только те сервисы, которые в ближайший год всё равно меняются по бизнес-логике — там новый формат ставим попутно.
Как отделить персональные данные от остальных полей?
Проводим разбор по схеме и списку полей: что считается идентификатором, что — телом операции. Дальше маскирование на приёме, отдельные потоки хранения с сокращённым сроком и разграничением доступа, чтобы аналитика шла по обезличенным срезам.
Обсудим задачу и вернёмся с оценкой и планом
Разговор начинается с исходных данных — без них оценка превращается в предположение. Нам достаточно текущей конфигурации логирования приложений, примеров реальных записей из журналов, схемы развёртывания и того, куда сегодня уходят события: файлы на диске, stdout контейнера, внешний сборщик.
Что мы уточним на созвоне:
- какие сервисы и среды попадают в объём работ, а какие остаются вне задачи;
- какие события критичны для разбора сбоев, а какие создают шум и вытесняют полезные записи;
- нужен ли сквозной идентификатор запроса, чтобы связать записи разных сервисов в одну цепочку;
- как в компании решается вопрос персональных данных в журналах и кто отвечает за политику маскирования;
- сроки хранения, объём потока событий и требования к скорости поиска по архиву;
- кто со стороны заказчика принимает результат и в каком виде ожидает документацию.
Дальше мы проводим аудит текущего логирования: смотрим, какие поля пишутся сейчас, где теряется контекст, как устроены уровни важности и ротация. По итогам возвращаемся с планом работ по внедрению — порядок перевода сервисов, схема обязательных полей, настройки сборщика и хранения, разбитый на этапы с понятным результатом на каждом.
Отдельным пунктом идёт передача знаний команде: описание формата записей, правила добавления новых полей, готовые шаблоны запросов к журналам и разбор на живых примерах. Опишите задачу логирования — вернёмся с оценкой и планом.







