Текстовый вывод журнала удобен человеку: строчки читаются глазами, ошибки выделяются, время видно сразу. Машинам нужен другой порядок: именованные поля, однозначные типы, предсказуемая структура. Журнальная утилита умеет отдавать записи в формате JSON, и это превращает системный журнал из текста для чтения в источник для программ: выгрузки парсятся без хрупких регулярных выражений, агрегации строятся за минуты, системы анализа получают готовые поля. Статья разбирает устройство формата, команды выгрузки, конвейеры с jq, конвертацию времени и подключение JSON-потока к внешним системам.

Формат JSON в журнале и его поля от сообщения до метки времени

Каждая запись журнала в JSON-режиме становится объектом с набором полей. Текст сообщения живёт в поле MESSAGE, приоритет в PRIORITY, источник в поле юнита, рядом идентификатор процесса, имя хоста и метка времени. Поля с двойным подчёркиванием в начале добавлены самим сборщиком и считаются доверенными: их нельзя подделать из приложения, поэтому на них опираются фильтры и отчёты.

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

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

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

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

Вывод журнала в JSON и варианты потоковой передачи

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

journalctl -u nginx -n 5 -o json-pretty --no-pager
journalctl --since "-1h" -p err -o json > errors.json

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

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

Для живых потребителей предусмотрен потоковый режим с обёрткой событий: записи оборачиваются в формат, который понимают браузерные и серверные клиенты подписки. Редкий, но честный сценарий: собственный наблюдатель за журналом без polling-циклов и самодельного слежения за файлами.

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

Парсинг через jq от простых выборок до агрегаций

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

journalctl --since "-1h" -o json | jq -r 'select(.PRIORITY=="3" or .PRIORITY=="4") | .MESSAGE' | sort | uniq -c | sort -rn | head

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

Третья ступень агрегации собирает записи в массив и считает по группам. Ключ slurp накапливает вход, группировка по полю приоритета с подсчётом элементов даёт распределение ошибок по уровням за окно. Так строятся ежедневные сводки: одно число вчерашних ошибок по каждому уровню вместо ручного просмотра.

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

Четвёртая ступень пересборка объектов. Парсер умеет не только выбирать, но и конструировать: новый объект из пары полей, компактная строка для следующего звена, массив значений одного поля. Это мостик к любому downstream-потребителю, от таблиц до систем мониторинга, без промежуточных скриптов на тяжёлых языках.

Конвертация меток времени и читаемые отчёты

Микросекундная метка конвертируется в парсере одной арифметикой и функцией форматирования: деление переводит микросекунды в секунды, функция дат печатает время в стандартном виде. Пара приёмов:

journalctl -n 3 -o json | jq -r '[((.__REALTIME_TIMESTAMP|tonumber)/1000000 | todate), .PRIORITY, .MESSAGE] | @tsv'

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

Форматная строка помогает там, где отчёт предназначен людям с местными привычками: часы, минуты и день недели выводятся так, как привыкла команда. Внутренние выгрузки при этом остаются в едином стандарте, и конвертация происходит на последнем шаге, перед показом. Такой порядок спасает от классической истории, когда отчёты с разных серверов расходились на три часа и никто не мог уверенно сказать, какому из них верить.

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

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

Интеграция выгрузок JSON с системами анализа

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

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

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

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

Развилка между разовыми и постоянными каналами решается объёмом. Один сервер и разовые разборы это скрипт с утилитой и файлы. Десяток серверов и постоянное наблюдение это агент на каждой машине и центральный приёмник. Формат один и тот же, меняется только доставка.

Структурированные записи от собственных сервисов

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

Выгода для эксплуатации прямая. Вопрос "найди все операции с таким идентификатором" решается фильтром по полю, а не поиском по тексту. Отчёт "распределение кодов результатов за день" строится группировкой по полю. Ошибки, которые раньше различал глаз по формулировкам, различает скрипт по значению.

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

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

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

Рабочие сценарии JSON-выгрузок и jq-конвейеров

Сценарии, в которых формат и парсер работают вместе, выглядят так:

  1. Протокол инцидента по окну времени в машинно-читаемом файле;
  2. Ежедневная сводка ошибок с распределением по приоритетам;
  3. Поиск записей по собственным полям сервисов без разбора текста;
  4. Потоковая передача живого журнала в JSON-совместимые приёмники;
  5. Табличные отчёты для протоколов с конвертированными метками времени;
  6. Периодические выгрузки по расписанию в системы анализа.

За сценариями стоит принцип: структура стоит дешевле догадок. Каждое поле, отданное форматом, это регулярное выражение, которое не пришлось писать, отлаживать и сопровождать. Регулярное выражение, которого не существует, это самое надёжное регулярное выражение из всех возможных.

Итоговое место формата в ремесле скромное и точное. Журнал уже хранит структуру, JSON-вывод просто перестаёт её прятать за текстом, а jq превращает её в выборки, отчёты и события для внешних систем. Пара привычных команд, пара конвейеров в личном файле практики, и системный журнал открывает свою вторую, машинную сторону, которая была там всё это время.
Напоследок о пограничной привычке: любая выборка, которая повторяется чаще раза в неделю, заслуживает собственной команды в личном справочнике. Конвейер журнала с парсером пишется один раз, потом живёт годами и переезжает с сервера на сервер вместе с администратором. Растущие справочники команд это признак растущего ремесла.