Jak naprawdę działają logi w Linuksie: journald, rsyslog i logrotate

Logi w Linuksie wyglądają na pierwszy rzut oka banalnie. Jest /var/log/messages, /var/log/maillog, czasem journalctl, gdzieś obok rsyslog, a raz dziennie lub raz w tygodniu logrotate coś tam sobie obraca.

Dopóki wszystko działa.

Kiedy jednak rsyslog zaczyna wpadać w:

rsyslog.service: Start request repeated too quickly.
rsyslog.service: Failed with result 'start-limit-hit'.

warto przestać traktować logi jako zestaw magicznych konfiguracji i zobaczyć, co naprawdę dzieje się pod spodem.

Cały mechanizm w dużym uproszczeniu

Na współczesnym systemie z systemd przepływ logów często wygląda mniej więcej tak:

aplikacja / kernel / usługa
            |
            v
      systemd-journald
            |
            v
          rsyslog
            |
            v
       /var/log/...
            |
            v
         logrotate

To model uproszczony. W zależności od konfiguracji rsyslog może czytać journal przez imjournal albo odbierać klasyczne komunikaty syslog przez gniazdo obsługiwane przez imuxsock.

Każdy z tych elementów robi coś innego.

systemd-journald zbiera komunikaty z kernela, usług systemd, stdout i stderr procesów oraz innych źródeł.

rsyslog może odbierać te komunikaty, filtrować je i zapisywać do klasycznych plików:

/var/log/messages
/var/log/secure
/var/log/maillog
/var/log/cron

logrotate natomiast nie generuje logów i nie zajmuje się ich przesyłaniem.

Jego zadanie jest dużo prostsze:

nie dopuścić do tego, żeby pliki logów rosły bez końca.

Co właściwie robi rsyslog?

Załóżmy, że rsyslog zapisuje dane do:

/var/log/messages

Proces otwiera ten plik i system operacyjny przydziela mu deskryptor pliku.

W uproszczeniu:

rsyslog
   |
   | file descriptor
   v
inode pliku /var/log/messages

I tu pojawia się bardzo ważna rzecz.

Proces nie zapisuje każdej kolejnej linii w sposób:

znajdź plik o nazwie /var/log/messages i dopisz coś na końcu.

On ma już otwarty plik.

A dokładniej: deskryptor wskazujący na konkretny inode.

Możemy to zobaczyć na przykład przez:

lsof /var/log/messages

albo:

ls -li /var/log/messages

gdzie -i pokaże numer inode.

I wtedy przychodzi logrotate

Załóżmy, że mamy tygodniową rotację.

logrotate może zrobić:

/var/log/messages
        |
        v
/var/log/messages-20261004

a następnie utworzyć nowy:

/var/log/messages

Problem polega na tym, że rsyslog nadal ma otwarty deskryptor do starego pliku.

Zmiana nazwy pliku nie zmienia inode.

Czyli sytuacja wygląda mniej więcej tak:

rsyslog
   |
   v
inode 12345
   |
   v
/var/log/messages-20261004

A nowy:

/var/log/messages

może mieć już zupełnie inny inode.

Jeśli nic więcej nie zrobimy, rsyslog może nadal pisać do starego, obróconego pliku.

Dlatego istnieje postrotate

Typowa konfiguracja wygląda tak:

/var/log/cron
/var/log/maillog
/var/log/messages
/var/log/secure
/var/log/spooler
{
    missingok
    sharedscripts
    postrotate
        /usr/bin/systemctl reload rsyslog.service >/dev/null 2>&1 || true
    endscript
}

Po obróceniu logów wykonywany jest:

systemctl reload rsyslog.service

W przypadku rsysloga unit systemd może mieć:

ExecReload=/usr/bin/kill -HUP $MAINPID

Czyli reload oznacza w praktyce wysłanie procesu sygnału SIGHUP.

Proces nie jest zabijany.

Nie dostaje nowego PID-u.

Dostaje informację, że powinien zamknąć otwarte pliki wyjściowe. Gdy pojawią się kolejne komunikaty, rsyslog ponownie otworzy pliki wskazane w konfiguracji.

I właśnie tego potrzebowaliśmy.

Warto przy tym pamiętać, że we współczesnym rsyslogu SIGHUP nie oznacza pełnego ponownego wczytania konfiguracji. Służy przede wszystkim do zamknięcia otwartych plików i wykonania prac porządkowych.

Reload to nie restart

To drobna różnica w komendzie, ale duża różnica w działaniu.

Reload

systemctl reload rsyslog

Proces nadal żyje.

PID pozostaje ten sam.

W uproszczeniu:

PID 1258
   |
   | SIGHUP
   v
PID 1258

Restart

systemctl restart rsyslog

oznacza:

PID 1258
   |
   v
STOP

START
   |
   v
PID 48213

Cały proces jest zatrzymywany i uruchamiany ponownie.

