Site icon Заметки разработчика

logmefmt: зачем нужен post-processing логов

logmefmt: зачем нужен post-processing логов

Логирование почти всегда начинается с простого вопроса: в каком формате писать сообщения?

Текстовый лог удобно читать человеку. Его можно открыть в less, Notepad++, tail -f или приложить к тикету. Он хорошо подходит для быстрой диагностики, особенно когда инженер просто хочет увидеть последовательность событий.

JSON удобнее машине. Его проще загружать в систему анализа, отправлять в pipeline, индексировать, фильтровать и связывать с другими источниками данных. XML может быть нужен старым внутренним инструментам, интеграциям или существующему формату обмена.

Но реальная система редко живет в одном формате вечно. Сегодня сервис пишет text log, завтра его нужно отдать в JSON pipeline. Старый диагностический архив нужно привести к единой схеме. Команда поддержки хочет читать текст, а центральная система анализа ожидает structured events. В таких случаях нужен post-processing логов.

Для этого в logme есть logmefmt.

logmefmt преобразует уже существующие записи logme между text, JSON и XML. Он не заменяет logging backend и не требует, чтобы приложение было переписано. Это отдельный инструмент, который берет готовый лог, разбирает его как набор records и выводит в другом представлении.

Direct structured output и post-processing решают разные задачи

В logme можно сразу писать JSON или XML через output format и file backend. Если заранее известно, что лог будет отправляться в систему аналитики, это обычно лучший путь. Приложение сразу создает structured records, и отдельная конвертация не требуется.

Но direct JSON output не отменяет необходимость в logmefmt.

Во-первых, в проекте уже могут существовать годы текстовых логов. Их нельзя задним числом переписать через backend, но можно преобразовать в structured format для анализа.

Во-вторых, один и тот же лог иногда нужен в двух формах. Локально команде удобно читать обычный text log. Позже тот же файл можно преобразовать в JSONL и загрузить в инструмент поиска или обработки.

В-третьих, формат логирования и схема приема данных не всегда совпадают. Например, logme пишет поле level, а внешняя система ожидает severity. Сообщение хранится в message, а downstream pipeline требует msg. logmefmt позволяет адаптировать такие поля без изменения приложения.

То есть post-processing логов — это не обходной путь вместо нормального logging API. Это способ не связывать формат уже записанного лога с единственным будущим сценарием его использования.

Как logmefmt видит лог

logmefmt работает с отдельными записями. Для text input он распознает стандартный префикс logme и извлекает поля, если они есть в строке.

Например, из записи:

2026-05-03 10:00:00:123 D [04D2:162E] {NET} #HTTP main.cpp(42): Connect(): connected

инструмент может извлечь timestamp, уровень, process id, thread id, channel, subsystem, файл, строку, метод и само сообщение.

После преобразования в JSON получится запись такого вида:

{"timestamp":"2026-05-03 10:00:00:123","level":"DEBUG","process_id":"04D2","thread_id":"162E","channel":"NET","subsystem":"HTTP","file":"main.cpp","line":"42","method":"Connect","message":"connected"}

Это особенно полезно для старых text logs. В исходном файле вся информация уже была, но она была удобна главным образом человеку. После обработки поля становятся явными и пригодными для машинного анализа.

Если строка не похожа на стандартную запись logme, logmefmt не пытается искусственно придумать metadata. Она сохраняется как message.

printf 'plain application output\n' \
  | logmefmt --input text --output json

Результат:

{"message":"plain application output"}

Это важно для mixed logs, где рядом с logme records могут оказаться сообщения сторонней библиотеки, shell output или обычный diagnostic text.

Основной сценарий: text log в JSONL

Самый частый post-processing workflow — преобразовать существующий text log в line-oriented JSON:

logmefmt --input text --output json \
  --in app.log \
  --out app.jsonl

Без --finalize инструмент пишет один JSON object на одну входную запись. Такой формат обычно называют JSONL или JSON Lines.

Он удобен для pipeline-ов, потому что файл можно читать и обрабатывать построчно. Не нужно ждать окончания всего лога, чтобы получить валидный record. Это особенно хорошо подходит для streaming обработки, импорта в аналитические системы и простых shell pipelines.

Например:

logmefmt --input text --output json < app.log \
  | some-log-import-tool

Одна текстовая строка превращается в один structured event.

Если нужен не поток records, а готовый JSON document для передачи как файла, можно использовать --finalize:

logmefmt --input text --output json --finalize \
  --in app.log \
  --out app.json

Тогда output будет JSON array:

[
  {"message":"first event"},
  {"message":"second event"}
]

Это удобнее для tools, которые ожидают один законченный JSON document, а не JSONL stream.

XML работает по тому же принципу

XML output может быть полезен, когда в проекте уже есть XML-based ingestion, внутренние отчеты или старые системы интеграции.

