Shell Scripting Bash Logging Intermediate May 2026

Shell Scripting Logging & Alerting

Build production-grade logging into every script — log levels, timestamped structured logs, log rotation, syslog integration with logger, and email and Slack alerts for critical events.

A script without logging is a black box — when it fails at 2 AM, you have no way to know what happened or why. Good logging is what transforms a script from a tool you run manually into infrastructure you can trust in production.

BASH
#!/usr/bin/env bash
# lib/logging.sh — Production logging library

# ── Configuration ─────────────────────────────────────────
LOG_FILE="${LOG_FILE:-/var/log/myapp/app.log}"
LOG_LEVEL="${LOG_LEVEL:-INFO}"   # DEBUG INFO WARN ERROR
LOG_TO_SYSLOG="${LOG_TO_SYSLOG:-false}"

# ── Level hierarchy ───────────────────────────────────────
declare -A _LOG_LEVELS=([DEBUG]=0 [INFO]=1 [WARN]=2 [ERROR]=3)

_should_log() {
  local msg_level="${1}"
  [[ ${_LOG_LEVELS[$msg_level]} -ge ${_LOG_LEVELS[$LOG_LEVEL]} ]]
}

_log() {
  local level="${1}"; shift
  _should_log "${level}" || return 0
  local ts; ts=$(date '+%Y-%m-%d %H:%M:%S')
  local msg="[${ts}] [${level}] [$$] $*"
  echo "${msg}" | tee -a "${LOG_FILE}"
  [[ "${LOG_TO_SYSLOG}" == "true" ]] && logger -t myapp "${level}: $*"
}

log_debug() { _log "DEBUG" "$@"; }
log_info()  { _log "INFO"  "$@"; }
log_warn()  { _log "WARN"  "$@"; }
log_error() { _log "ERROR" "$@" >&2; }
log_fatal() { _log "ERROR" "$@" >&2; exit 1; }
BASH
# /etc/logrotate.d/myapp
/var/log/myapp/*.log {
    daily                    # rotate daily
    rotate 14                # keep 14 rotated files
    compress                 # gzip old files
    delaycompress            # compress yesterday's, not today's
    missingok                # don't error if log is missing
    notifempty               # don't rotate empty files
    create 0640 vriddh adm  # new file permissions owner group
    dateext                  # date suffix instead of numbers
    postrotate
        systemctl reload myapp 2>/dev/null || true
    endscript
}

# Test your logrotate config
logrotate -d /etc/logrotate.d/myapp    # dry run
logrotate -f /etc/logrotate.d/myapp    # force rotation now
BASH
# ── Simple mail alert ─────────────────────────────────────
send_alert() {
  local subject="${1}"
  local body="${2}"
  local to="${ALERT_EMAIL:-ops@example.com}"

  echo "${body}" | mail -s "[ALERT] ${subject}" "${to}"
}

# ── Alert with log file attachment ────────────────────────
send_alert_with_log() {
  local subject="${1}"
  local logfile="${2}"
  mail -s "[ALERT] ${subject}" \
       -a "${logfile}" \
       "${ALERT_EMAIL:-ops@example.com}" \
    < "${logfile}"
}

# ── Slack webhook alert ───────────────────────────────────
slack_alert() {
  local message="${1}"
  local colour="${2:-danger}"   # good warning danger
  local webhook="${SLACK_WEBHOOK_URL:?SLACK_WEBHOOK_URL not set}"

  curl -sf -X POST "${webhook}" \
    -H 'Content-Type: application/json' \
    -d "{
      \"attachments\": [{
        \"color\": \"${colour}\",
        \"title\": \"$(hostname -s) Alert\",
        \"text\": \"${message}\",
        \"footer\": \"$(date '+%Y-%m-%d %H:%M:%S')\"
      }]
    }" >/dev/null
}

# Usage in scripts
if (( disk_pct > 90 )); then
  log_error "Disk ${disk_pct}% full on $(hostname)"
  slack_alert "🚨 Disk ${disk_pct}% full on $(hostname)" "danger"
  send_alert "Disk Almost Full - $(hostname)" \
    "Disk usage: ${disk_pct}%\nMount: ${mount_point}"
fi
bash — logging library in action
vriddh@prod-01:~/scripts$LOG_LEVEL=DEBUG ./deploy.sh
[2026-05-01 10:14:02] [DEBUG] [18421] Checking prerequisites...
[2026-05-01 10:14:02] [INFO] [18421] mysql found at /usr/bin/mysql
[2026-05-01 10:14:02] [INFO] [18421] DB connection verified
[2026-05-01 10:14:03] [WARN] [18421] Disk 78% full — consider cleanup
[2026-05-01 10:14:05] [INFO] [18421] Deployment complete
vriddh@prod-01:~/scripts$LOG_LEVEL=WARN ./deploy.sh
[2026-05-01 10:15:02] [WARN] [18432] Disk 78% full — consider cleanup
(DEBUG and INFO suppressed)
✔ Logging rules — Always write to a file, not just stdout. Always include timestamp, level, and PID in every line. Use log levels and let operators filter. Rotate logs — never let them grow unbounded. Send alerts to Slack or email for ERROR and above — never just log them silently.