Jeśli daemon potrafi ponownie otworzyć pliki po HUP, restartowanie go tylko dlatego, że obróciliśmy log, jest zwykle niepotrzebne.

Co daje sharedscripts?

Spójrzmy ponownie na:

/var/log/cron
/var/log/maillog
/var/log/messages
/var/log/secure
/var/log/spooler
{
    missingok
    sharedscripts

    postrotate
        systemctl reload rsyslog
    endscript
}

W jednym bloku mamy pięć plików.

Dzięki:

sharedscripts

skrypt postrotate wykonywany jest raz dla całego bloku, a nie osobno dla każdego obróconego pliku.

Czyli:

cron       \
maillog     \
messages ----> rotacja ----> jeden reload rsysloga
secure      /
spooler    /

To ważny szczegół.

sharedscripts działa jednak tylko w obrębie konkretnego bloku.

Jeśli stworzymy osiem osobnych plików w /etc/logrotate.d/ i w każdym umieścimy:

postrotate
    systemctl restart rsyslog
endscript

to każdy z nich wykona swój własny restart, o ile objęty nim log rzeczywiście został obrócony.

I wtedy zaczyna się robić ciekawie.

Jak zrobić mały DoS na własnym rsyslogu

Załóżmy, że mamy kilka konfiguracji:

/etc/logrotate.d/rsyslog
/etc/logrotate.d/maillog
/etc/logrotate.d/policyd
/etc/logrotate.d/app-json
/etc/logrotate.d/audit-json

Każda zawiera:

postrotate
    systemctl restart rsyslog
endscript

O północy logrotate zaczyna pracę:

restart rsyslog
restart rsyslog
restart rsyslog
restart rsyslog
restart rsyslog

Wszystko w ciągu jednej lub dwóch sekund.

Systemd ma zabezpieczenie przed usługami, które są uruchamiane zbyt często. Limit dotyczy wszystkich prób startu, w tym uruchomień wywołanych ręcznie.

Po kilku startach w krótkim czasie systemd może powiedzieć:

Start request repeated too quickly.
Failed with result 'start-limit-hit'.

I nagle rsyslog przestaje działać.

Nie dlatego, że konfiguracja rsysloga jest błędna.

Nie dlatego, że daemon się wysypał.

Tylko dlatego, że logrotate kilka razy pod rząd kazał go zatrzymać i ponownie uruchomić.

Każdy pojedynczy fragment konfiguracji wygląda poprawnie.

Problem pojawia się dopiero wtedy, kiedy spojrzymy na system jako całość.

Jak sprawdzić konfigurację rsysloga?

Najprościej:

rsyslogd -N1

Przykładowy poprawny wynik:

rsyslogd: version 8.2510.0
rsyslogd: End of config validation run. Bye.

To sprawdza składnię konfiguracji.

Jeżeli rsyslog nadal ma problem, warto zobaczyć:

systemctl status rsyslog

oraz:

journalctl -u rsyslog

Jeżeli widzimy:

Stopping System Logging Service...
Stopped System Logging Service.
Starting System Logging Service...

to nie wygląda jak crash.

Ktoś wyraźnie wydaje systemd polecenie zatrzymania i ponownego uruchomienia usługi.

Jak znaleźć restart rsysloga w logrotate?

Prosty grep:

grep -Rni 'restart rsyslog' /etc/logrotate.d

albo szerzej:

grep -RniE 'rsyslog|postrotate' \
    /etc/logrotate.conf \
    /etc/logrotate.d/

Jeśli zobaczymy kilka takich wpisów:

/etc/logrotate.d/policyd: systemctl restart rsyslog
/etc/logrotate.d/maillog: systemctl restart rsyslog
/etc/logrotate.d/app-json: systemctl restart rsyslog

warto zadać pytanie:

czy naprawdę każdy z tych plików musi restartować cały daemon?

Najczęściej odpowiedź brzmi: nie.

Jak zobaczyć, co zrobi logrotate?

Bardzo użyteczne jest:

logrotate -d -v /etc/logrotate.conf

Opcja:

-d

oznacza debug.

Logrotate pokazuje, co zrobiłby w normalnym przebiegu, ale niczego faktycznie nie rotuje i nie aktualizuje pliku stanu.

Możemy zobaczyć na przykład:

considering log /var/log/messages
Last rotated at 2026-10-06 00:00
log does not need rotating

albo:

not running postrotate script, since no logs were rotated

To bardzo wygodny sposób diagnozowania konfiguracji bez rozwalania logów podczas testów.

Globalne i lokalne ustawienia logrotate

Typowy /etc/logrotate.conf może wyglądać tak:

weekly
rotate 4
create
dateext
compress

include /etc/logrotate.d

Czyli domyślnie:

  • rotacja raz w tygodniu,
  • cztery stare kopie,
  • tworzenie nowego pliku,
  • data w nazwie,
  • kompresowanie starych logów.

