在日常运维工作中,我们经常会遇到日志文件不断膨胀的问题。一个不注意,日志就可能占满整个磁盘空间,导致系统崩溃。今天我们就来聊聊如何用Shell脚本这个老伙计,帮我们自动搞定日志轮转和归档这件麻烦事。

一、为什么需要日志轮转

想象一下,你的应用每天都在产生日志,就像家里不断堆积的快递包装盒。如果不及时清理,很快整个房间就会被塞满。日志文件也是这样,如果不加以控制,它们会不断吞噬宝贵的磁盘空间。

更糟糕的是,单个巨大的日志文件会带来很多问题:

  • 查找特定信息变得异常困难
  • 打开和操作文件速度变慢
  • 备份和传输变得麻烦
  • 可能触发磁盘空间告警

二、日志轮转的基本原理

日志轮转的核心思想很简单:定期将当前日志文件重命名或移动,然后让应用继续写入新的日志文件。通常我们会:

  1. 重命名当前日志文件(比如加上日期后缀)
  2. 创建新的空日志文件
  3. 压缩或归档旧的日志文件
  4. 删除过期的日志文件

在Linux系统中,logrotate是专门做这个的工具,但今天我们要用更灵活的Shell脚本来实现。

三、基础版Shell脚本实现

让我们从一个最简单的例子开始。假设我们有一个应用日志/app/logs/app.log需要管理。

#!/bin/bash
# 基础日志轮转脚本
# 技术栈:Bash Shell

LOG_DIR="/app/logs"
LOG_FILE="$LOG_DIR/app.log"
DATE=$(date +%Y%m%d)

# 检查日志目录是否存在
if [ ! -d "$LOG_DIR" ]; then
    echo "日志目录不存在: $LOG_DIR"
    exit 1
fi

# 检查日志文件是否存在
if [ ! -f "$LOG_FILE" ]; then
    echo "日志文件不存在: $LOG_FILE"
    exit 1
fi

# 执行日志轮转
mv "$LOG_FILE" "$LOG_FILE.$DATE"
touch "$LOG_FILE"
gzip "$LOG_FILE.$DATE"  # 压缩旧日志

echo "日志轮转完成: $LOG_FILE -> $LOG_FILE.$DATE.gz"

这个脚本做了以下几件事:

  1. 定义日志目录和文件路径
  2. 检查必要的目录和文件是否存在
  3. 重命名当前日志文件(加上日期后缀)
  4. 创建新的空日志文件
  5. 压缩旧日志文件

四、进阶版:支持保留期限和自动清理

基础版虽然能用,但还不够完善。让我们增强一下功能:

#!/bin/bash
# 进阶日志轮转脚本
# 技术栈:Bash Shell

LOG_DIR="/app/logs"
LOG_FILE="$LOG_DIR/app.log"
KEEP_DAYS=30  # 保留30天的日志
DATE=$(date +%Y%m%d)

# 检查日志目录
[ -d "$LOG_DIR" ] || { echo "日志目录不存在: $LOG_DIR"; exit 1; }
[ -f "$LOG_FILE" ] || { echo "日志文件不存在: $LOG_FILE"; exit 1; }

# 执行日志轮转
mv "$LOG_FILE" "$LOG_FILE.$DATE"
touch "$LOG_FILE"
gzip "$LOG_FILE.$DATE"

# 清理过期日志
find "$LOG_DIR" -name "app.log.*.gz" -mtime +$KEEP_DAYS -delete

echo "日志轮转完成,并清理了超过$KEEP_DAYS天的旧日志"

这个版本新增了:

  1. 定义日志保留天数
  2. 使用find命令自动清理过期日志
  3. 更简洁的错误检查语法

五、企业级完整解决方案

在实际生产环境中,我们需要考虑更多因素。下面是一个更完整的实现:

#!/bin/bash
# 企业级日志轮转脚本
# 技术栈:Bash Shell

# 配置部分
LOG_DIR="/app/logs"
LOG_FILES=("app.log" "access.log" "error.log")  # 支持多个日志文件
KEEP_DAYS=30
COMPRESS_LEVEL=6  # gzip压缩级别1-9
ARCHIVE_DIR="$LOG_DIR/archives"  # 归档目录
LOG_ROTATE_LOG="$LOG_DIR/log_rotate.log"  # 本脚本的操作日志

# 初始化
mkdir -p "$ARCHIVE_DIR"
TIMESTAMP=$(date +"%Y-%m-%d %H:%M:%S")

