Statuszusammenfassung in Mails
Häufig führt man Wartungsscripte aus und lässt sich den Status z.B. via Mail zustellen. Oft schleicht es sich dann irgendwann ein, dass man diese zwar zur Kenntnis nimmt, aber nicht mehr wirklich intensiv beachtet. Je nachdem, wie der Inhalt gestaltet ist, können hierbei Fehlermeldungen untergehen.
Daher mag ich es, wenn neben dem vollständigen Output der Aktionen eine Statuszusammenfassung enthalten ist, bei der man auf einen Blick erkennt, ob alle Teilschritte funktioniert haben.
Bestenfalls sollte diese Zusammenfassung am Anfang der Mail stehen, damit man ihn sieht, ohne langwierig in der Mail ans Ende scrollen zu müssen.
Beispiel:
Remove stale locks [ OK ]
Create backup [FAILED]
Prune backup generations [ OK ]
Wichtig ist mir hierbei, dass nicht jeder einzelne Befehl einen Status ausgibt, sondern ggf. eine Gruppe von Befehlen. So bleibt es Übersichtlich.
Script
Bei Redhat ist bzw. war eine Funktionssammlung in /etc/init.d/functions
enthalten, die hierfür sehr hilfreich war. Die Funktionen sind folgende:
BOOTUP="color"
LOGFILE="/tmp/backup.log"
RES_COL=70
MOVE_TO_COL="echo -en \\033[${RES_COL}G"
SETCOLOR_SUCCESS="echo -en \\033[1;32m"
SETCOLOR_FAILURE="echo -en \\033[1;31m"
SETCOLOR_NORMAL="echo -en \\033[0;39m"
echo_success() {
[ "$BOOTUP" = "color" ] && $MOVE_TO_COL
echo -n "["
[ "$BOOTUP" = "color" ] && $SETCOLOR_SUCCESS
echo -n $" OK "
[ "$BOOTUP" = "color" ] && $SETCOLOR_NORMAL
echo -n "]"
echo -ne "\r"
return 0
}
echo_failure() {
[ "$BOOTUP" = "color" ] && $MOVE_TO_COL
echo -n "["
[ "$BOOTUP" = "color" ] && $SETCOLOR_FAILURE
echo -n $"FAILED"
[ "$BOOTUP" = "color" ] && $SETCOLOR_NORMAL
echo -n "]"
echo -ne "\r"
return 1
}
step() {
echo -n "$@" | awk '{printf "%-'${RES_COL}'s", $0}'
echo "" >> ${LOGFILE}
echo "------------------------------------------------------------------------------" >> ${LOGFILE}
echo "$(date)" >> ${LOGFILE}
echo "$*" >> ${LOGFILE}
STEP_OK=0
[[ -w /tmp ]] && echo $STEP_OK > /tmp/step.$$
}
try() {
"$@" &>> ${LOGFILE}
local EXIT_CODE=$?
if [[ $EXIT_CODE -ne 0 ]]; then
STEP_OK=$EXIT_CODE
[[ -w /tmp ]] && echo $STEP_OK > /tmp/step.$$
if [[ -n $LOG_STEPS ]]; then
local FILE=$(readlink -m "${BASH_SOURCE[1]}")
local LINE=${BASH_LINENO[0]}
echo "$FILE: line $LINE: Command \`$*' failed with exit code $EXIT_CODE." >> ${LOGFILE}
fi
fi
return $EXIT_CODE
}
next() {
[[ -f /tmp/step.$$ ]] && { STEP_OK=$(< /tmp/step.$$); rm -f /tmp/step.$$; }
[[ $STEP_OK -eq 0 ]] && echo_success || echo_failure
echo
return $STEP_OK
}
Wichtig sind hier insbesondere die Funktionen step
, try
und next
. Die Funktion step
gruppiert ggf. mehrere Befehlsaufrufe in einen Status. try
führt Befehlsaufrufe aus und speichert deren Rückgabewert. next
wertet die Rückgabewerte einer step
Gruppe aus.
Befehle werden nun im Wartungsscript wie folgt aufgerufen:
step "Removing stale locks"
try /usr/bin/restic unlock
next
step "Create Backup"
try /usr/bin/restic backup /opt /srv /etc
try echo "The next command will fail to make this example work better:"
try /opt/foobar
next
step "Prune Backups"
try /usr/bin/restic forget --keep-daily 30 --prune
next
step "Backup Info"
try /usr/bin/restic stats
next
Zunächst wird der step
definiert, dann die zugehörigen Befehle mittels try
ausgeführt. Die Auswertung erfolgt mittels next
.
Während der Befehlsausführung wird ein Logfile mit dem jeweiligen Output erzeugt, dieses kann am Ende des Script ausgegeben werden:
echo ""
echo "-------------------------------------------------------------------------------"
echo "Logfile:"
cat ${LOGFILE}
rm -f ${LOGFILE}
echo "-------------------------------------------------------------------------------"
Cronjob mit Mail
Um den Output (inkl. Zusammenfassung) per Mail zu versenden, tragen wir einfach folgendes in unseren Cronjob ein:
0 1 * * * /root/backup.sh 2>&1 | mail -s "Backup status" root@example.com
Output
Der vollständige Mailoutput könnte dann wie folgt aussehen:
Starting Backup Sun Feb 26 05:37:36 PM CET 2023
Remove stale locks [ OK ]
Create backup [FAILED]
Prune backup generations [ OK ]
-------------------------------------------------------------------------------
Logfile:
Remove stale locks
successfully removed locks
Create backup
Files: 406 new, 256 changed, 3239830 unmodified
Dirs: 11 new, 192 changed, 500609 unmodified
Added to the repo: 1.788 GiB
processed 3240492 files, 868.106 GiB in 58:28
snapshot 1ea026a5 saved
The next command will fail to make this example work better:
./backup.sh: line 64: /opt/foobar: command not found
Prune backup generations
Applying Policy: keep 30 daily snapshots
remove 1 snapshots:
[0:00] 100.00% 1 / 1 files deleted
done
Backup Info
scanning...
Stats in restore-size mode:
Snapshots processed: 30
Total File Count: 98691102
Total Size: 860.688 GiB
-------------------------------------------------------------------------------
ANSI in Mails
Verschickt man den Output per Mail, werden ANSI Steuercodes für die Positionierung und Farbe des Status ggf. nicht korrekt angezeigt. In diesem Fall setzt man BOOTUP
auf einen anderen Wert als color
.
Alternative (trap)
Eine Alternative (oder auch Ergänzung) zu obigen Verfahren kann die Nutzung von trap
sein. Mittels Trap kann man in einem Script definieren, was in einem Fehlerfall oder bei einem Abbruch des Scripts passieren soll. Das ist insbesondere dann praktisch, wenn in diesen Fällen gewisse Aufräumarbeiten nötig sind:
trap 'echo "ERROR: Backup interrupted"; do_cleanup; exit 2' INT TERM
trap 'echo "There was an error"; do_cleanup; exit 1' SIGINT SIGQUIT SIGTERM
Die erste Zeile greift wenn z.B. während der Laufzeit des Scripts Strg+C
gedrückt wird. Die zweite Zeile wird ausgeführt, wenn das Script (oder ein vom Script ausgeführter Befehl) einen fatalen Fehler (der zum Abbruch des Scriptes führt) generiert.
In beiden Fällen wird die Funktion do_cleanup
aufgerufen, diese könnte z.B. wie folgt aussehen:
do_cleanup () {
echo "Unmounting and removing snapshots"
/bin/umount /mnt/backup/data
/sbin/lvremove /dev/vg0/data-snapshot -f
cat ${LOGFILE} | mail -s "There was an error" root@example.com
}
Durch die exit
Aufrufe würde das Script im Fehlerfall beendet, das führt natürlich die Nutzung von try
und next
ad-absurdum. Man könnte aber natürlich auch auf den Aufruf von exit
verzichten und auch ggf. eine andere Funktion aufrufen. Auch die Übergabe von Parametern an die Funktion sind möglich, um zielgerichtet auf bestimmte Fehler in bestimmten Steps reagieren zu können.
Ich lasse mir z.B. einfach gern eine Fehlermail durch trap
senden:
trap 'cat ${LOGFILE} | mail -s "There was an error during backup" root@example.com' ERR
Die Mail wird bei jedem Fehler, der während des Scriptlaufs erzeugt wird, versendet. Wichtig hierbei, nutzt man die o.g. Funktionen, werden hierbei ggf. pro Fehler mehrere Mails generiert. Ich nutze hier daher dann lieber die Möglichkeiten meines Mailservers mir Mails die [FAILURE]
enthalten, entsprechend hervorzuheben.