Когда на сервере что-то ломается, первой остановкой для инженера всё чаще становится журнал systemd: в одну очередь выстраиваются сообщения ядра, служб, сеансов пользователей и обычных процессов. Утилита journalctl читает этот журнал и отдаёт записи по любым фильтрам: по службе, по времени, по важности, по тексту сообщения. В умелых руках она закрывает половину диагностики: находит упавшую службу за секунду, вырезает окно аварии по минутам, показывает, что происходило перед внезапной перезагрузкой, и подсказывает, куда делось место на диске. Ниже собрано больше двух десятков команд на все эти случаи, от слежения в реальном времени до уборки журнала. Примеры проверены на сервере с веткой systemd 259, которую продолжают сопровождать, мажорная версия с номером 262 вышла в конце сентября 2026 года, а показанные ключи работают одинаково на выпусках последних лет.

Как устроен журнал systemd и почему его не прочитать обычным cat

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

PRIORITY=6
SYSLOG_IDENTIFIER=CRON
_COMM=cron
_SYSTEMD_UNIT=cron.service
_EXE=/usr/sbin/cron
_PID=1093386
MESSAGE=pam_unix(cron:session): session closed for user root

Видно и программу, и путь к исполняемому файлу, и юнит, из которого прилетела строка. Именно по этим полям строятся фильтры, показанные дальше.

Файлы журнала живут в двух местах. Каталог /run/log/journal размещается в памяти и исчезает при перезагрузке, /var/log/journal лежит на диске и переживает её. Что из них задействовано, выбирает настройка Storage в /etc/systemd/journald.conf: значение auto включает запись на диск, только если каталог уже создан, persistent создаёт его и ведёт журнал всегда, volatile держит всё в памяти, none отключает журнал полностью. Первое знакомство начинается с трёх безобидных команд:

journalctl --version
ls /run/log/journal /var/log/journal
journalctl -b -n 5 --no-pager

На многих дистрибутивах рядом продолжает работать rsyslog, переписывающий часть записей в текстовые файлы вроде /var/log/syslog. Журнал systemd его не отменяет, а дополняет: у него точнее метки времени, шире охват и есть поля, которых в текстовых логах нет вообще.

Слежение за конкретной службой в реальном времени и хвост последних строк

Ключ -u ограничивает выдачу конкретным юнитом, а в связке с -f превращает терминал в окно слежения, где новые записи появляются по мере поступления. Самая частая команда при разборах:

journalctl -f -u nginx.service
journalctl -f -u myapp -u myapp-worker
journalctl -u myapp -n 200 --no-pager
journalctl -e -u myapp

Флаг -f включает режим, знакомый всякому по tail. Несколько -u складываются в одну выдачу, что удобно для пары фронтенда и бэкенда. Ключ -n показывает последние двести строк без прокрутки, а -e сразу переносит в конец журнала внутри пейджера, подразумевая последнюю тысячу строк текущей загрузки. Для беглого взгляда на состояние службы есть и более короткий путь:

systemctl status myapp

Команда сама выводит последние десять записей журнала юнита, это первая помощь при падении.

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

journalctl -f -p warning
journalctl -f -g "out of memory"

Поиск по номерам загрузок и по календарю с точностью до минуты

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

journalctl --list-boots
journalctl -b -p err
journalctl -b -1 -u myapp
journalctl -b -2 -e

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

IDX BOOT ID                          FIRST ENTRY                 LAST ENTRY
-1 4c2e0a3b9f1d4e6c8a7b2d1f0e9c8a4b Sun 2026-09-20 09:59:40 UTC Sat 2026-10-01 03:41:12 UTC
 0 887ed9835ded4616823012d02541fc75 Sun 2026-10-01 03:41:18 UTC Fri 2026-10-09 06:00:03 UTC

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

journalctl -b -1 -r -n 100

Календарные границы задаются парой --since и --until, для набора есть короткие формы -S и -U. Формат времени на удивление человечный: полная дата с секундами, слова yesterday и today, сдвиг от текущего момента:

journalctl -S "2026-09-01 09:00" -U "2026-09-01 09:15" -u myapp
journalctl --since yesterday --until today -p warning
journalctl -S -1h -u myapp
journalctl --utc -S yesterday

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

Фильтры по важности сообщений и поиск по регулярному выражению

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

journalctl -p err
journalctl -p 3
journalctl -p err..alert
journalctl -b -p warning -u myapp

Поиск по содержимому делает ключ -g: он принимает регулярное выражение PCRE2 и проходит по полю сообщения, в остальных полях не ищет. Правило регистра изящное: шаблон из одних строчных букв ищет без учёта регистра, стоит добавить заглавную, и поиск станет точным, а ключ --case-sensitive управляет поведением явно. Практическое следствие: поиск слова "failed" поймает и "Failed", а поиск с заглавной оставит только точное написание. Приятная мелочь: с ограничением строк выдача автоматически переворачивается, новейшее идёт первым:

journalctl -g "connection refused"
journalctl -g "Failed to start"
journalctl -g "timeout|too many open files"
journalctl -g 'disk full' -u myapp

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

