日志记录

日志是脚本运行的重要记录,对于调试、审计、问题排查都非常有价值。良好的日志记录习惯能让脚本的维护和问题定位事半功倍。

简单日志

最基本的日志就是 echo:

echo "开始执行任务"
echo "处理文件:$filename"
echo "任务完成"

输出到文件

echo "日志信息" >> app.log
echo "$(date) - 日志信息" >> app.log

日志函数

封装日志函数,统一格式:

log() {
    local level=$1
    shift
    local message="$*"
    local timestamp=$(date '+%Y-%m-%d %H:%M:%S')
    echo "[$timestamp] [$level] $message"
}

log "INFO" "开始执行"
log "ERROR" "文件不存在:$file"
log "DEBUG" "变量值:$var"

日志级别

#!/bin/bash

LOG_LEVEL=${LOG_LEVEL:-INFO}

LEVEL_DEBUG=0
LEVEL_INFO=1
LEVEL_WARN=2
LEVEL_ERROR=3

log() {
    local level=$1
    shift
    local message="$*"
    
    local current_level
    case $level in
        DEBUG) current_level=$LEVEL_DEBUG ;;
        INFO)  current_level=$LEVEL_INFO ;;
        WARN)  current_level=$LEVEL_WARN ;;
        ERROR) current_level=$LEVEL_ERROR ;;
    esac
    
    local threshold_level
    case $LOG_LEVEL in
        DEBUG) threshold_level=$LEVEL_DEBUG ;;
        INFO)  threshold_level=$LEVEL_INFO ;;
        WARN)  threshold_level=$LEVEL_WARN ;;
        ERROR) threshold_level=$LEVEL_ERROR ;;
    esac
    
    if [ $current_level -ge $threshold_level ]; then
        local timestamp=$(date '+%Y-%m-%d %H:%M:%S')
        echo "[$timestamp] [$level] $message"
    fi
}

log "DEBUG" "调试信息"
log "INFO" "普通信息"
log "WARN" "警告信息"
log "ERROR" "错误信息"

设置日志级别:

LOG_LEVEL=DEBUG ./script.sh

输出到不同目标

同时输出到屏幕和文件

log() {
    local message="[$(date '+%Y-%m-%d %H:%M:%S')] $*"
    echo "$message" | tee -a app.log
}

错误输出到 stderr

log_error() {
    echo "[ERROR] $*" >&2
}

使用 exec 重定向

#!/bin/bash

LOG_FILE="app.log"

exec 1> >(tee -a "$LOG_FILE")
exec 2>&1

echo "这行会同时输出到屏幕和文件"
echo "错误信息也会被记录"

使用 logger 命令

logger 命令可以把日志写入系统日志:

logger "脚本执行完成"
logger -t myscript "自定义标签"
logger -p local0.info "指定 facility 和级别"

查看日志:

tail -f /var/log/syslog
journalctl -t myscript

日志轮转

长时间运行的脚本需要考虑日志轮转:

按大小轮转

rotate_log() {
    local log_file=$1
    local max_size=${2:-10485760}  # 默认 10MB
    local max_files=${3:-5}
    
    if [ -f "$log_file" ]; then
        local size=$(stat -c%s "$log_file")
        if [ $size -gt $max_size ]; then
            for i in $(seq $((max_files - 1)) -1 1); do
                [ -f "${log_file}.${i}" ] && mv "${log_file}.${i}" "${log_file}.$((i + 1))"
            done
            mv "$log_file" "${log_file}.1"
        fi
    fi
}

按日期分割

log() {
    local log_file="app_$(date '+%Y%m%d').log"
    local timestamp=$(date '+%Y-%m-%d %H:%M:%S')
    echo "[$timestamp] $*" >> "$log_file"
}

使用 logrotate

创建配置文件 /etc/logrotate.d/myapp

/var/log/myapp/*.log {
    daily
    rotate 7
    compress
    missingok
    notifempty
    create 0644 user group
}

结构化日志

输出 JSON 格式的日志:

log_json() {
    local level=$1
    shift
    local message="$*"
    
    cat << EOF
{"timestamp":"$(date -Iseconds)","level":"$level","message":"$message","script":"$(basename $0)"}
EOF
}

log_json "INFO" "开始执行"

输出:

{"timestamp":"2024-03-15T10:00:00+08:00","level":"INFO","message":"开始执行","script":"script.sh"}

实用示例

完整的日志模块

#!/bin/bash

LOG_FILE=${LOG_FILE:-"/var/log/myapp/app.log"}
LOG_LEVEL=${LOG_LEVEL:-INFO}
LOG_MAX_SIZE=${LOG_MAX_SIZE:-10485760}
LOG_MAX_FILES=${LOG_MAX_FILES:-5}

init_log() {
    local log_dir=$(dirname "$LOG_FILE")
    mkdir -p "$log_dir"
    rotate_log
}

rotate_log() {
    if [ -f "$LOG_FILE" ]; then
        local size=$(stat -c%s "$LOG_FILE" 2>/dev/null || echo 0)
        if [ $size -gt $LOG_MAX_SIZE ]; then
            for i in $(seq $((LOG_MAX_FILES - 1)) -1 1); do
                [ -f "${LOG_FILE}.${i}" ] && mv "${LOG_FILE}.${i}" "${LOG_FILE}.$((i + 1))"
            done
            mv "$LOG_FILE" "${LOG_FILE}.1"
        fi
    fi
}

log() {
    local level=$1
    shift
    local message="$*"
    
    local timestamp=$(date '+%Y-%m-%d %H:%M:%S')
    local log_line="[$timestamp] [$level] $$ $message"
    
    case $level in
        ERROR|WARN)
            echo "$log_line" >&2
            ;;
        *)
            echo "$log_line"
            ;;
    esac
    
    echo "$log_line" >> "$LOG_FILE"
}

log_info()  { log "INFO" "$@"; }
log_warn()  { log "WARN" "$@"; }
log_error() { log "ERROR" "$@"; }
log_debug() { log "DEBUG" "$@"; }

# 使用
init_log
log_info "脚本开始执行"
log_error "发生错误"

带颜色的日志

RED='\033[0;31m'
GREEN='\033[0;32m'
YELLOW='\033[0;33m'
NC='\033[0m'

log_info() {
    echo -e "${GREEN}[INFO]${NC} $*"
}

log_warn() {
    echo -e "${YELLOW}[WARN]${NC} $*"
}

log_error() {
    echo -e "${RED}[ERROR]${NC} $*" >&2
}

小结

  • 使用日志函数统一日志格式
  • 支持日志级别控制输出
  • 重要日志输出到文件持久化
  • 错误日志输出到 stderr
  • 使用 logger 写入系统日志
  • 实现日志轮转避免日志过大
  • 结构化日志便于后续分析