“Где мои логи — в /var/log/messages, /var/log/syslog или только в journalctl?” — этот вопрос рано или поздно задает себе каждый инженер, который вынужден переключаться между разными дистрибутивами: Ubuntu, CentOS, Alpine, корпоративные Unix системы.
Типичный сценарий: вы заходите на сервер, ищете /var/log/messages, а его или нет, или он есть, но journalctl показывает гораздо больше событий, чем файл.
Свою лепту вносит разнородный парк, где рядом с Ubuntu живут динозавры на AIX и Solaris, путаница приобретает глобальный характер.
Плюс эпоха «двоевластия»: systemd‑journald уже стал стандартом де‑факто, но rsyslog все еще присутствует во многих дистрибутивах по инерции или ради совместимости.
Эта статья для инженеров, которые хотят понимать, кто именно пишет логи в Linux, почему они дублируются, где теряются CPU и I/O, и как настроить логирование, чтобы диск не превращался в помойку.
Также пройдем путь от бинарных логов AIX до journald, а в конце разберемся, как практически использовать journalctl с популярными инфраструктурными службами PostgreSQL, Redis, Nginx, Kafka и др.
Откуда вообще взялись бинарные логи
Расхожее мнение, что бинарные логи journald — это странный эксперимент авторов systemd, не выдерживает столкновения с реальностью enterprise Unix. В серьезных Unix‑системах критическое логирование десятилетиями было бинарным и тесно связано с подсистемой управления сервисами.
IBM AIX: дедушка journald
В AIX критические события ядра и железа всегда логировались бинарно: за это отвечает демон errdaemon, который пишет в /var/adm/ras/errlog, а читать это можно только через утилиту errpt. Попытка сделать cat /var/adm/ras/errlog заканчивается бинарным мусором — и это норма, а не баг. Такой подход выбрали ради высокой скорости записи, минимального I/O, строгой структуры событий и надежности при сбоях.
Syslog в AIX (/etc/syslog.conf) тоже есть, но он вторичен: системно важные ошибки идут в errpt, а текстовый syslog часто обслуживает только пользовательские приложения. Характерный костыль: чтобы отправить события из errpt наружу, используется errnotify, который триггерит logger и уже тот пишет в syslog — ровно то, что современный systemd делает прозрачно и без костылей.
Oracle Solaris: SMF как прообраз systemd
Solaris пошел еще немного дальше и внедрил SMF (Service Management Facility), который не только управляет сервисами, но и хранит их состояние и логи. Концептуально это очень похоже на привычное управление логированием сервиса через journalctl (например, journalctl -u nginx), где у каждого юнита есть своя история событий.
Ротация логов делается встроенным logadm, а не внешним logrotate, то есть логирование — часть самой системы управления сервисами, а не набор скриптов вокруг нее.
Таким образом, бинарные логи пришли вовсе не с systemd, и не в systemd возникла идея связать систему управления сервисами с их журналированием. По сути Linux просто догнал то, что в enterprise‑Unix уже работало годами.
Как Linux пришел к journald
Эпоха текстового syslog
Классическая схема выглядела просто: приложения пишут текст в стандартный сокет /dev/log, демон syslogd (позже rsyslog) читает из этого сокета и раскладывает сообщения по файлам согласно /etc/rsyslog.conf.
Плюсы очевидны: все лежит в обычных текстовых файлах, которые легко читать и обрабатывать с помощью всеми любимых cat, grep, awk и легко отправлять по UDP.
Минусы тоже: линейный поиск по гигабайтам текста, потеря логов под нагрузкой, отсутствие строгой структуры и невозможность полноценно логировать раннюю стадию загрузки.
Linux dmesg: кольцевой буфер ядра
Можно сказать, что dmesg стал неким промежуточным звеном между хаотичным текстовым логированием и современными бинарными журналами.
dmesg читает кольцевой буфер ядра — специальную область RAM размером порядка 1 МБ (настраивается через CONFIG_LOG_BUF_SHIFT), куда ядро Linux пишет сообщения о своей работе в реальном времени: загрузка драйверов, подключение устройств, ошибки железа, OOM-kill, сетевые события.
Кольцевой буфер — это циклическая структура данных. При его заполнении новые сообщения перезаписывают самые старые. Это обеспечивает гарантированную производительность ядра, предотвращая блокировки системы из-за чрезмерного логирования. С 1991 года Linux использует этот подход, чтобы избежать записи гигабайтов логов на диск под нагрузкой.
Буфер создается на ранних этапах загрузки системы, что позволяет захватывать логи до запуска пользовательских служб.
Путь сообщения ядра выглядит следующим образом:
функция printk() → кольцевой буфер (/proc/kmsg) → klogd (исторически) → journald (сейчас)
Ранее служба klogd или syslogd читала буфер через /proc/kmsg и перенаправляла в текстовые логи. В более современных дистрибутивах с systemd journald интегрирует эти сообщения напрямую, и их можно просматривать с помощью journalctl -k.
Преимущества кольцевого буфера:
1. Скорость: нет дисковых операций, только RAM
2. Надежность: ядро не зависнет, даже если генерирует мегабайты логов в секунду
3. Структура: каждое сообщение несет приоритет (emerg, crit, err, warning)
4. Уже сложно сделать подлог, как в текстовых логах: даже root не сможет выборочно очистить или отредактировать события из буфера (но все еще можно очистить или перезаписать).
Несмотря на эволюцию логов (от syslog к journald), dmesg сохраняет роль “моста” между ядром и пользователем, особенно в сценариях, где нужна информация о ранней загрузке или аппаратных событиях.
Несколько примеров команд для работы с dmesg:
# последние сообщения dmesg | tail # фильтр по времени dmesg --since=5m # только критические ошибки dmesg -l err,crit # режим отслеживания dmesg -w
Революция systemd и journald
С появлением systemd в Linux пришел journald, который перехватывает логи на самом раннем этапе загрузки системы и пишет их в бинарном индексированном формате.
Журнал состоит из нескольких типов объектов:
• DATA — сами значения полей (например, текст сообщения)
• FIELD — имена полей (MESSAGE, PID, SYSTEMD_UNIT и др.)
• ENTRY — записи, которые ссылаются на набор полей
Физически данные хранятся в файлах журнала, которые отображаются в память через mmap, дописываются (append-only) и автоматически сжимаются (обычно LZ4). Отображение в адресное пространство процесса позволяет читать данные без большого количества системных вызовов read() и без лишнего копирования между ядром и пользовательским пространством.
Для ускорения поиска journald ведёт индексы по ключевым полям, таким как:
• SYSTEMDUNIT
• _PID
• _UID
• временные метки
За счёт этого фильтрация (journalctl -u nginx, --since, -p err) выполняется без линейного сканирования всего файла, в отличие от grep по текстовым логам.
Практический эффект: вы можете мгновенно отфильтровать ошибки, например, за прошлый вторник с 14:00 до 15:00 для конкретного сервиса, не убивая диск grep‑ом по гигабайтным файлам.
Пример запроса, который выдаст только ошибки нужного юнита за указанный период:
journalctl -u openvpn@server.service --since yesterday --priority err
Бинарный формат - это прежде всего оптимизация под скорость, объем и фильтрацию.
Кроме того в journald реализован механизм контроля целостности журнала: записи связываются в цепочку с использованием хешей. Это позволяет обнаружить факт изменения или удаления данных задним числом, что является большим плюсом для задач аудита.
Хотя пользователь с правами root по-прежнему может удалить журнал или отключить механизм проверки.
Чтобы избежать перегрузки системы неконтролируемым потоком логов journald ограничивает интенсивность их записи.
По умолчанию он принимает только определённое количество сообщений за интервал времени, а лишние просто отбрасывает.
Это защищает CPU и диск, но может скрыть часть событий при пиковых нагрузках.
Настройка задаётся в journald.conf:
RateLimitIntervalSec=30s RateLimitBurst=1000
Journald может хранить логи либо в памяти, либо на диске — это напрямую влияет на то, переживут ли они перезагрузку.
Настройка Storage в journald.conf имеет три варианта:
• volatile — логи только в RAM (/run/log/journal), после reboot исчезают
• persistent — логи пишутся на диск (/var/log/journal)
• auto — если каталог /var/log/journal существует, используется диск
Частая причина пропажи логов после перезагрузки — запуск journald в режиме volatile.
Почему тогда journald и rsyslog до сих пор сосуществуют? Все еще очень большое количество программного обеспечения, систем мониторинга и системных администраторов привыкло работать с текстовыми файлами, и systemd не мог просто взять и убрать дорогие сердцу /var/log/messages.
Архитектура: /dev/log и push/pull
/dev/log в Linux — это локальный сокет, используемый для передачи логов от приложений и системных служб демону журналирования (например, rsyslog или systemd-journald). Это по сути труба, через которую программы отправляют свои события, а демон собирает их и записывает в файлы, обычно находящиеся в /var/log.
В большинстве systemd-систем владельцем /dev/log является journald, если это не переопределено явно, или если journald не отключен, и все приложения, которые думают, что пишут в syslog, на самом деле пишут в journald.
Задача отправки в текстовые файлы решается двумя способами, основанными на разных подходах: push и pull, что также зачастую может вносить путаницу.
Модель Push
Push-подход (ForwardToSyslog + imuxsock) заключается в том, что journald принимает событие, пишет его в свой журнал и сразу пересылает копию в сокет, который слушает rsyslog с модулем imuxsock. Для этого в journald.conf включают строку ForwardToSyslog=yes, а в rsyslog загружают модуль imuxsock (load=imuxsock).
Плюсы: это быстро и мало нагружает CPU.
Минусы: в rsyslog попадает уже обработанный текст, в котором теряется часть системных метаданных.
Такая схема по умолчанию используется в дистрибутивах Debian/Ubuntu.
Модель Pull
Pull-подход (imjournal) заключается в том, что rsyslog ничего не слушает, а сам читает journald через модуль imjournal, используя его API.
Плюсы: в rsyslog попадают все структурированные поля journald, с помощью которых можно строить сложные фильтры.
Минусы: при высоком потоке логов imjournal может создать значительную нагрузку на CPU и начнет отставать от реального журнала.
Эта схема используется по умолчанию в дистрибутивах RHEL/CentOS/Fedora.
Определить используемую модель можно по процессам и конфигам:
ps aux | grep rsyslog grep imjournal /etc/rsyslog.conf /etc/rsyslog.d/* journalctl --disk-usage grep ^ForwardToSyslog /etc/systemd/journald.conf*
Эти команды дадут представление о текущей настройке логирования. Если вы не знаете, какой режим сейчас используется, значит вы не контролируете свою систему логирования.
Где на самом деле лежат логи
Чтобы не блуждать по системе в поисках нужного файла, полезно иметь карту местности по разным операционным системам.
OS | Основной инструмент | Текстовые логи | Бинарные логи | Особенности |
IBM AIX | errpt | /var/adm/messages | /var/adm/ras/errlog | syslog вторичен, главное в errpt |
Oracle Solaris | svcs, tail | /var/adm/messages | внутренние логи SMF | SMF управляет сервисами и их логами |
RHEL / CentOS 7–9 | journalctl | /var/log/messages | /var/log/journal/ | По умолчанию imjournal |
Ubuntu / Debian | journalctl | /var/log/syslog | /var/log/journal/ | По умолчанию imuxsock |
Alpine Linux | cat, grep | /var/log/messages | - | Чистый syslog (busybox/rsyslog), минимализм |
Amazon Linux 2023 | journalctl | - | /var/log/journal/ | rsyslog отсутствует по-умолчанию |
В современных Linux‑системах journald почти всегда является первой точкой приема логов, а все остальное - это адаптеры и совместимость. Отсюда вытекают и ошибки конфигурации, и типичные анти‑паттерны.
Грабли и best practices вокруг journald и rsyslog
Дублирование логов и лишний I/O
Частая картина: одно и то же сообщение оказывается и в бинарном журнале, и в нескольких текстовых файлах, а иногда еще и отправляется по сети.
В результате вы тратите дисковый IOPS и место не на полезные данные, а на копии одних и тех же строк. Если у вас нет жестких требований совместимости со старыми парсерами, rsyslog на современных системах часто можно просто отключать.
Конфликт ротаций
Journald сам регулирует объем своих логов через настройки SystemMaxUse/RuntimeMaxUse/SystemKeepFree/RuntimeKeepFree/, сжимает и удаляет старые записи.
Обычно значения по умолчанию для MaxUse - 10% от файловой системы, где находится /var/log/journal (с лимитом в 4GB), для KeepFree - 15%.
Одновременно logrotate управляет /var/log/syslog, /var/log/messages и прочими текстовыми файлами, созданными rsyslog.
Типичная проблема: администратор настроил journald на лимит в 4 GB, но забыл про logrotate, и внезапно все место на диске заполняется текстовыми дубликатами бинарных журналов.
Лог‑loop
Еще один опасный сценарий возникает в том случае, когда из‑за неверной настройки forwarding логи попадают в rsyslog, который пишет их в сокет, который в свою очередь снова забирает journald.
Проблема возникает при одновременном использовании ForwardToSyslog=yes в journald и модуля imjournal в rsyslog. Таким образом происходит лавинообразный рост числа логов, который очень быстро заполняет диск и грузит CPU.
Признаки проблемы:
# Экспоненциальный рост журналов journalctl --disk-usage # Повторяющиеся сообщения с увеличивающейся частотой journalctl -f | grep -E "imjournal|rsyslog" # Высокая нагрузка на systemd-journald и rsyslogd top -p $(pgrep -d',' 'rsyslog|systemd-journal') # Статистика rate limiting в rsyslog journalctl -u rsyslog | grep "rate-limit"
Best practices
Для Cloud Native сценариев (Kubernetes‑ноды, микросервисы) лучшая стратегия — journald‑only: отключить rsyslog (systemctl disable --now rsyslog), хранить логи в RAM (Storage=volatile) или сильно ограничить SystemMaxUse, а сбор и отправку на внешние системы отдать агентам вроде Alloy, Vector, Fluent Bit, умеющим читать формат journald.
Для Enterprise / Legacy (базы данных, монолиты) можно использовать гибридную схему: journald как первый уровень, rsyslog — как адаптер к старым пайплайнам, но с оптимизацией.
На RHEL‑подобных системах при высокой нагрузке имеет смысл переключиться с imjournal на imuxsock, приняв небольшую потерю метаданных ради снижения нагрузки на CPU.
Практика: journalctl
Теория — хорошо, но в реальности мы чаще всего разбираемся с логами конкретных сервисов, таких как PostgreSQL, Redis, Nginx, HAProxy, Kafka, Patroni.
В systemd-системах логирование встроено в жизненный цикл сервиса.
Поток выглядит следующим образом:
stdout/stderr приложения → systemd → journald → (опционально) rsyslog → файлы / сеть
По умолчанию systemd перехватывает: stdout/stderr и передаёт их в journald без участия syslog. Поведением можно управлять через параметры unit-файла сервиса:
StandardOutput=journal StandardError=journal SyslogIdentifier=myservice LogLevelMax=info
Это означает, что современное приложение вообще не обязано знать о syslog — достаточно писать в stdout/stderr.
Стоит отметить, что journald также предоставляет API (sd_journal_send), позволяющий приложениям писать структурированные логи напрямую, без stdout или syslog.
Это даёт возможность добавлять произвольные поля и избегать парсинга текста, однако на практике чаще используются стандартные механизмы — stdout/stderr или syslog.
Ниже приведен практический чек‑лист того, что требуется настроить в конфигурации приложения и unit‑файлах systemd, чтобы все логировалось в journald, а не в произвольные файлы.
Общий принцип интеграции логов в journald
Чтобы сервис нормально логировался в journalctl, достаточно соблюдать три условия:
- Приложение не работает в режиме daemon и не уходит в фон самостоятельно, а работает в foreground.
- Приложение пишет логи в stdout/stderr (или в /dev/log, если есть поддержка syslog API).
- В unit‑файле systemd либо оставляем поведение по умолчанию (stdout/stderr в journald), либо явно задаем StandardOutput=journal и StandardError=journal.
Дальше это уже вопрос конкретных настроек для каждого сервиса.
PostgreSQL
PostgreSQL умеет писать в syslog, поэтому его проще всего подружить с journald, который является владельцем /dev/log на systemd‑системах.
Ключевые настройки postgresql.conf:
log_destination = 'syslog' logging_collector = off syslog_facility = 'local0' syslog_ident = postgres log_line_prefix = '%q%u@%d ' log_min_duration_statement = 1000
Здесь мы отключаем собственный файловый логгер PostgreSQL (logging_collector), переходим на syslog как backend и убираем из префикса лишнее (timestamp/PID уже есть у journald).
В unit‑файле для PostgreSQL в таком случае важно указать StandardOutput и StandardError:
StandardOutput=journal StandardError=journal
Полезные команды:
journalctl -u postgresql -b journalctl -u postgresql --since 1 hour ago journalctl -u postgresql -p err journalctl -u postgresql -f
Patroni + PostgreSQL
Patroni управляет PostgreSQL, поэтому важно не плодить зоопарк логов.
Unit‑файл Patroni:
[Unit] Description=Patroni PostgreSQL Manager After=network-online.target Wants=network-online.target [Service] Type=simple User=patroni Group=patroni Environment=PATRONI_CONFIG_LOCATION=/etc/patroni/patroni.yml ExecStart=/usr/bin/patroni /etc/patroni/patroni.yml StandardOutput=journal StandardError=journal SyslogIdentifier=patroni TimeoutStopSec=30s ExecStop=/bin/kill -TERM $MAINPID Restart=on-failure RestartSec=10s [Install] WantedBy=multi-user.target
Ключевые параметры в patroni.yml для PostgreSQL:
postgresql: data_dir: /var/lib/postgresql/14/main parameters: log_destination: syslog logging_collector: off syslog_facility: local0 syslog_ident: postgres log_line_prefix: '%q%u@%d '
Patroni логирует в stderr (journald это забирает), PostgreSQL — в syslog, который уходит в journald через /dev/log.
Redis
По умолчанию Redis любит режим daemonize и писать в файл, что плохо сочетается с systemd.
Ключевые правки /etc/redis/redis.conf:
daemonize no logfile "" loglevel notice
Этим мы запрещаем Redis становиться демоном и отключаем логирование в файл: логи пойдут в stdout и будут перехвачены journald.
Пример unit‑файла Redis:
[Unit] Description=Redis In-Memory Data Store After=network-online.target Wants=network-online.target [Service] Type=simple User=redis Group=redis WorkingDirectory=/var/lib/redis ExecStart=/usr/bin/redis-server /etc/redis/redis.conf ExecStop=/usr/bin/redis-cli shutdown StandardOutput=journal StandardError=journal SyslogIdentifier=redis Restart=on-failure RestartSec=5s LimitNOFILE=65536 ProtectSystem=strict ProtectHome=yes NoNewPrivileges=yes [Install] WantedBy=multi-user.target
Команды:
journalctl -u redis -f journalctl -u redis --since today
Nginx
У Nginx два потока логов: error и access.
Для интеграции с journald нужно развести их по stderr и syslog.
Фрагмент nginx.conf:
http { error_log stderr; access_log syslog:server=unix:/dev/log combined; }
Здесь error‑логи уходят в stderr (journald их перехватит), а access‑логи — в качестве компромисса, в syslog‑endpoint /dev/log, которым владеет journald.
Unit‑файл Nginx:
[Unit] Description=NGINX HTTP Server After=network-online.target Wants=network-online.target [Service] Type=forking User=nginx Group=nginx ExecStartPre=/usr/sbin/nginx -t ExecStart=/usr/sbin/nginx ExecReload=/bin/kill -s HUP $MAINPID ExecStop=/bin/kill -s QUIT $MAINPID PIDFile=/var/run/nginx.pid StandardOutput=journal StandardError=journal Restart=on-failure RestartSec=5s ProtectSystem=strict ProtectHome=yes NoNewPrivileges=yes [Install] WantedBy=multi-user.target
Команды:
journalctl -u nginx journalctl -u nginx -p err journalctl -u nginx -f
HAProxy
HAProxy традиционно логируется через syslog, что удобно использовать вместе с journald.
Фрагмент /etc/haproxy/haproxy.cfg:
global log /dev/log local0 log /dev/log local1 notice frontend http_in log global # ... backend backend_servers log global # ...
Unit‑файл HAProxy:
[Unit] Description=HAProxy Load Balancer After=network-online.target Wants=network-online.target [Service] Type=forking User=haproxy Group=haproxy ExecStartPre=/usr/sbin/haproxy -f /etc/haproxy/haproxy.cfg -c -q ExecStart=/usr/sbin/haproxy -W -f /etc/haproxy/haproxy.cfg ExecReload=/bin/kill -s HUP $MAINPID ExecStop=/bin/kill -s TERM $MAINPID StandardOutput=journal StandardError=journal Restart=on-failure RestartSec=5s [Install] WantedBy=multi-user.target
Команды:
journalctl -u haproxy -f
Kafka
Kafka и сопутствующие компоненты используют Log4j и обычно уже имеют systemd‑юниты в пакетах дистрибутива или от вендора.
Типовой unit‑файл брокера:
[Unit] Description=Kafka Broker After=network-online.target zookeeper.service Wants=network-online.target zookeeper.service [Service] Type=simple User=kafka Group=kafka Environment=LOG_DIR=/var/log/kafka Environment=KAFKA_HEAP_OPTS=-Xms1G -Xmx1G Environment=KAFKA_LOG4J_OPTS=-Dlog4j2.configurationFile=/etc/kafka/log4j2.properties ExecStart=/usr/bin/kafka-server-start /etc/kafka/server.properties StandardOutput=journal StandardError=journal Restart=on-failure RestartSec=10s [Install] WantedBy=multi-user.target
Даже если Kafka продолжает писать ротационные файлы через Log4j, системные сообщения сервиса (crash‑лог, ошибки старта, environment) всегда будут в journald.
Команды:
journalctl -u kafka -f journalctl -u kafka --since 10 minutes ago
Мини‑чек‑лист для любого сервиса
Проверьте конфиг:
- нет daemonize yes/форкинга без необходимости;
- отключены собственные файловые логи, либо сведены к минимуму.
Убедитесь, что при запуске в foreground сервис выводит логи в консоль.
Проверьте unit‑файл:
- StandardOutput=journal
- StandardError=journal
Проверьте:
journalctl -u myservice -n 50 journalctl -u myservice -f
Если после этого логи все еще дублируются в файлы — ищите остатки rsyslog или включенные файловые логгеры в самом приложении.
Заключение
Исторически мы прошли полный круг: от бинарного errpt в AIX к текстовому syslog и обратно к бинарному journald, который стал первым уровнем логирования в большинстве современных дистрибутивов. Логи в файлах, таких как /var/log/messages — это уже скорее анахронизм ради совместимости, чем архитектурная необходимость. Будущее (и отчасти настоящее, например в Amazon Linux 2023) за «чистым» journald и агентами, которые умеют читать его формат напрямую.
Не бойтесь journalctl: при грамотной настройке он закрывает и задачи devops, и задачи отладки продакшена, и при этом экономит вам диск и нервы.