journalctl -k -p warning
journalctl -t myapp
journalctl _COMM=sshd -b
journalctl -u myapp _PID=4242

Имена полей и их значения подсматриваются двумя служебными командами: -N выводит список всех полей, -F показывает, какие значения принимало конкретное поле. Так находят имя юнита, под которым светится незнакомая служба, и все идентификаторы, писавшие в журнал за неделю:

journalctl -N
journalctl -F _SYSTEMD_UNIT

Форматы вывода от короткого до JSON для скриптов и разбора аварий

Дефолтный формат short выглядит как классический syslog и привычен глазу. Для разбора удобнее short-iso с датой по ISO 8601, для человека до предела честен cat без служебных колонок, для автоматизации готов JSON:

journalctl -u myapp -o short-iso -n 5
journalctl -u myapp -o cat -n 10
journalctl -n 2 -o verbose
journalctl -b -o json-pretty -n 1

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

journalctl -b -o export > /root/incident.journal
journalctl -D /mnt/backup/var-log-journal -b

Обратный порядок включает -r: новейшие строки идут первыми, листать до интересного места не приходится. Ключ -x добавляет к строкам выдержки из каталога сообщений, где типовые ошибки системы снабжены внятными пояснениями и подсказками к действию. Разница хорошо видна на одной строке: short-iso даёт дату по стандарту ISO 8601, а cat оставляет голое сообщение:

2026-09-01T09:00:02+00:00 srv01 sshd[4242]: Accepted publickey for deploy

Для скриптов добавляется --no-pager, а --output-fields= сужает набор полей в JSON-режиме:

journalctl -x -u myapp -p err
journalctl -r -n 20 -o short-iso
journalctl --no-pager -b -o json --output-fields=MESSAGE,PRIORITY,_SYSTEMD_UNIT

Уборка журнала и лимиты хранения, чтобы диск не кончался внезапно

Первый вопрос при уборке: сколько журнал занимает прямо сейчас.

journalctl --disk-usage

Дальше три рубильника. --vacuum-size= подрезает архивы, пока их суммарный объём не уложится в заданный, суффиксы K, M, G, T считаются по 1024. --vacuum-time= удаляет архивы старше указанного срока, вроде 2week. --vacuum-files= ограничивает количество файлов. Секрет эффективности в ключе --rotate: он закрывает активные файлы и превращает их в архивные, а в одной команде с уборкой позволяет подрезать вообще всё, что накопилось:

journalctl --vacuum-size=500M
journalctl --vacuum-time=14d
journalctl --rotate --vacuum-size=1G
journalctl --verify

В ответ утилита отчитывается короткой строкой, из которой сразу видно, сколько места освобождено и из какого каталога:

Vacuuming done, freed 3.1G of archived journals from /var/log/journal.

Уборка вручную хороша разово, постоянный порядок задаётся в /etc/systemd/journald.conf. Парная группа настроек с префиксом Runtime ограничивает копию журнала в памяти, если запись на диск не ведётся. SystemMaxUse= ограничивает суммарный объём журнала на диске, SystemKeepFree= требует оставлять свободное место для остальных нужд, MaxRetentionSec= подрезает архивы по возрасту. По умолчанию журнал занимает не больше 10 процентов файловой системы и обязан оставлять свободными ещё 15, оба порога не поднимаются выше 4 гигабайт:

[Journal]
Storage=persistent
Compress=yes
SystemMaxUse=2G
SystemKeepFree=1G
MaxRetentionSec=2month
systemctl restart systemd-journald.service

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

Порядок разбора аварии на сервере за пять минут по журналу

Мини-кейс для наглядности. В 3:41 приложение myapp перестало отвечать, в 3:44 сервер перезагрузили вручную. Утром разбор идёт по цепочке: таблица загрузок уточняет границы, вырезка с 3:30 до 3:44 по службе показывает каскад таймаутов, поиск по тексту ошибки находит первый симптом в 3:39, а подробный формат последней записи подсказывает, что у конкретного воркера кончилась память. Пять минут, и у аварии есть имя, время и причина.

При разборе ночной аварии выстроить мысли помогает короткая последовательность шагов:

  1. оценить масштаб одним взглядом: ошибки текущей загрузки с приоритетом не выше err;
  2. если сервер перезагружался, найти нужную загрузку в таблице загрузок и просмотреть её с конца;
  3. вырезать окно времени от первых симптомов до падения и наложить фильтр по службе;
  4. собрать текст ошибки в регулярное выражение и пройти с ним историю всей загрузки;
  5. развернуть самую подозрительную запись в подробном формате и вытащить поля о происхождении;
  6. зафиксировать причину и принятые лимиты в документации сервера, чтобы следующая авария разбиралась быстрее.

Журнал systemd силён тем, что собирает всё в одном месте: сообщения ядра, события загрузок, записи служб и пользовательских программ уже проиндексированы по времени, юнитам и приоритету. Два десятка освоенных ключей journalctl возвращают администратору уверенность, что ни одна строка не потерялась между перезапусками, и скорость, с которой ночная авария превращается из тайны в короткий отчёт с временем, виновником и последствиями.