# 记录日志函数
log() {
    echo "[$TIMESTAMP] $1" >> "$LOG_ROTATE_LOG"
}

# 主循环处理每个日志文件
for LOG_FILE in "${LOG_FILES[@]}"; do
    FULL_PATH="$LOG_DIR/$LOG_FILE"
    
    # 检查日志文件是否存在
    if [ ! -f "$FULL_PATH" ]; then
        log "警告: 日志文件不存在 - $FULL_PATH"
        continue
    fi
    
    # 检查日志文件是否为空
    if [ ! -s "$FULL_PATH" ]; then
        log "信息: 日志文件为空 - $FULL_PATH,跳过轮转"
        continue
    fi
    
    # 准备新文件名
    ROTATE_DATE=$(date +%Y%m%d_%H%M%S)
    ARCHIVE_FILE="$ARCHIVE_DIR/${LOG_FILE}.${ROTATE_DATE}.gz"
    
    # 执行轮转
    log "开始轮转: $FULL_PATH"
    gzip -c -$COMPRESS_LEVEL "$FULL_PATH" > "$ARCHIVE_FILE" && {
        cat /dev/null > "$FULL_PATH"  # 清空原日志文件
        log "成功轮转: $FULL_PATH -> $ARCHIVE_FILE"
    } || {
        log "错误: 轮转失败 - $FULL_PATH"
        continue
    }
done

# 清理旧日志
log "开始清理超过$KEEP_DAYS天的归档日志"
find "$ARCHIVE_DIR" -name "*.gz" -mtime +$KEEP_DAYS -delete

log "日志轮转任务完成"

这个企业级版本包含:

  1. 支持多个日志文件
  2. 独立的归档目录
  3. 脚本自身的操作日志
  4. 更完善的错误处理
  5. 可配置的压缩级别
  6. 更精确的时间戳
  7. 详细的日志记录

六、如何与crontab配合实现自动化

写好了脚本,我们需要让它定期自动执行。Linux的crontab是完美的搭档。

# 每天凌晨2点执行日志轮转
0 2 * * * /path/to/log_rotate.sh >/dev/null 2>&1

或者,如果你想保留cron的执行日志:

# 每天凌晨2点执行,并记录cron日志
0 2 * * * /path/to/log_rotate.sh >> /var/log/log_rotate_cron.log 2>&1

七、处理特殊情况:应用需要重新加载日志文件

有些应用在启动时会锁定日志文件句柄,简单的mv可能无法让应用写入新文件。这时我们需要更聪明的办法:

#!/bin/bash
# 支持应用重新加载日志文件的轮转脚本
# 技术栈:Bash Shell

LOG_FILE="/var/log/nginx/access.log"
PID_FILE="/var/run/nginx.pid"

# 复制并截断原日志文件
cp "$LOG_FILE" "${LOG_FILE}.$(date +%Y%m%d)"
: > "$LOG_FILE"  # 清空原日志文件

# 通知Nginx重新打开日志文件
kill -USR1 $(cat "$PID_FILE")

这个技巧适用于Nginx、Apache等支持通过信号重新加载日志文件的服务。

八、技术优缺点分析

优点:

  1. 轻量级,不需要额外安装软件
  2. 灵活可控,可以完全自定义轮转逻辑
  3. 性能开销小
  4. 可以轻松集成到现有运维体系中

缺点:

  1. 需要手动处理很多细节
  2. 错误处理需要额外编码
  3. 对于复杂场景,脚本可能变得臃肿
  4. 不如logrotate等专业工具功能全面

九、注意事项

  1. 文件权限:确保脚本执行用户有足够的权限操作日志文件和目录
  2. 磁盘空间:轮转和压缩过程中需要临时磁盘空间
  3. 时区问题:确保服务器时区设置正确,以免影响日志日期
  4. 文件句柄:对于长时间运行的服务,可能需要特殊处理
  5. 日志完整性:确保轮转过程不会丢失任何日志数据

十、总结

通过Shell脚本实现日志轮转和归档是一个简单而强大的解决方案。虽然需要自己处理更多细节,但它提供了无与伦比的灵活性。对于中小型项目,或者有特殊需求的场景,这是一个非常值得考虑的方案。

记住,好的日志管理策略应该包括:

  • 明确的轮转周期
  • 合理的保留期限
  • 可靠的归档机制
  • 完善的监控告警

希望这篇文章能帮助你建立自己的日志管理方案。Happy logging!