“Где мои логи — в /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, и задачи отладки продакшена, и при этом экономит вам диск и нервы.