Без --finalize каждая запись становится отдельным <event> element:

logmefmt --input text --output xml < app.log

Результат выглядит как поток фрагментов:

<event><level>ERROR</level><message>connection failed</message></event>
<event><level>WARN</level><message>retry scheduled</message></event>

Это удобно для append-friendly обработки. Но это еще не полный XML document.

Когда нужен полноценный файл, добавляется --finalize:

logmefmt --input text --output xml --finalize \
  --root events \
  --in app.log \
  --out app.xml

Тогда output получает общий root element:

<events>
  <event><level>ERROR</level><message>connection failed</message></event>
  <event><level>WARN</level><message>retry scheduled</message></event>
</events>

--root позволяет выбрать имя корневого элемента. По умолчанию используется log.

Нормализация схемы без изменения приложения

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

Например, текстовый logme log может содержать level и message, а приемник ожидает severity и msg:

logmefmt --input text --output json \
  --output-field level=severity \
  --output-field message=msg \
  --in app.log \
  --out app.jsonl

Результат:

{"timestamp":"2026-05-03 10:00:00:123","severity":"WARN","msg":"retry"}

--output-field применяется к данным перед записью. Это удобно, когда внутреннюю схему logme менять не нужно, но внешний consumer имеет свои требования.

Если поле нужно переименовать еще до преобразования, используется --input-field. А --field делает оба шага сразу: переименовывает поле и на входе, и на выходе.

Такой подход полезнее, чем добавлять в приложение специфический код только ради одной интеграции. Сервис продолжает писать понятный logme log, а схема для конкретного consumer-а задается снаружи.

Преобразование structured logs в text

logmefmt может работать и в обратную сторону:

logmefmt --input json --output text \
  --in app.jsonl \
  --out app-readable.log

Но здесь нужно понимать смысл text output.

Если в record есть поле message или msg, инструмент выводит именно его. Он не пытается полностью восстановить исходный logme prefix с timestamp, level, channel, file и method в прежнем точном виде.

Это намеренное упрощение. JSON или XML могут содержать другую схему, другие поля и другой порядок данных. Поэтому structured → text в logmefmt — это прежде всего способ получить читаемый message-oriented view, а не обещание побайтного восстановления исходного text log.

Если record вообще не содержит message или msg, text output превращается в набор field=value:

level=ERROR channel=NET code=42

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

Почему инструмент ориентирован на line-oriented records

logmefmt рассчитан на поток записей: одна входная строка соответствует одной логической записи.

Это хорошо сочетается с обычными log files, JSONL и XML event fragments. Такие данные легко передавать через stdin/stdout, обрабатывать в shell pipeline, читать по частям и не держать весь файл в памяти.

Но это не универсальный parser для произвольных JSON documents или XML files любой сложности. Например, JSON input здесь логически представляет поток отдельных objects, а не большой вложенный JSON array с произвольной структурой. XML input тоже ориентирован на отдельные event records.

Эта ограниченность намеренная. logmefmt — легкий utility для преобразования log streams, а не общий ETL engine и не замена полноценной системы аналитики.

Где post-processing особенно полезен

Post-processing полезен, когда лог уже существует и его формат нельзя изменить задним числом. Например, после инцидента есть большой text archive, но нужно быстро выделить ошибки конкретного channel-а в downstream tool. Или нужно отправить выбранный лог в систему, которая принимает только JSONL.

Он также удобен при постепенной миграции. Команда может продолжать писать text logs, пока не договорится о единой structured schema. Затем existing logs и новые файлы приводятся к нужному виду через один и тот же conversion step.

Еще один хороший сценарий — support workflow. Приложение может писать читаемый text log для локальной диагностики, а инженер при необходимости конвертирует его в JSON/XML для автоматического анализа, отчета или загрузки в другую систему.

Когда лучше не использовать logmefmt

Если известно, что единственный нужный output — JSON, лучше сразу настроить JSON output в logme. Тогда поля появляются structured с самого начала, а conversion step не нужен.

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

Именно поэтому инструмент остается простым, предсказуемым и пригодным для pipelines.

Итог

logmefmt нужен не потому, что logme не умеет писать JSON или XML напрямую. Он нужен потому, что формат log output часто приходится менять после записи.

Text logs удобны для человека. JSONL удобен для потоковой обработки. Finalized JSON и XML удобны для передачи как законченного документа. Внешняя система может требовать другие имена полей, чем те, которые естественно использует приложение.

logmefmt связывает эти сценарии. Он преобразует existing logs между text, JSON и XML, извлекает standard logme metadata из text prefix, сохраняет неизвестные строки как message и позволяет нормализовать поля без изменения application code.

Это и есть практический смысл post-processing логов: не заставлять формат, удобный сегодня, ограничивать способы анализа завтра.

Exit mobile version