5.5.1 日志设计与分级规范

日志设计与分级规范#

日志是脚本可观测性的基础,应围绕“可读、可搜、可关联、可追溯”设计。本节从规范、落地实现、排错与练习四个角度给出可执行方案。

1. 原理草图:日志从脚本到平台#

文章图片

2. 日志设计原则(带示例)#

  • 结构化:字段固定、顺序统一
    示例(键值对):
    ts=2024-06-10 10:20:33 level=INFO host=node01 script=backup.sh func=main trace=job-20240610 msg="backup start"
  • 上下文完整:包含时间、主机、脚本名、函数名、任务标识、耗时
  • 可关联:引入 TRACE_ID/JOB_ID 贯穿多步骤
  • 可过滤:按级别筛选
    示例:只看错误
    bash grep 'level=ERROR' /var/log/app/task.log
  • 可持久:关键操作和错误必须落盘,调试日志可开关

3. 日志分级规范(五级)#

  • DEBUG:调试信息,如变量值、外部命令输出
  • INFO:正常流程信息
  • WARN:可恢复问题(重试/降级)
  • ERROR:失败但可继续
  • FATAL:致命错误并退出

4. 标准日志格式#

推荐统一格式(可读 & 可解析):

ts=<time> level=<LEVEL> host=<host> script=<script> func=<func> trace=<id> msg="<text>"

示例:

ts=2024-06-10 10:20:33 level=INFO host=node01 script=backup.sh func=dump_db trace=job-20240610 msg="backup start"

5. Shell 实现(可执行完整示例)#

文件:/opt/scripts/backup.sh

#!/usr/bin/env bash
set -o pipefail

LOG_FILE="/var/log/app/task.log"
DEBUG="${DEBUG:-0}"
TRACE_ID="${TRACE_ID:-job-$(date +%Y%m%d%H%M%S)}"

log() {
  local level="$1"; shift
  local msg="$*"
  local ts
  ts=$(date "+%Y-%m-%d %H:%M:%S")
  local line="ts=${ts} level=${level} host=${HOSTNAME} script=${0##*/} func=${FUNCNAME[1]:-main} trace=${TRACE_ID} msg=\"${msg}\""

  # 同时输出到控制台与文件;ERROR/FATAL走stderr
  if [[ "$level" == "ERROR" || "$level" == "FATAL" ]]; then
    echo "$line" | tee -a "$LOG_FILE" >&2
  else
    echo "$line" | tee -a "$LOG_FILE"
  fi
}

debug() { [[ "$DEBUG" == "1" ]] && log DEBUG "$*"; }
info()  { log INFO "$*"; }
warn()  { log WARN "$*"; }
error() { log ERROR "$*"; }
fatal() { log FATAL "$*"; exit 1; }

dump_db() {
  local start end cost
  start=$(date +%s)
  info "start dump"
  mysqldump -uroot -p'pass' testdb >/tmp/testdb.sql 2>/tmp/mysqldump.err
  if [[ $? -ne 0 ]]; then
    error "mysqldump failed, err=$(tail -n1 /tmp/mysqldump.err)"
    return 1
  fi
  end=$(date +%s); cost=$((end - start))
  info "dump success cost=${cost}s"
}

main() {
  info "backup job start"
  dump_db || warn "dump_db failed but continue"
  info "backup job end"
}

main "$@"

运行示例:

mkdir -p /var/log/app
chmod +x /opt/scripts/backup.sh
DEBUG=1 TRACE_ID=job-20240610 /opt/scripts/backup.sh

预期日志(节选):

ts=2024-06-10 10:20:33 level=INFO host=node01 script=backup.sh func=main trace=job-20240610 msg="backup job start"
ts=2024-06-10 10:20:33 level=INFO host=node01 script=backup.sh func=dump_db trace=job-20240610 msg="start dump"
ts=2024-06-10 10:20:34 level=ERROR host=node01 script=backup.sh func=dump_db trace=job-20240610 msg="mysqldump failed, err=Access denied"

6. 与系统日志联动(logger 示例)#

将日志同时写入 syslog:

logger -t backup.sh "level=INFO trace=${TRACE_ID} msg=backup start"

查看:

journalctl -t backup.sh -n 20

7. 常见排错#

  • 日志文件为空
    检查权限与路径:
    bash ls -l /var/log/app/task.log id
  • 只在屏幕不落盘
    确认 tee -aLOG_FILE 变量是否正确
  • TRACE_ID 未生效
    检查脚本开头是否设置:
    bash echo "$TRACE_ID"

8. 练习#

  1. 增加 elapsed_ms 字段,记录外部命令耗时(毫秒)。
  2. 增加 LOG_LEVEL 参数,支持只输出 INFO 及以上。
  3. ERROR/FATAL 日志单独写入 /var/log/app/task.err