Advanced Bash logging to handle errors.

So we've all done a quick log from a bash script, which looks like this

LOGFILE="logs/$(date +%F)_backup.log"
# Append to the file
restic backup /etc/ >> $LOGFILE
find /root/scripts/ -type f -name "restic*.log" -mtime +14 -delete -print >> $LOGFILE

That's great for small scripts. But sometimes you find that its not logging errors, which are also helpful, and you need to add those:

LOGFILE="logs/$(date +%F)_backup.log"
# Append to the file
restic backup /etc/ 2>&1 >> $LOGFILE
find /root/scripts/ -type f -name "restic*.log" -mtime +14 -delete -print 2>&1 >> $LOGFILE

And different commands behave differently, so it all starts to become complicated. But there's an easier way. Two actually. The first is to use curly brackets to contain all the commands you want to log, and then pipe it through tee at the end. Commands after that block won't be logged.

LOGFILE="logs/$(date +%F)_backup.log"
{
echo "Starting Restic backup at $(date)"
restic backup /etc/
find /root/scripts/ -type f -name "restic*.log" -mtime +14 -delete -print
echo "Restic backup and maintenance completed"
} | tee -a "${LOGFILE}"

And then there's the slightly less readable command substitution method.

exec > >(tee -a "${LOGFILE}") 2>&1
echo "Starting Restic backup at $(date)"
$RESTIC backup /etc/
find /root/scripts/ -type f -name "restic*.log" -mtime +14 -delete -print
echo "Restic backup and maintenance completed"

Comparison

Both methods will log all output of all commands, including errors. I find the curly brackets way slightly easier to read. It also allows you to select which commands are logged, but putting them inside the brackets or not. On the other hand, the exec method will log everything until the end of the script, which may or may not be desirable. Curly braces also seems to be more widely compatible.

There may be other considerations, different performance considerations and buffering for heavy duty scripts, but that's beyond my regular use cases.

Leave a Comment