Reliable automation
Write structured log lines
Add timestamps, levels, and one event per line so human and automated readers can follow a run.
10 minute lesson
Automation often runs without a terminal. When a backup job fires at 03:00, nobody is watching — the log is the only witness. Logs must show when, what, and whether an operation succeeded, in a shape both a human and grep can process.
A small logger function
Create a small logger:
log() {
local level=$1
shift
printf '%s level=%s message=%q\n' "$(date -u +%FT%TZ)" "$level" "$*"
}
log info 'backup started'
# 2026-08-03T15:12:09Z level=info message=backup\ started
Piece by piece: shift drops the level from the arguments so $* holds only the message words. date -u +%FT%TZ prints a UTC timestamp like 2026-08-03T15:12:09Z — always log in UTC, or the first daylight-saving change makes your timeline lie. The %q format quotes the message so spaces and special characters survive as one parseable field.
One event per line, key=value fields. That’s the entire format. grep 'level=error' finds failures, and a log shipper parses it without custom rules.
Use it through a run
log info 'backup started'
if tar -czf "$archive" -C /var/www .; then
log info 'archive created'
else
log error 'tar failed'
exit 1
fi
Redirect output to a lab file and generate success and error events:
./backup.sh >> /tmp/backup.log 2>&1
grep 'level=error' /tmp/backup.log
Verify each event carries a timestamp and a level, and that the error path logged before exiting. A failure the log never mentions is the failure you’ll debug blind.
Keep a run identifier when several jobs may overlap. Add run=$$ — the shell’s process ID — as a field on every line, and interleaved runs untangle instantly instead of reading as one confused story.
What must never be logged
Redact tokens, passwords, cookies, and private content before logging. The classic leak is logging a full curl command line, Authorization header included: the secret then lives in a world-readable file long after it was rotated. Log which endpoint you called and the status you got back, never the credential that authenticated you.
Lesson completed