reproduzierbar und protokolliert mit set -x, trap ERR und LINENO
Ein Deploy-Fehler, der sich nicht reproduzieren lässt, ist kein gelöster Fehler – er ist aufgeschobenes Chaos. set -x, trap ERR, LINENO und strukturierte Logfiles machen jeden Fehler in Deploy-Skripten nachvollziehbar, reproduzierbar und als Grundlage für Post-Mortem-Analysen nutzbar.
Inhaltsverzeichnis
- 1. Das Problem mit Deploy-Fehlern ohne Protokoll
- 2. set -x: Jeden Befehl mit expandierten Werten sehen
- 3. trap ERR: Fehler sofort abfangen und protokollieren
- 4. LINENO und BASH_LINENO: Fehlerposition im Skript
- 5. Strukturierte Logfiles für Deploy-Skripte
- 6. Kontextinformationen im Fehlerfall sammeln
- 7. Fehler reproduzierbar machen: Umgebungs-Snapshots
- 8. Post-Mortem-Prozess für Deploy-Fehler
- 9. Debugging-Ansätze im direkten Vergleich
- 10. Zusammenfassung
- 11. FAQ
1. Das Problem mit Deploy-Fehlern ohne Protokoll
Ein fehlgeschlagener Deploy ohne aussagekräftiges Protokoll ist ein Alptraum für jedes Team. Die Fragen sind immer dieselben: Welcher Schritt hat versagt? Welche Variable hatte welchen Wert? War der Server überhaupt erreichbar? War der Fehler deterministisch oder lag eine Race Condition vor? Ohne systematische Fehleranalyse in Deploy-Skripten bleibt die Ursachenforschung Raterei – und die Lösung ist bestenfalls ein Fix für das Symptom, nicht für die Ursache.
Das eigentliche Problem ist nicht das Fehlen von Informationen per se, sondern das Fehlen von reproduzierbaren Informationen. Bash-Skripte produzieren standardmäßig wenig Ausgabe. Kein Zeitstempel, keine Zeilennummern, keine expandierten Variablenwerte. Was der Entwickler sieht, ist ein kryptisches Fehlermeldungsfragment – wenn überhaupt. Die Werkzeuge für eine systematische Fehleranalyse in Deploy-Skripten existieren in Bash: set -x, trap ERR, LINENO, BASH_LINENO und strukturierte Logfiles. Sie müssen nur konsequent eingesetzt werden.
Der Unterschied zwischen einem Team, das Deploys analytisch beherrscht, und einem, das bei jedem Ausfall neu rät, liegt genau hier: in der Qualität der Protokollierung und der Systematik der Fehleranalyse in Deploy-Skripten. Ein Deploy-Skript, das bei Fehler genau sagt, welcher Befehl auf welcher Zeile mit welchem Exit-Code gescheitert ist, welche Umgebungsvariablen aktiv waren und welche Ausgaben der fehlgeschlagene Befehl produziert hat, ist die Grundlage für eine Analyse in Minuten statt in Stunden.
2. set -x: Jeden Befehl mit expandierten Werten sehen
set -x ist das mächtigste Debugging-Werkzeug für die Fehleranalyse in Deploy-Skripten. Es aktiviert den Tracing-Modus: Jeder Befehl, der ausgeführt wird, wird vorher nach stderr ausgegeben – mit vollständig expandierten Variablen, aufgelösten Globs und Subshell-Ergebnissen. Das Präfix + (oder ++ für Befehle in Subshells) markiert jede Trace-Zeile. So wird sofort sichtbar, welche Variablen welche konkreten Werte hatten, und ob ein Pfad oder Argument wie erwartet expandiert wurde.
Das Muster für selektives Tracing: ${DEBUG:-0} am Skriptanfang prüfen und set -x nur bei DEBUG=1 aktivieren. So läuft das Skript in der Produktion ohne Trace-Ausgabe, kann aber durch Setzen der Umgebungsvariable jederzeit in den Debug-Modus versetzt werden, ohne den Code zu ändern. Für noch präziseres Tracing kann set -x und set +x innerhalb des Skripts um spezifische Abschnitte gesetzt werden – nur der verdächtige Codeblock wird dann getract. BASH_XTRACEFD lenkt die Trace-Ausgabe in eine eigene Datei, damit sie nicht mit normalem stdout/stderr vermischt wird.
#!/usr/bin/env bash
# deploy-with-tracing.sh — Deploy script with full debug capabilities
set -euo pipefail
IFS=$'\n\t'
readonly SCRIPT_NAME="$(basename "${BASH_SOURCE[0]}")"
readonly LOG_DIR="/var/log/deploy"
readonly TIMESTAMP="$(date +%Y%m%d-%H%M%S)"
readonly LOG_FILE="${LOG_DIR}/${SCRIPT_NAME%.sh}-${TIMESTAMP}.log"
readonly TRACE_FILE="${LOG_DIR}/${SCRIPT_NAME%.sh}-${TIMESTAMP}.trace"
mkdir -p "$LOG_DIR"
# Redirect stdout and stderr to log file while also showing on terminal
exec > >(tee -a "$LOG_FILE") 2>&1
# Enable tracing to separate file if DEBUG=1
if [[ "${DEBUG:-0}" == "1" ]]; then
exec {BASH_XTRACEFD}>>"$TRACE_FILE"
set -x
echo "[DEBUG] Trace output: $TRACE_FILE"
fi
log() {
local level="$1"; shift
echo "[$(date '+%Y-%m-%d %H:%M:%S')] [$level] [$SCRIPT_NAME:${BASH_LINENO[0]}] $*"
}
log INFO "Deploy started: PID=$$, USER=$USER, PWD=$PWD"
log INFO "Environment: DEPLOY_ENV=${DEPLOY_ENV:-unset}"
# Trace selectively around the critical section only
set +x # Ensure tracing is off here
log INFO "Starting database migration..."
# Temporarily enable tracing for this specific step
{ set -x; bin/magento setup:upgrade; set +x; } 2>>"$TRACE_FILE"
log INFO "Database migration completed"
3. trap ERR: Fehler sofort abfangen und protokollieren
trap ERR registriert eine Funktion, die bei jedem Befehl mit Nicht-Null-Exit-Code ausgeführt wird – bevor das Skript durch set -e beendet wird. Das ist der ideale Punkt für die Fehleranalyse in Deploy-Skripten: Der ERR-Handler kann den Exit-Code, die Zeilennummer, den Funktionsnamen und den Stack-Trace ausgeben, bevor der Prozess endet. So enthält das Logfile immer eine präzise Fehlermeldung, die direkt auf die fehlerhafte Stelle zeigt.
Der ERR-Handler bekommt über ${BASH_LINENO[0]} die Zeilennummer, über ${FUNCNAME[*]} den aktuellen Funktions-Call-Stack und über ${BASH_SOURCE[0]} die aktuelle Datei. Diese drei zusammen bilden einen vollständigen Stack-Trace – ähnlich wie in höheren Programmiersprachen. Kombiniert mit der Ausgabe der letzten Log-Zeilen ermöglicht das eine Fehleranalyse in Deploy-Skripten ohne interaktives Debugging: Das Logfile alleine reicht aus, um den Fehler zu lokalisieren und zu verstehen.
#!/usr/bin/env bash
# error-handler.sh — Comprehensive error handling for deploy scripts
set -euo pipefail
readonly LOG_FILE="/var/log/deploy/current.log"
readonly NOTIFY_EMAIL="${NOTIFY_EMAIL:-}"
# Full stack trace on any error
err_handler() {
local exit_code=$?
local line_number="${BASH_LINENO[0]}"
local command="${BASH_COMMAND}"
echo "" >&2
echo "════════════════════════════════════════════════════════════" >&2
echo "[ERROR] Command failed with exit code $exit_code" >&2
echo "[ERROR] Failed command: $command" >&2
echo "[ERROR] Line: $line_number in ${BASH_SOURCE[1]:-$0}" >&2
echo "" >&2
echo "Call stack (most recent first):" >&2
local i
for (( i=1; i<${#FUNCNAME[@]}; i++ )); do
local func="${FUNCNAME[$i]}"
local src="${BASH_SOURCE[$i]:-main}"
local lineno="${BASH_LINENO[$((i-1))]}"
echo " #$((i-1)) $func() at $src:$lineno" >&2
done
echo "" >&2
# Capture last 20 lines of log for context
echo "Last log entries:" >&2
tail -20 "$LOG_FILE" 2>/dev/null | sed 's/^/ /' >&2
echo "════════════════════════════════════════════════════════════" >&2
# Send alert if email is configured
if [[ -n "$NOTIFY_EMAIL" ]] && command -v mail &>/dev/null; then
{
echo "Deploy failed at line $line_number"
echo "Command: $command"
echo "Exit code: $exit_code"
echo ""
echo "Last 50 log lines:"
tail -50 "$LOG_FILE" 2>/dev/null
} | mail -s "[DEPLOY FAIL] $(hostname) - exit $exit_code" "$NOTIFY_EMAIL"
fi
}
trap err_handler ERR
# Cleanup on any exit
cleanup() {
local exit_code=$?
if [[ $exit_code -eq 0 ]]; then
echo "[OK] Deploy completed successfully"
else
echo "[FAIL] Deploy aborted with exit code $exit_code"
fi
}
trap cleanup EXIT
4. LINENO und BASH_LINENO: Fehlerposition im Skript
LINENO ist eine von Bash automatisch gesetzte Variable, die die aktuelle Zeilennummer im Skript enthält. Sie wird bei jedem Befehl aktualisiert und ist in Logging-Funktionen unverzichtbar für eine präzise Fehleranalyse in Deploy-Skripten. Das Muster: log() { echo "[${BASH_LINENO[0]}] $*"; } – der Caller übergibt LINENO implizit über das BASH_LINENO-Array, das alle Zeilennummern im Call-Stack enthält.
BASH_LINENO ist ein Array, bei dem ${BASH_LINENO[0]} die Zeilennummer des Aufrufers der aktuellen Funktion enthält. In einem ERR-Handler zeigt ${BASH_LINENO[0]} auf die Zeile, die den Fehler verursacht hat. ${BASH_LINENO[1]} zeigt auf die Zeile, von der die fehlschlagende Funktion aufgerufen wurde. Dieses Array ermöglicht den vollständigen Call-Stack-Trace, der für eine zuverlässige Fehleranalyse in Deploy-Skripten entscheidend ist: Man sieht nicht nur wo, sondern auch wie das Skript zu der fehlerhaften Stelle kam.
5. Strukturierte Logfiles für Deploy-Skripte
Strukturierte Logfiles sind die Grundlage jeder ernsthaften Fehleranalyse in Deploy-Skripten. Ein strukturiertes Logfile enthält für jede Zeile: Zeitstempel, Log-Level, Skriptname, Zeilennummer und die eigentliche Meldung. Diese fünf Felder ermöglichen es, Logs über mehrere Deployments hinweg zu vergleichen, Zeitpunkte zu korrelieren und Fehlermuster zu erkennen. Ein unstrukturiertes Logfile – eine Anhäufung von echo-Aufrufen ohne einheitliches Format – ist für die automatisierte Analyse wertlos.
Das effizienteste Muster: exec > >(tee -a "$LOG_FILE") 2>&1 am Skriptanfang leitet alle Ausgaben (stdout und stderr) gleichzeitig ins Terminal und in die Logdatei. Eine Logging-Funktion wrappet alle echo-Aufrufe und fügt Metadaten hinzu. Die Logdatei erhält einen Zeitstempel im Namen, so dass jeder Deploy-Lauf eine eigene Logdatei hat und Logs nie überschrieben werden. Log-Rotation durch einen separaten Cron-Job oder logrotate-Konfiguration verhindert, dass das Log-Verzeichnis unkontrolliert wächst.
#!/usr/bin/env bash
# logging-library.sh — Structured logging for deploy scripts
# Source this file: source /usr/local/lib/deploy/logging.sh
readonly LOG_LEVELS=([DEBUG]=0 [INFO]=1 [WARN]=2 [ERROR]=3 [FATAL]=4)
LOG_LEVEL="${LOG_LEVEL:-INFO}"
LOG_FILE="${LOG_FILE:-/var/log/deploy/deploy.log}"
LOG_FORMAT="${LOG_FORMAT:-text}" # text or json
_log_write() {
local level="$1"; shift
local message="$*"
local timestamp
timestamp="$(date -u '+%Y-%m-%dT%H:%M:%SZ')"
local caller_line="${BASH_LINENO[1]}"
local caller_func="${FUNCNAME[2]:-main}"
local caller_file="${BASH_SOURCE[2]:-unknown}"
# Filter by log level
if [[ "${LOG_LEVELS[$level]:-0}" -lt "${LOG_LEVELS[$LOG_LEVEL]:-1}" ]]; then
return 0
fi
if [[ "$LOG_FORMAT" == "json" ]]; then
printf '{"timestamp":"%s","level":"%s","file":"%s","line":%d,"func":"%s","msg":%s}\n' \
"$timestamp" "$level" "$(basename "$caller_file")" \
"$caller_line" "$caller_func" \
"$(printf '%s' "$message" | python3 -c 'import json,sys; print(json.dumps(sys.stdin.read()))')"
else
printf '[%s] [%-5s] [%s:%d] %s\n' \
"$timestamp" "$level" "$(basename "$caller_file")" "$caller_line" "$message"
fi | tee -a "$LOG_FILE"
}
log_debug() { _log_write DEBUG "$@"; }
log_info() { _log_write INFO "$@"; }
log_warn() { _log_write WARN "$@" >&2; }
log_error() { _log_write ERROR "$@" >&2; }
log_fatal() { _log_write FATAL "$@" >&2; exit 1; }
# Usage example:
# LOG_FORMAT=json LOG_LEVEL=DEBUG source logging.sh
# log_info "Starting deployment of version $VERSION"
# log_error "Failed to connect to database: $db_error"
6. Kontextinformationen im Fehlerfall sammeln
Ein Stack-Trace und eine Fehlermeldung sind für die Fehleranalyse in Deploy-Skripten notwendig, aber nicht hinreichend. Um einen Fehler wirklich zu verstehen, braucht man Kontext: Welche Umgebungsvariablen waren gesetzt? Welche Dienste waren erreichbar? Wie war der Speicher- und CPU-Zustand zum Zeitpunkt des Fehlers? Welche anderen Prozesse liefen parallel? Diese Informationen müssen automatisch im Fehlerfall gesammelt werden, damit sie für die Post-Mortem-Analyse verfügbar sind.
Ein Fehleranalyse in Deploy-Skripten-Kontext-Collector ist eine Funktion im ERR-Handler, die beim Fehler automatisch ausgeführt wird: env | sort für alle Umgebungsvariablen, df -h für Festplattenauslastung, free -h für Speicher, netstat -tn oder ss -tn für offene Verbindungen, ps aux für laufende Prozesse. All diese Informationen in eine Kontext-Datei zu schreiben, die zusammen mit dem Logfile aufbewahrt wird, macht spätere Analysen deutlich effizienter.
7. Fehler reproduzierbar machen: Umgebungs-Snapshots
Reproduzierbarkeit ist das Kernziel jeder Fehleranalyse in Deploy-Skripten. Ein Fehler, der sich nicht reproduzieren lässt, kann nicht zuverlässig gefixt werden. Der häufigste Grund für Nicht-Reproduzierbarkeit: Die genaue Umgebung zum Zeitpunkt des Fehlers ist nicht bekannt. Welche Version der Abhängigkeiten war installiert? Welche Konfiguration war aktiv? War ein bestimmter Service down, der beim erneuten Test wieder läuft?
Umgebungs-Snapshots lösen dieses Problem für die Fehleranalyse in Deploy-Skripten. Beim Deployment-Start wird ein Snapshot der relevanten Umgebungsinformationen gespeichert: installierte Package-Versionen (dpkg --get-selections oder rpm -qa), aktive Service-Status (systemctl list-units --state=running), Konfigurationsdatei-Checksums, Netzwerk-Routing-Tabelle und aktive Verbindungen. Wenn ein Deploy-Fehler auftritt, kann der Snapshot zum Zeitpunkt des Fehlers mit dem Snapshot eines erfolgreichen Deployments verglichen werden. Unterschiede im Snapshot sind potenzielle Fehlerursachen.
8. Post-Mortem-Prozess für Deploy-Fehler
Ein Post-Mortem ist kein Schuldzuweisungsprotokoll – es ist ein strukturierter Analyseprozess, der sicherstellt, dass ein Deploy-Fehler nicht zweimal auftritt. Die Grundlage für jedes Post-Mortem zur Fehleranalyse in Deploy-Skripten: Das vollständige Logfile des fehlgeschlagenen Deployments, der ERR-Handler-Stack-Trace, der Umgebungs-Snapshot und die Timeline der Ereignisse. Ohne diese Artefakte ist ein Post-Mortem Spekulation.
Ein effektiver Post-Mortem-Prozess für Fehleranalyse in Deploy-Skripten folgt fünf Schritten. Erstens: Den exakten Fehlermoment aus dem Logfile isolieren – Zeitstempel, fehlgeschlagener Befehl, Exit-Code, Zeilennummer. Zweitens: Den Kontext-Snapshot auswerten – was war anders als beim letzten erfolgreichen Deploy? Drittens: Den Fehler auf einem Testserver mit identischer Umgebung reproduzieren. Viertens: Den Fix implementieren und in einem Staging-Deploy verifizieren. Fünftens: Das Deploy-Skript um eine automatische Prüfung erweitern, die genau die Bedingung abfängt, die zum Fehler führte – so wird der Fix strukturell in das Skript eingebaut, nicht nur dokumentiert.
9. Debugging-Ansätze im direkten Vergleich
Für die Fehleranalyse in Deploy-Skripten stehen mehrere Ansätze zur Verfügung, die sich in ihrer Informationsdichte, Aufdringlichkeit und Eignung für verschiedene Szenarien unterscheiden.
| Ansatz | Informationsdichte | Aufdringlichkeit | Bester Einsatzbereich |
|---|---|---|---|
set -x |
Sehr hoch – jeder Befehl expandiert | Hoch – sehr viel Output | Lokales Debugging, BASH_XTRACEFD in Datei |
trap ERR |
Hoch – Stack-Trace bei Fehler | Niedrig – nur bei Fehler | Produktion – immer aktiv, kein Overhead |
| Strukturiertes Logging | Mittel – definierte Messpunkte | Niedrig – nur explizit | Produktion – Timeline-Rekonstruktion |
| Umgebungs-Snapshot | Hoch – vollständiger Systemzustand | Mittel – einmalig beim Start | Reproduzierbarkeit, Post-Mortem-Vergleich |
| ShellCheck | Mittel – statische Analyse | Keine – läuft vor Deployment | CI-Pipeline – Fehler vor dem Deploy finden |
Die Tabelle zeigt: Für eine vollständige Fehleranalyse in Deploy-Skripten braucht man alle vier Ansätze kombiniert. ShellCheck und statische Analyse finden viele Fehler vor dem Deploy. Strukturiertes Logging und trap ERR sind immer aktiv. set -x und Umgebungs-Snapshots werden bei Bedarf zugeschaltet oder als Standard-Artefakt beim Deployment erzeugt. Die Kombination sorgt dafür, dass kein Deploy-Fehler ohne vollständige Analyse bleibt.
Mironsoft
Deploy-Automatisierung, Fehleranalyse und Post-Mortem-Tooling
Deploy-Fehler reproduzierbar analysieren statt raten?
Wir rüsten bestehende Deploy-Skripte mit trap ERR, strukturiertem Logging, Umgebungs-Snapshots und Post-Mortem-Tooling nach – damit jeder Deploy-Fehler vollständig dokumentiert und reproduzierbar ist.
ERR-Handler
Stack-Trace, Exit-Code, Zeilennummer und Kontext-Collector bei jedem Fehler
Logging-Infrastruktur
Strukturierte JSON- oder Text-Logs mit Zeitstempel, Level und Zeilennummern
Post-Mortem-Tooling
Umgebungs-Snapshots, Diff-Tools und strukturierter Post-Mortem-Prozess
10. Zusammenfassung
Reproduzierbare Fehleranalyse in Deploy-Skripten ist kein Luxus, sondern die Grundlage zuverlässiger Automatisierung. set -x macht jeden ausgeführten Befehl mit expandierten Werten sichtbar. trap ERR fängt jeden Fehler ab und protokolliert Stack-Trace, Zeilennummer und Kontext. LINENO und BASH_LINENO zeigen präzise, wo im Skript ein Fehler auftrat. Strukturierte Logfiles machen Deploys über Zeit vergleichbar und automatisch auswertbar.
Der entscheidende Mehrwert dieser Werkzeuge liegt nicht im einzelnen Tool, sondern in ihrer Kombination und konsequenten Anwendung. Ein Deploy-Skript mit vollständiger Fehleranalyse-Infrastruktur macht Post-Mortems zu einer Frage von Minuten, nicht Stunden. Das senkt die Hemmschwelle für häufigere Deployments und erhöht das Vertrauen in den Automatisierungsprozess – weil jeder weiß: Wenn etwas schiefgeht, werden wir genau wissen, was und warum.
Fehleranalyse in Deploy-Skripten — Das Wichtigste auf einen Blick
set -x mit BASH_XTRACEFD
Trace-Ausgabe in separate Datei: exec {BASH_XTRACEFD}>>trace.log; set -x. Nur bei DEBUG=1 aktivieren für saubere Produktion.
trap ERR immer aktiv
ERR-Handler mit Stack-Trace via BASH_LINENO und FUNCNAME. Kontext-Collector im Fehlerfall automatisch ausführen.
Strukturierte Logfiles
Zeitstempel, Level, Datei, Zeilennummer in jeder Logzeile. JSON-Format für automatische Auswertung. Einen Logfile pro Deploy-Lauf.
Umgebungs-Snapshots
Beim Deploy-Start Systemzustand speichern. Nach Fehler mit vorherigem Erfolgs-Snapshot vergleichen. Grundlage für reproduzierbare Post-Mortems.