在日常运维工作中,我们经常会遇到日志文件不断膨胀的问题。一个不注意,日志就可能占满整个磁盘空间,导致系统崩溃。今天我们就来聊聊如何用Shell脚本这个老伙计,帮我们自动搞定日志轮转和归档这件麻烦事。
一、为什么需要日志轮转
想象一下,你的应用每天都在产生日志,就像家里不断堆积的快递包装盒。如果不及时清理,很快整个房间就会被塞满。日志文件也是这样,如果不加以控制,它们会不断吞噬宝贵的磁盘空间。
更糟糕的是,单个巨大的日志文件会带来很多问题:
- 查找特定信息变得异常困难
- 打开和操作文件速度变慢
- 备份和传输变得麻烦
- 可能触发磁盘空间告警
二、日志轮转的基本原理
日志轮转的核心思想很简单:定期将当前日志文件重命名或移动,然后让应用继续写入新的日志文件。通常我们会:
- 重命名当前日志文件(比如加上日期后缀)
- 创建新的空日志文件
- 压缩或归档旧的日志文件
- 删除过期的日志文件
在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"
这个脚本做了以下几件事:
- 定义日志目录和文件路径
- 检查必要的目录和文件是否存在
- 重命名当前日志文件(加上日期后缀)
- 创建新的空日志文件
- 压缩旧日志文件
四、进阶版:支持保留期限和自动清理
基础版虽然能用,但还不够完善。让我们增强一下功能:
#!/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天的旧日志"
这个版本新增了:
- 定义日志保留天数
- 使用find命令自动清理过期日志
- 更简洁的错误检查语法
五、企业级完整解决方案
在实际生产环境中,我们需要考虑更多因素。下面是一个更完整的实现:
#!/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 "日志轮转任务完成"
这个企业级版本包含:
- 支持多个日志文件
- 独立的归档目录
- 脚本自身的操作日志
- 更完善的错误处理
- 可配置的压缩级别
- 更精确的时间戳
- 详细的日志记录
六、如何与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等支持通过信号重新加载日志文件的服务。
八、技术优缺点分析
优点:
- 轻量级,不需要额外安装软件
- 灵活可控,可以完全自定义轮转逻辑
- 性能开销小
- 可以轻松集成到现有运维体系中
缺点:
- 需要手动处理很多细节
- 错误处理需要额外编码
- 对于复杂场景,脚本可能变得臃肿
- 不如logrotate等专业工具功能全面
九、注意事项
- 文件权限:确保脚本执行用户有足够的权限操作日志文件和目录
- 磁盘空间:轮转和压缩过程中需要临时磁盘空间
- 时区问题:确保服务器时区设置正确,以免影响日志日期
- 文件句柄:对于长时间运行的服务,可能需要特殊处理
- 日志完整性:确保轮转过程不会丢失任何日志数据
十、总结
通过Shell脚本实现日志轮转和归档是一个简单而强大的解决方案。虽然需要自己处理更多细节,但它提供了无与伦比的灵活性。对于中小型项目,或者有特殊需求的场景,这是一个非常值得考虑的方案。
记住,好的日志管理策略应该包括:
- 明确的轮转周期
- 合理的保留期限
- 可靠的归档机制
- 完善的监控告警
希望这篇文章能帮助你建立自己的日志管理方案。Happy logging!
评论