Stage 2 · Tools
Scripting Patterns
Logging in Shell Scripts
Log levels, timestamps, colored output, and writing to syslog.
Log Levels
Log levels categorize messages by severity. Using consistent levels helps filter and prioritize information during debugging and monitoring.
| Level | Use Case | Example |
|---|---|---|
| DEBUG | Detailed diagnostic information | Variable values, function entry/exit |
| INFO | Normal operation messages | Task started, completed successfully |
| WARN | Potential issues | Disk space low, retry attempted |
| ERROR | Failures requiring attention | Command failed, file not found |
| FATAL | Critical failures, script cannot continue | Cannot connect to database, aborting |
Logging Functions
#!/usr/bin/env bash
set -euo pipefail
LOG_LEVEL="{LOG_LEVEL:-INFO}"
# Log level ordering
log_level_num() {
case "$1" in
DEBUG) echo 0 ;;
INFO) echo 1 ;;
WARN) echo 2 ;;
ERROR) echo 3 ;;
FATAL) echo 4 ;;
*) echo 0 ;;
esac
}
should_log() {
local msg_level="$1"
local configured
configured=$(log_level_num "$LOG_LEVEL")
local message
message=$(log_level_num "$msg_level")
(( message >= configured ))
}
log() {
local level="$1"
shift
should_log "$level" || return 0
local timestamp
timestamp=$(date '+%Y-%m-%d %H:%M:%S')
echo "[$timestamp] [$level] $*" >&2
}
log_debug() { log DEBUG "$@"; }
log_info() { log INFO "$@"; }
log_warn() { log WARN "$@"; }
log_error() { log ERROR "$@"; }
log_fatal() { log FATAL "$@"; exit 1; }
# Usage
log_info "Starting backup process"
log_debug "Config file: $CONFIG_FILE"
log_warn "Disk space below threshold"
log_error "Failed to connect to database"LOG_LEVEL environment variable controls which messages are displayed. log_level_num converts levels to numbers for comparison. Each log function checks the level before outputting.
Colored Output
#!/usr/bin/env bash
# Color codes
readonly RED='\033[0;31m'
readonly GREEN='\033[0;32m'
readonly YELLOW='\033[0;33m'
readonly BLUE='\033[0;34m'
readonly NC='\033[0m' # No Color
# Disable colors if not a terminal
if [[ ! -t 2 ]]; then
readonly RED='' GREEN='' YELLOW='' BLUE='' NC=''
fi
log_color() {
local color="$1"
local level="$2"
shift 2
echo -e "{color}[$(date '+%H:%M:%S')] [$level] $*{NC}" >&2
}
log_info() { log_color "$GREEN" "INFO" "$@"; }
log_warn() { log_color "$YELLOW" "WARN" "$@"; }
log_error() { log_color "$RED" "ERROR" "$@"; }ANSI color codes make log levels visually distinct. The -t check disables colors when output is not a terminal (e.g., when piped to a file). This prevents garbled log files.
Always check [[ -t 2 ]] before using colors. When stderr is redirected to a file, colors become escape sequences. The pattern above handles this automatically.
File Logging
#!/usr/bin/env bash
set -euo pipefail
LOG_FILE="{LOG_FILE:-/var/log/$(basename "$0").log}"
LOG_LEVEL="{LOG_LEVEL:-INFO}"
# Dual output: terminal and file
exec > >(tee -a "$LOG_FILE") 2>&1
log() {
local level="$1"
shift
echo "[$(date '+%Y-%m-%d %H:%M:%S')] [$level] $*"
}
log_info "Script started"
# All output goes to both terminal and log fileexec > >(tee -a file) redirects both stdout and stderr through tee, which duplicates output to both the terminal and the log file. This is the simplest way to implement file logging.
syslog Integration
#!/usr/bin/env bash
set -euo pipefail
readonly SYSLOG_TAG="myapp"
log_syslog() {
local level="$1"
shift
logger -t "$SYSLOG_TAG" -p "user.$level" "$*"
}
# Usage
log_syslog info "Deployment started"
log_syslog warning "Disk space low"
log_syslog err "Connection failed"
# Read from syslog
journalctl -t myapp --since "1 hour ago"
# Filter by level
journalctl -t myapp -p errlogger writes to syslog. -t sets the tag (identify your app). -p sets the facility and level. journalctl reads from systemd's journal, which replaces traditional syslog on modern systems.
#!/usr/bin/env bash
# JSON structured logging (for log aggregation systems)
log_json() {
local level="$1"
local message="$2"
shift 2
local extra=""
while [[ $# -gt 0 ]]; do
extra="$extra, "$1": "$2""
shift 2
done
echo "{"timestamp":"$(date -u '+%Y-%m-%dT%H:%M:%SZ')","level":"$level","message":"$message"$extra}"
}
# Usage
log_json INFO "Deploy started" version "1.2.3" user "alice"
# {"timestamp":"2025-01-15T10:30:00Z","level":"INFO","message":"Deploy started","version":"1.2.3","user":"alice"}
log_json ERROR "Connection failed" host "db.example.com" port "5432"
# {"timestamp":"2025-01-15T10:30:00Z","level":"ERROR","message":"Connection failed","host":"db.example.com","port":"5432"}Structured logging (JSON format) is essential for log aggregation systems like ELK, Datadog, or CloudWatch. It makes logs searchable and analyzable.
Use logrotate for log management: compress old logs, delete after N days, and rotate by size or time. Configure it in /etc/logrotate.d/ for system-wide logs or per-application.
Mark this lesson complete to store local progress and unlock a cleaner resume path the next time you visit.