Observability, Metrics, Logs, and Machine-Readable Output
Production automation is not complete when it merely succeeds. Operators and downstream systems need to understand what happened, how long it took, and what evidence exists when it fails.
Learning objectives
By the end of this lesson
- Separate machine results from diagnostics.
- Emit stable structured fields.
- Correlate events with run IDs.
- Record durations and outcomes.
- Keep observability secret-safe.
1. Observability explains one run
A production script should make it easy to answer what started, what changed, what failed, how long it took, and which resources were affected.
flowchart LR R["run"] --> L["logs"] R --> M["metrics"] R --> O["machine output"] R --> D["diagnostics"] L --> P["operator"] M --> P O --> P
2. Keep stdout for results and stderr for diagnostics
printf '%s\n' "$release_id"
printf 'level=INFO event=release_created id=%q\n' "$release_id" >&2This lets downstream automation capture a clean result without swallowing useful logs.
3. Use stable structured fields
printf 'ts=%s level=INFO event=deploy service=%q env=%q status=ok\n' "$(date -u '+%Y-%m-%dT%H:%M:%SZ')" "$service" "$environment" >&24. Generate machine-readable JSON with a JSON-aware tool
jq -n --arg release_id "$release_id" --arg environment "$environment" '{release_id:$release_id, environment:$environment, status:"success"}'5. Give every invocation a run ID
RUN_ID=${RUN_ID:-"release-$(date +%s)-$$"}
log_info() {
printf 'run_id=%q level=INFO msg=%q\n' "$RUN_ID" "$*" >&2
}A correlation ID ties together retries, remote commands, deployment events, and rollback diagnostics.
6. Record useful counts, durations, and outcomes
start=$SECONDS
deploy_all
status=$?
elapsed=$((SECONDS - start))
printf 'metric=release_duration_seconds value=%d\n' "$elapsed" >&2
printf 'metric=release_status value=%d\n' "$status" >&27. Observability must remain secret-safe
Do not add tokens, passwords, cookies, private keys, or sensitive payloads to events in the first place.
8. Hands-on lab: structured release result
mkdir -p "$HOME/devops-academy/bash/chapter20/lesson02"
cd "$HOME/devops-academy/bash/chapter20/lesson02"
cat > observable.sh <<'EOF'
#!/usr/bin/env bash
set -u
RUN_ID="demo-$$"
start=$SECONDS
log() {
local level=$1
shift
printf 'ts=%s run_id=%s level=%s msg=%q\n' "$(date -u '+%Y-%m-%dT%H:%M:%SZ')" "$RUN_ID" "$level" "$*" >&2
}
log INFO "release started"
sleep 1
release_id=rel-123
elapsed=$((SECONDS - start))
log INFO "release finished duration_s=$elapsed"
printf '{"release_id":"%s","status":"success"}\n' "$release_id"
EOF
chmod u+x observable.sh
./observable.sh > result.json
cat result.jsonVerification checklist
9. Knowledge check
Question 1. Why separate stdout and stderr?
Question 2. What is a run ID for?
Question 3. What makes useful shell metrics?
Question 4. What should never be logged by default?
10. Summary
Production observability combines clean stdout contracts, structured stderr logs, run IDs, durations, outcomes, and bounded diagnostic evidence.
11. Further reading
- POSIX standard streams.
- OpenTelemetry logging and metrics concepts.
- OWASP Logging Cheat Sheet.
- jq documentation.
Keep the academy open
Support free, practical DevOps education.
Every lesson is designed to remain readable in a browser, downloadable from GitHub, and usable without a paid learning platform. Contributions help expand and maintain the curriculum.
0x716c4Ab160C4B66F31a28AE2448BfF68fc3a2ef0
Send only Ethereum/ERC-20 compatible assets to this address.