Konfiguracje z /etc/logrotate.d/* dziedziczą wcześniejsze ustawienia globalne, chyba że je nadpiszą. Kolejność ma znaczenie: ustawienia globalne umieszczone po dyrektywie include nie wpływają wstecz na wcześniej wczytane pliki.

Dlatego plik:

/var/log/messages {
    missingok
}

może efektywnie oznaczać:

weekly
rotate 4
create
dateext
compress
missingok

Nie wszystko musi być zapisane w jednym miejscu.

Jak sprawdzić, czy ktoś modyfikował konfigurację z RPM-a?

Na systemach RPM bardzo przydatne jest:

rpm -qf /etc/logrotate.conf

które pokaże właściciela pliku.

Potem:

rpm -V logrotate

Jeżeli zobaczymy:

S.5....T.  c /etc/logrotate.conf

oznacza to między innymi:

S - zmienił się rozmiar
5 - zmieniła się suma kontrolna
T - zmienił się czas modyfikacji
c - plik konfiguracyjny

Czyli plik różni się od wersji dostarczonej przez RPM.

Do szybkiej administracyjnej archeologii jest to bardzo użyteczne.

A co z copytruncate?

Czasami daemon nie potrafi ponownie otworzyć pliku logu.

Wtedy możemy spotkać:

copytruncate

Mechanizm jest inny.

Zamiast:

rename starego logu
utworzenie nowego pliku
reload procesu

logrotate robi mniej więcej:

kopiuj log -> archiwum
wyzeruj istniejący plik

Proces nadal ma otwarty ten sam inode.

Nie musi niczego ponownie otwierać.

Ma to jednak wadę.

Między kopiowaniem a wyzerowaniem pliku istnieje krótkie okno, w którym mogą pojawić się nowe wpisy i część z nich może zostać utracona.

Dlatego jeśli aplikacja obsługuje reopen, reload, HUP albo własną komendę do rotacji, zwykle jest to lepsze rozwiązanie.

Najlepszy przypadek: aplikacja wie, jak obracać swoje logi

Niektóre programy mają własny mechanizm.

Przykład z chrony:

postrotate
    /usr/bin/chronyc cyclelogs
endscript

Tu nie restartujemy całej usługi.

Mówimy aplikacji dokładnie:

zamknij bieżące pliki logów i zacznij pisać do nowych.

Podobnie inne usługi mogą reagować na HUP, USR1 albo udostępniać własną komendę administracyjną.

Dla wielu usług sensowna kolejność preferencji wygląda następująco, choć ostateczny wybór zależy od sposobu działania konkretnej aplikacji:

1. własny mechanizm aplikacji

2. reload / HUP

3. copytruncate albo kontrolowany restart — zależnie od aplikacji

Pełny restart nie powinien być automatyczną reakcją na rotację pliku, ale w niektórych przypadkach może być bezpieczniejszy niż copytruncate i związane z nim ryzyko utraty wpisów.

A gdzie w tym wszystkim jest journald?

Na systemach z systemd logi bardzo często najpierw trafiają do:

systemd-journald

Możemy je zobaczyć przez:

journalctl

Rsyslog może następnie czytać journal przez moduł imjournal albo odbierać klasyczne komunikaty przez imuxsock.

Dlatego czasem w jego logach zobaczymy:

imjournal: journal files changed, reloading...

Nie oznacza to restartu rsysloga.

Moduł zauważył po prostu zmianę plików journala i odświeża swoje uchwyty.

Czyli warto rozróżniać:

journalctl

jako narzędzie do odczytu danych z journald

oraz:

/var/log/messages
/var/log/maillog

jako klasyczne pliki, do których może pisać rsyslog.

Oba światy mogą działać jednocześnie.

Co warto z tego zapamiętać?

Nie trzeba pamiętać wszystkich dyrektyw logrotate.

Warto natomiast rozumieć mechanizm:

proces
  |
  v
otwarty file descriptor
  |
  v
inode
  |
  v
plik logu

Potem:

logrotate
  |
  v
rename starego pliku
  |
  v
nowy plik
  |
  v
HUP/reload
  |
  v
proces otwiera nowy plik

Jeśli rozumiemy ten przepływ, dużo łatwiej odpowiedzieć na pytania:

  • po co istnieje postrotate,
  • dlaczego potrzebny jest reload,
  • dlaczego restart często jest przesadą,
  • po co jest sharedscripts,
  • czym różni się copytruncate,
  • dlaczego proces może pisać do pliku, którego nazwa została już zmieniona.

I przede wszystkim łatwiej zauważyć konfigurację, która pojedynczo wygląda poprawnie, ale jako całość nie ma sensu.

W Linuksie bardzo często problemem nie jest brak znajomości kolejnej komendy.

Problemem jest brak zrozumienia, co ta komenda naprawdę robi.