Bash - Script pour ajouter des fonctionnalités de log à votre script
Ajoutez des fonctionnalités de log robustes à vos scripts Bash grâce à cette petite bibliothèque réutilisable. Fichiers de log automatiques, horodatages et trace complète des appels de fonctions pour déboguer facilement.
Traduction automatique
Cet article a plus d'un an, mais il a été relu le 30 juillet 2026 — son contenu est toujours exact.
Cet article fait partie de la série Écrire de meilleurs scripts Bash :
- Bash - ASCII art
- Bash - Charger des variables d'environnement depuis un fichier
- Bash - Script pour ajouter des fonctionnalités de log à votre script
- Bash - Afficher sur la console et dans un fichier de log en même temps
- Linux - Comparer deux versions du même script
- Linux - Trier les fonctions dans un script Bash
- Linux - Générer de la documentation à partir de scripts Bash
- Linux - Tirez parti du nombre de CPU dont vous disposez ; lancez des jobs concurrents
- Linux - Utiliser une barre de progression dans vos scripts
- Linux - Scripts Bash - Lancer des tests unitaires avec bats/bats

Cet article partage une bibliothèque Bash réutilisable, log.sh, qui ajoute le logging à n'importe quel script : vous la sourcez, vous appelez log::write "message" là où c'est utile, et les entrées horodatées sont écrites automatiquement dans un fichier de log. Chaque entrée contient une trace d'appel complète (quelle fonction, à quelle ligne, appelée depuis où) pour faciliter le débogage des scripts non interactifs ou lancés par cron.
Lisez aussi Bash - Afficher sur la console et dans un fichier de log en même temps
Quand vous écrivez des scripts Bash, et surtout quand vous prévoyez de les lancer dans un cron, vous devriez mettre en place un fichier de log. Chaque action déclenchée par votre script devrait être enregistrée quelque part afin de pouvoir lancer le script en mode non interactif et, en cas de besoin, consulter le dernier fichier de log.
Le logging devient indispensable dès que votre script exécute plusieurs jobs en parallèle : la sortie console de dix tâches concurrentes est illisible, un fichier de log horodaté ne l'est pas.
Voici un script que j'ai développé sous forme de bibliothèque, ce qui veut dire que vous pouvez facilement l'inclure dans votre code existant sans rien devoir changer.
Il suffit d'inclure le fichier dans votre script (c'est-à-dire ajouter une ligne source log.sh) puis de prévoir ici et là un log::write "Something to log". Facile, non ?
Pour illustrer le fonctionnement du log, nous allons créer deux fichiers. Votre script (ici, je vais l'appeler run.sh) et celui qui contient l'implémentation du log (log.sh).
Le résultat
En lançant le script, on voit nos différents echo, mais aussi le contenu du log généré.

Vous pouvez bien sûr choisir de ne pas afficher le log automatiquement : il suffit de commenter la ligne log::__displayLog.
Trace
Chaque instruction loguée sera accompagnée d'une trace, comme vous pouvez le voir. Par exemple, le bloc [Function __main line 43] signifie que l'appelant était la fonction __main et que c'était à la ligne 43.
[2024-03-25T12:29:08+0100] Start ./run.sh
[2024-03-25T12:29:08+0100] Running main with these parameters: --help [Function __main line 43]
[2024-03-25T12:29:08+0100] Doing some stuff in test2 [Function test2 line 22;Function test1 line 30;Function __main line 48]
[2024-03-25T12:29:08+0100] I'm doing something in the test5 function... [Function test5 line 4;Function test4 line 10;Function test3 line 16;Function test2 line 24;Function test1 line 30;Function __main line 48]
[2024-03-25T12:29:08+0100] Logfile /tmp/run.sh.uplzaefiWq.log ended for ./run.sh.
[2024-03-25T12:29:08+0100] Duration: 0 second(s)
Quand il y a plusieurs parents, vous obtenez la trace complète, par exemple [Function test5 line 4;Function test4 line 10;Function test3 line 16;Function test2 line 24;Function test1 line 30;Function __main line 48] : la fonction __main à la ligne 48 a appelé test1, qui a ensuite appelé test2, et ainsi de suite.
Chaque instruction du log contiendra donc la trace complète, ce qui simplifie le débogage.
Votre script
Si vous avez déjà un script, ouvrez-le. Sinon, créez-en simplement un nouveau et copiez/collez le code ci-dessous.
Nommez le fichier comme vous voulez (je vais l'appeler run.sh par exemple) et assurez-vous que le script est exécutable en lançant chmod +x run.sh dans votre console.
#!/usr/bin/env bashset -o nounset -o errexit
function test5() { log::write "I'm doing something in the test5 function..."
return 0}
function test4() { test5
return 0}
function test3() { test4
return 0}
function test2() { log::write "Doing some stuff in test2"
test3
return 0}
function test1() { test2
return 0}
function __init() { source ./log.sh return 0}
function __main() { __init
log::write "Running main with these parameters: $*"
echo "Welcome in this script" echo "Please do ..."
test1
return 0}
__main "$@"Ce script bash définit une chaîne de fonctions qui s'appellent les unes les autres de manière imbriquée, en commençant par test1 qui appelle test2, qui appelle test3, et ainsi de suite jusqu'à test5. La fonction __main initialise le script en chargeant un fichier de logging, affiche des messages de bienvenue, puis déclenche cette chaîne d'appels. Les options set -o nounset et errexit garantissent que le script s'arrête immédiatement si une variable non définie est utilisée ou si une commande échoue.
Le helper de log
Créez un fichier appelé log.sh, exactement dans le même dossier que votre script, et copiez/collez ce contenu : c'est lui qui produit la trace et le fichier de log montrés plus haut.
#!/usr/bin/env bash
# region - Intro block -----------------------------------------------## Working with log files## ## How to use this script?## 1. Include the script in your own Bash like this `source log.sh` (adjust path if needed).# 2. Here and there, where you want a log, just type `log::write "This is a log info"`## ## Dependencies## None## endregion - Intro block --------------------------------------------
# let script exit if an unset variable is used or if a command failsset -o nounsetset -o errexit
# region - private function log::__main## ## Description## This function will initialize important global variables# This function is called as soon as this file is "sourced" (like "source log.sh")# to make sure variables are initialized as soon as possible.## ## Examples## ```bash# log::__main# ```## ## Exit code## * 0 If successful.## endregionfunction log::__main() { # "trap ... EXIT" allow to define a function to call when the script exits, # so, here, call our __logDestruct function trap log::__logDestruct EXIT
# Example of a filename: /tmp/myScript.sh.1J6Sqb62.log # Set LOG_FILE="" if you don't want to write to a log file (but simpler to not source this file then) [[ ! -v LOG_FILE ]] && LOG_FILE="$(mktemp /tmp/"$(basename "$0")".XXXXXXXXXX.log)"
# Quick function to export the system date in the chosen format. log::__logNow() { date +"%Y-%m-%dT%H:%M:%S%z"; }
# Generate a trace dump like [test5,line 728;test4,line 731;test3,line 734;test2,line 737;test1,line 740;main,line 755] # i.e. the latest called function, then the one who called that function, ... until the very first function called # (the entry point function) log::__trace() { local traceInfo traceInfo=""
LEN=${#BASH_LINENO[@]} # INDEX=0 is this __trace function, INDEX=1 is the log::write function so start at 2. for (( INDEX=2; INDEX < LEN-1; INDEX++ )); do traceInfo="${traceInfo}Function ${FUNCNAME[(( INDEX ))]} line ${BASH_LINENO[ (( INDEX - 1)) ]};" done
traceInfo="$(echo "${traceInfo}" | sed 's/;$//g')"
echo "[${traceInfo}]"
return 0 }
# From now, writing to 3 will write to the log file [[ -z "${LOG_FILE}" ]] || exec 3>"${LOG_FILE}"
return 0}
# region - private function log::__logDestruct## ## Description## Before exiting the script, display or not the logfile content (and remove it then)## ## Examples## ```bash# log::__logDestruct# ```## ## Exit code## * 0 If successful.## endregionfunction log::__logDestruct() { # Only if the file isn't empty if [[ -s ${LOG_FILE} ]]; then logEntry="$(printf '[%s] Logfile %s ended for %s.' "$(log::__logNow)" "$LOG_FILE" "$0")" echo "${logEntry}" >&3
logEntry="$(printf '[%s] Duration: %d second(s)' "$(log::__logNow)" "$SECONDS")" echo "$logEntry" >&3
log::__displayLog fi
return 0}
# region - public function log::write()## ## Description## Write a sentence to a logfile## ## Examples## ```bash# log::write 'This is a log entry'# ```## ## Parameters## * @arg string The string to write to the logfile## ## Exit code## * 0 If successful.# * 1 An error has occurred.## endregionfunction log::write() { # When LOG_FILE was not defined, it means no log so just exit [[ -z ${LOG_FILE} ]] && return 0
local -r message="${1:-}" [[ -z ${message} ]] && return 0 # No message so nothing to write in the log
# Create the log file if not yet there [[ ! -f ${LOG_FILE} && ! "${LOG_FILE}" = "/dev/stdout" ]] && touch "${LOG_FILE}"
# If the file isn't writable, error and return if [[ ! -w ${LOG_FILE} ]]; then errorMessage="ERROR - The ${LOG_FILE} file isn't writable. Exiting..." printf "\033[1;${__RED:-31}m%s\033[0m\n" "ERROR - ${message} Exiting..." >&2 # Write on stderr fi
# If the file size is 0, echo a start info date/time; do it once if [[ ! -s ${LOG_FILE} ]]; then printf '[%s] %s\n' "$(date +"%Y-%m-%dT%H:%M:%S%z")" "Start $0" >&3 fi
printf '[%s] %s %s\n' "$(date +"%Y-%m-%dT%H:%M:%S%z")" "${message}" "$(log::__trace)" >&3
return 0}
# region - private function log::__displayLog## ## Description## Display the content of the logfile## ## Examples## ```bash# log::__displayLog# ```## ## Exit code## * 0 If successful.## endregionfunction log::__displayLog() { # If the file is empty, nothing to do [[ ! -s ${LOG_FILE} ]] && return 0
printf "%s\n" "--------------------- LOG FILE CONTENT (${LOG_FILE}) ---------------------"
# Show the log content but make sure to remove, if any, ANSI colors. # (@see https://stackoverflow.com/a/51141872/1065340) printf "\033[1;${__GRAY:-30}m%s\n" "" sed "s/\x1B\[[0-9;]\{1,\}[A-Za-z]//g" "${LOG_FILE}" printf "\033[0m\n\n"
echo ""
return 0}
# This script didn't contains executable code; only helpers.sourced=0(return 0 2> /dev/null) && sourced=1 || sourced=0if [ "${sourced}" -eq "0" ]; then printf "\033[1;31m%s\033[0m\n" "ERROR, the $0 script is meant to be sourced. Try 'source $0' and use public functions." >&2 # Write on stderr exit 1fi
log::__mainCe script fournit un système de journalisation pour Bash. Il crée un fichier log temporaire au démarrage, capture les messages d'erreur avec horodatage et trace d'appel, puis affiche le contenu du log à la fin de l'exécution. Le script doit être sourcé (inclus) dans un autre script Bash pour fonctionner, pas exécuté directement.
Cet article fait partie de la série Écrire de meilleurs scripts Bash :
- Bash - ASCII art
- Bash - Charger des variables d'environnement depuis un fichier
- Bash - Script pour ajouter des fonctionnalités de log à votre script
- Bash - Afficher sur la console et dans un fichier de log en même temps
- Linux - Comparer deux versions du même script
- Linux - Trier les fonctions dans un script Bash
- Linux - Générer de la documentation à partir de scripts Bash
- Linux - Tirez parti du nombre de CPU dont vous disposez ; lancez des jobs concurrents
- Linux - Utiliser une barre de progression dans vos scripts
- Linux - Scripts Bash - Lancer des tests unitaires avec bats/bats





