← retour aux snippets

tee: journaliser sans perdre le code retour

Afficher à l'écran et écrire en log tout en conservant le code retour de la commande d'origine.


objectif

Exécuter une commande, afficher sa sortie en direct, la stocker dans un fichier et retourner exactement son code retour (pas celui de tee), y compris avec stderr.

code minimal

# usage: run_logged LOGFILE -- commande args...
run_logged() {
  local log="$1"; shift
  set -o pipefail
  "$@" 2>&1 | tee -a "$log"
  local rc=${PIPESTATUS[0]}
  return "$rc"
}

utilisation

# 1) build de data.pm avec log daté et statut fidèle
LOG="/var/log/data.pm/build-$(date -u +%Y-%m-%d).log"
mkdir -p "$(dirname "$LOG")"
if run_logged "$LOG" -- npm run build; then
  echo "build ok"
else
  echo "build ko" >&2; exit 1
fi

# 2) capturer stdout+stderr vers fichier ET syslog en même temps
LOG="/var/log/data.pm/deploy-$(date -u +%Y-%m-%d).log"
run_logged "$LOG" -- bash -lc 'set -Eeuo pipefail; ./deploy.sh' \
  | tee >(:) >(logger -t data.pm -p user.info) >/dev/null
# (le code retour reste celui de ./deploy.sh via run_logged)

# 3) forcer l'affichage des couleurs quand l'outil détecte un TTY
# (de nombreux outils supportent --color=always ou variables d'env dédiées)
run_logged "$LOG" -- grep --color=always -RIn 'pattern' .

# 4) éviter le buffering des grosses sorties
run_logged "$LOG" -- stdbuf -oL -eL pytest -q

variante(s) utile(s)

# rediriger globalement un script: tout est tee'lé, codes retours préservés
LOG="/var/log/data.pm/task-$(date -u +%Y-%m-%d).log"
# ouvre deux pipes de process substitution, stdout et stderr séparés
exec > >(tee -a "$LOG") 2> >(tee -a "$LOG" >&2)
# ... vos commandes ici ...
# le code retour de chaque commande reste accessible via $?

# journaliser en JSONL minimal (timestamp + rc)
run_logged_json() {
  local log="$1"; shift
  local ts rc
  ts="$(date -u +%Y-%m-%dT%H:%M:%SZ)"
  set -o pipefail
  "$@" 2>&1 | tee -a "$log"
  rc=${PIPESTATUS[0]}
  printf '{"ts":"%s","cmd":%q,"rc":%d}\n' "$ts" "$*" "$rc" >> "$log"
  return "$rc"
}

# verrouiller l'écriture pour éviter des logs entremêlés (concurrence)
flock -n /tmp/data.pm.log.lock -c 'run_logged /var/log/data.pm/app.log -- ./job.sh'

notes

  • ${PIPESTATUS[0]} capture le code retour du premier élément du pipeline (la commande exécutée), indépendamment de tee.
  • set -o pipefail fait échouer le pipeline si une étape échoue; utile pour ne pas masquer les erreurs.
  • stdbuf n’est pas toujours disponible par défaut sur macOS; à défaut, préférez des options d’outils qui flushent en ligne ou script -q /dev/null -c "cmd".
  • protégez vos logs (droits 0640/0600) et utilisez des noms datés pour faciliter la rotation (logrotate ou équivalent).