Aller au contenu

Injecter scripts et sorties cron dans le journal systemd avec systemd-cat

Utiliser systemd-cat pour donner aux jobs cron, scripts de backup et commandes ponctuelles une présence structurée dans journalctl — avec le bon identifiant, la bonne priorité et des champs structurés.

Les jobs cron, scripts de backup et commandes shell ponctuelles loguent typiquement dans un fichier plat ou dans /dev/null, ni l’un ni l’autre n’étant découvrable quand un incident arrive. systemd-cat est le petit utilitaire qui prend stdin (ou exécute une commande) et écrit les lignes dans le journal systemd avec un identifiant et une priorité choisis. Le résultat : un script qui apparaît dans journalctl -t my-backup -p err à côté de tout le reste, avec des champs structurés qu’une requête peut filtrer. Cet article est le guide pratique pour bien l’utiliser.

Comment vérifier

Après avoir câblé systemd-cat dans un script, le journal doit tenir la sortie avec l’identifiant et la priorité fixés.

echo "test from $(whoami) at $(date)" | systemd-cat -t test-cat -p info
journalctl -t test-cat --since "1 min ago" -o json-pretty | head -30
# Cherchez SYSLOG_IDENTIFIER et PRIORITY dans la sortie

# Streamer plusieurs lignes d'une commande longue
( echo "starting"; sleep 1; echo "midway"; sleep 1; echo "done" ) | \
  systemd-cat -t demo-stream -p notice
journalctl -t demo-stream --since "1 min ago"

Erreur fréquente de vérification : piper dans systemd-cat fonctionne quand c’est l’opérateur qui lance, puis échoue silencieusement depuis cron parce que l’environnement de cron n’a pas XDG_RUNTIME_DIR et systemd-cat ne peut pas atteindre le journal utilisateur — voir étape 3.

Ce qui se passe vraiment

systemd-cat est un mince wrapper autour de sd_journal_send (l’API C libsystemd pour les écritures structurées dans le journal). Il s’utilise dans deux modes :

  • Mode pipecommande | systemd-cat -t TAG -p PRIORITÉ. Lit stdin ligne par ligne, écrit chaque ligne comme une entrée de journal avec le tag donné (= SYSLOG_IDENTIFIER) et la priorité.
  • Mode execsystemd-cat -t TAG -p PRIORITÉ -- commande args. Exécute la commande elle-même, capture stdout et stderr et écrit les deux dans le journal. Stderr est écrit à la priorité fixée ; stdout utilise --stderr-priority si spécifié séparément. Le code de sortie de la commande est préservé.

Les priorités suivent syslog : emerg, alert, crit, err, warning, notice, info, debug. Un wrapper à notice avec stderr-priority err est un pattern courant — les lignes informationnelles restent sous le seuil de bruit, les vraies erreurs remontent en -p err.

La procédure

  1. Utiliser le mode exec pour les jobs cron et les backups. C’est le pattern le plus propre — une ligne dans cron, toute la sortie dans le journal.

    # /etc/cron.d/nightly-backup
    0 2 * * * root systemd-cat -t nightly-backup -p notice --stderr-priority=err \
      /usr/local/bin/backup-postgres

    Dans le journal : chaque ligne du script de backup apparaît taguée nightly-backup, avec les lignes informationnelles en notice et tout stderr en err. journalctl -t nightly-backup -p err --since today est alors la ligne de triage.

  2. Utiliser le mode pipe pour les scripts qu’on ne veut pas wrapper entièrement. Utile dans les pipelines shell et quand seule une partie de la sortie compte.

    #!/usr/bin/env bash
    set -euo pipefail
    exec 1> >(systemd-cat -t my-script -p info)
    exec 2> >(systemd-cat -t my-script -p err)
    
    echo "démarrage du travail"
    do_something_dangerous
    echo "terminé"

    Les deux lignes exec redirigent le stdout restant du script vers systemd-cat en info, stderr en err. Tout ce qui vient après tombe dans le journal automatiquement.

  3. Dans cron, s’assurer que le répertoire runtime de l’utilisateur est disponible. C’est le piège qui mord les gens une fois et qui n’est plus jamais oublié.

    • Pour le cron root, /run/systemd/journal/dev-log est toujours disponible — aucun correctif nécessaire.

    • Pour le cron utilisateur (crontab -e en utilisateur normal), si vous voyez Failed to connect to system bus, mettez XDG_RUNTIME_DIR dans le crontab :

      XDG_RUNTIME_DIR=/run/user/1000
      0 2 * * * systemd-cat -t my-job -p notice -- /home/me/bin/the-job
    • Ou, mieux, lancez ça comme un timer systemd utilisateur — l’environnement du timer est correct par construction. Voir systemd-timers-vs-cron.

  4. Ajouter des champs structurés pour ce qu’on voudra interroger plus tard. systemd-cat lui-même n’expose pas ça, mais la commande logger sous-jacente oui.

    logger --journald=- <<EOF
    MESSAGE=backup terminé
    SYSLOG_IDENTIFIER=nightly-backup
    PRIORITY=5
    BACKUP_JOB=postgres-main
    BACKUP_BYTES=8543219200
    BACKUP_DURATION_MS=49210
    EOF
    
    # Puis interrogeable comme :
    journalctl BACKUP_JOB=postgres-main --since "1 day ago" -o json-pretty

    PRIORITY=5 est notice. Les noms de champs personnalisés sont par convention en majuscules. Ce pattern est la façon de faire émettre par des scripts des données qui s’intègrent proprement avec le filtrage journalctl décrit dans journalctl-filtering-deep.

  5. Utiliser --identifier par job logique, pas par hôte. Des identifiants comme nightly-backup, cert-renewal, disk-rotation rendent journalctl -t <nom> instantanément utile. Un identifiant fourre-tout comme cron-jobs défait l’objectif.

  6. Combinez avec --service-type=notify-reload pour les scripts à longue durée. Si un script doit tourner pendant des heures, le wrapper dans une unité systemd transitoire (systemd-run --unit=my-job.service --collect) donne la sortie au journal, des limites de ressources et un systemctl status my-job qui montre l’état courant — mieux que systemd-cat pour tout ce qui dépasse un one-shot.

Notes opérationnelles

  • Le rate limiter du journal (RateLimitIntervalSec / RateLimitBurst dans /etc/systemd/journald.conf) jette les messages d’un script bruyant sans erreur ; ajustez la limite par service via un drop-in si un burst légitime est attendu.
  • systemd-cat -p accepte des priorités numériques (0–7) et nommées — les deux fonctionnent, mais utilisez les noms dans les scripts pour la lisibilité.
  • Le tag -t n’est pas la même chose qu’une unité systemd ; journalctl -u et journalctl -t filtrent sur des champs différents. La plupart des scripts veulent -t.
  • logger (la commande syslog BSD-style) écrit aussi dans le journal, mais ne donne pas de champs structurés sauf à utiliser le mode --journald.
  • Pour des taux d’écriture très élevés, préférez wrapper le script dans une unité systemd et utiliser le journal directement via stdout — le mode pipe de systemd-cat introduit un petit surcoût par ligne.

Pour interroger ce que ces scripts émettent, voir journalctl-filtering-deep. Pour l’alternative timer systemd à cron, voir systemd-timers-vs-cron.


Stack Harbor câble systemd-cat (et logger --journald structuré) dans chaque script opérationnel livré dans le cadre de l’offre opérations infogérées — exécutions de backup, renouvellements de certificats, rotations de logs tombent tous dans le journal avec la même discipline de tag et de priorité, pour qu’un même filtre de triage fonctionne dans chaque environnement client.