Przejdź do treści

Logowanie do journald z poziomu Bash'a

Tworząc narzędzia systemowe, daemony lub usługi działające na systemach opartych o systemd, warto logować zdarzenia bezpośrednio do journald. Dzięki temu logi trafiają do centralnego dziennika systemowego.

W tym artykule pokażę, jak logować do journald w Bashu - zarówno przy użyciu polecenia systemd-cat, jak i bezpośrednio przez logger oraz journalctl.


Wymagania

Wszystkie narzędzia są dostępne w standardowych dystrybucjach z systemd:

  • systemd-cat - przekierowanie stdout/stderr do journald
  • logger - standardowe narzędzie syslog/journald

Metoda 1: systemd-cat

Parametry:

  • -t IDENTIFIER - identyfikator źródła loga (np. nazwa skryptu)
  • -p PRIORITY - poziom ważności (nazwa lub cyfra)
  • -f FACILITY - facility syslog (domyślnie user)

Przykład z różnymi poziomami:

#!/bin/bash

echo "App started" | systemd-cat -t my_app -p info

echo "Error occured!" | systemd-cat -t my_app -p err

echo "Low memory" | systemd-cat -t my_app -p warning

Metoda 2: logger

Standardowe narzędzie Unix/Linux do logowania przez syslog/journald:

#!/bin/bash

logger -t my_script "Script started"

# Z określonym poziomem
logger -t my_script -p user.err "Error occurred"
logger -t my_script -p user.warning "Warning: low memory"
logger -t my_script -p user.info "just info message"

Parametry logger:

  • -t TAG - identyfikator źródła logu
  • -p FACILITY.PRIORITY - facility i poziom (np. user.err, daemon.info)
  • -s - dodatkowo wyświetl na stderr

Poziomy PRIORITY:

Nazwa Cyfra Opis
emerg 0 Krytyczny - system nie działa
alert 1 Wymaga natychmiastowej reakcji
crit 2 Krytyczny błąd
err 3 Błąd
warning 4 Ostrzeżenie
notice 5 Istotna informacja
info 6 Informacyjny
debug 7 Debugowanie

Metoda 3: Funkcje pomocnicze 🔧

Dla większych skryptów warto stworzyć funkcje logujące:

#!/bin/bash

SCRIPT_NAME="my_service"

log_info() {
    logger -t "$SCRIPT_NAME" -p user.info "$1"
}

log_warning() {
    logger -t "$SCRIPT_NAME" -p user.warning "$1"
}

log_error() {
    logger -t "$SCRIPT_NAME" -p user.err "$1"
}

log_debug() {
    logger -t "$SCRIPT_NAME" -p user.debug "$1"
}

log_info "Script started"
log_warning "Low memory: $(free -h | awk 'NR==2{print $3}')"
log_error "Cannot connect to database"

Zaawansowane funkcje z dodatkową funkcjonalnością:

#!/bin/bash

SCRIPT_NAME="$(basename "$0")"
DEBUG_MODE=${DEBUG:-false}

log_with_level() {
    local level="$1"
    local message="$2"
    local timestamp=$(date '+%Y-%m-%d %H:%M:%S')

    logger -t "$SCRIPT_NAME" -p "user.$level" "$message"

    if [[ "$DEBUG_MODE" == "true" ]]; then
        echo "[$timestamp] [$level] $message"
    fi
}

log_info() { log_with_level "info" "$1"; }
log_warn() { log_with_level "warning" "$1"; }
log_error() { log_with_level "err" "$1"; }
log_debug() { 
    if [[ "$DEBUG_MODE" == "true" ]]; then
        log_with_level "debug" "$1"
    fi
}

log_info "App started"
log_warn "Low memory"
log_error "Connection error"
log_debug "Configured PATH: $PATH"

Uruchomienie z debugowaniem:

DEBUG=true bash my_script.sh

gdzie DEBUG to zmienna środowiskowa, która włącza dodatkowe logowanie na konsolę, a my_script.sh to nazwa skryptu. Wynikiem będzie wyświetlenie logów zarówno w konsoli, jak i w journald, np:

[2025-06-01 12:00:00] [info] App started
[2025-06-01 12:00:01] [warning] Low memory
[2025-06-01 12:00:02] [err] Connection error
[2025-06-01 12:00:03] [debug] Configured PATH: /usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin

Odczyt logów w journalctl 🔍

Sprawdzenie logów:

# Wszystkie logi dla danego identyfikatora
journalctl -t my_script

# Tylko błędy i krytyczne
journalctl -t my_script -p err

# Ostatnie 50 wpisów
journalctl -t my_script -n 50

# Śledzenie na żywo
journalctl -t my_script -f

# Pełne dane (z PRIORITY, PID, etc.)
journalctl -t my_script -o verbose

# Logi z ostatniej godziny
journalctl -t my_script --since "1 hour ago"

Metoda 4: Logowanie całego skryptu

Przekierowanie całego wyjścia skryptu do journald:

#!/bin/bash

exec > >(systemd-cat -t my_script -p info)
exec 2> >(systemd-cat -t my_script -p err)

echo "This is an info message"
echo "This is an error" >&2

Przykład z obsługą sygnałów:

#!/bin/bash

SCRIPT_NAME="my_daemon"

exec > >(systemd-cat -t "$SCRIPT_NAME" -p info)
exec 2> >(systemd-cat -t "$SCRIPT_NAME" -p err)

cleanup() {
    logger -t "$SCRIPT_NAME" -p user.info "Stopped due to signal"
    exit 0
}

trap cleanup SIGTERM SIGINT

echo "Daemon started (PID: $$)"

while true; do
    echo "Doing task..."
    sleep 3
    echo "Task done"
done

Rozwiązywanie problemów

1. Brak logów w journalctl?

Sprawdź, czy journald działa:

systemctl status systemd-journald

2. Logi nie pojawiają się natychmiast?

Dodaj --flush aby wymusić zapis:

logger -t test "Test message"
journalctl --flush
journalctl -t test

3. Debugowanie z verbose:

journalctl -t my_script -o verbose --no-pager

4. Sprawdzenie dostępnych facility:

# Standardowe facility
logger -t test -p mail.info "Test mail facility"
logger -t test -p daemon.warning "Test daemon facility"
logger -t test -p local0.err "Test local0 facility"

Podsumowanie ✅

  • systemd-cat - prosty sposób przekierowania stdout/stderr do journald
  • logger - elastyczne narzędzie do logowania z różnymi poziomami
  • funkcje pomocnicze - ułatwiają zarządzanie logowaniem w skryptach
  • journalctl - filtrowanie logów po identyfikatorze (-t) i poziomie (-p)
  • obsługa sygnałów - ważna dla daemonów i długo działających skryptów

Dla prostych skryptów używaj systemd-cat, a dla bardziej złożonych rozwiązań - logger z funkcjami pomocniczymi.