1. 日志轮转的必要性与核心挑战
日志文件就像系统的"黑匣子",记录着每个关键操作和异常事件。但如果不加控制,单个日志文件可能膨胀到几十GB,不仅占用磁盘空间,还会拖慢日志查询速度。去年我们线上系统就遇到过因单个日志文件过大导致日志分析工具崩溃的情况,最终不得不手动分割处理。
日志轮转(Log Rotation)正是解决这个问题的标准方案。它通过预设规则(如文件大小或时间间隔)自动分割、归档旧日志,确保当前日志文件保持合理体积。在Linux系统中,logrotate是官方推荐的轮转工具,但掌握Shell脚本实现方案能带来两个独特优势:
- 完全可控:自定义轮转逻辑,适应特殊业务场景(如特定错误日志需要单独保留)
- 无依赖:在精简环境中无需安装额外软件包
2. 基于文件大小的轮转实现
2.1 核心检测逻辑
判断日志文件是否超限的最可靠方式是直接检查其字节数。这里推荐使用stat命令而非du,因为前者能精确获取文件实际内容大小:
bash复制LOG_FILE="/var/log/myapp.log"
MAX_SIZE_MB=100
current_size=$(stat -c%s "$LOG_FILE")
max_size_bytes=$((MAX_SIZE_MB * 1024 * 1024))
if [ "$current_size" -gt "$max_size_bytes" ]; then
echo "触发轮转:当前日志大小 ${current_size} 字节"
fi
注意:
stat在不同系统上的参数可能不同。BSD系统(如MacOS)需使用stat -f%z获取文件大小
2.2 轮转文件命名策略
好的命名方案应包含时间戳和序列号,方便后续排查。建议采用这样的格式:
bash复制rotate_time=$(date +"%Y%m%d_%H%M%S")
mv "$LOG_FILE" "${LOG_FILE}.${rotate_time}.1"
对于可能发生的并发写入,可以添加锁机制:
bash复制(
flock -x 200
mv "$LOG_FILE" "${LOG_FILE}.${rotate_time}.1"
# 重新创建日志文件
> "$LOG_FILE"
) 200>"${LOG_FILE}.lock"
2.3 历史日志清理
轮转后需要控制归档文件数量,避免磁盘被旧日志占满。这里演示保留最近5个版本:
bash复制# 按修改时间排序,删除最旧的
ls -t "${LOG_FILE}".* | tail -n +6 | xargs rm -f
3. 基于时间的轮转方案
3.1 定时触发机制
通过crontab设置每日轮转是最常见方案:
bash复制0 0 * * * /usr/local/bin/rotate_logs.sh
在脚本中可以通过日期判断实现更灵活的周期控制,比如按周轮转:
bash复制current_week=$(date +%V)
if [ ! -f "${LOG_FILE}.week" ]; then
echo "$current_week" > "${LOG_FILE}.week"
elif [ "$current_week" != "$(cat "${LOG_FILE}.week")" ]; then
# 执行轮转
echo "$current_week" > "${LOG_FILE}.week"
fi
3.2 时区处理要点
对于跨时区系统,必须统一使用UTC时间避免混乱:
bash复制rotate_time=$(date -u +"%Y%m%d")
4. 生产环境增强方案
4.1 日志切割的原子性操作
直接mv大文件可能导致日志丢失,更安全的做法是:
bash复制# 1. 复制文件内容
cp "$LOG_FILE" "${LOG_FILE}.${rotate_time}"
# 2. 清空原文件
: > "$LOG_FILE"
# 3. 压缩旧日志(避免占用空间)
gzip "${LOG_FILE}.${rotate_time}"
4.2 日志服务重载
对于持续写入的守护进程,需要通知其重新打开日志文件。典型方案:
bash复制# Nginx示例
kill -USR1 $(cat /var/run/nginx.pid)
4.3 监控与报警
添加轮转结果检查,失败时触发报警:
bash复制if ! mv "$LOG_FILE" "${LOG_FILE}.${rotate_time}"; then
echo "日志轮转失败!" | mail -s "警报: $HOSTNAME 日志异常" admin@example.com
fi
5. 性能优化实践
5.1 大文件处理技巧
当处理GB级日志时,可以启用进度显示:
bash复制pv "$LOG_FILE" > "${LOG_FILE}.${rotate_time}"
5.2 压缩算法选择
根据CPU和IO平衡选择压缩级别:
bash复制# 快速压缩(低CPU开销)
gzip --fast "${LOG_FILE}.${rotate_time}"
# 最佳压缩率(高CPU开销)
gzip --best "${LOG_FILE}.${rotate_time}"
6. 典型问题排查指南
6.1 轮转后日志丢失
可能原因:
- 应用程序未正确重新打开文件(需发信号)
- 权限问题导致新文件创建失败
检查步骤:
bash复制# 检查文件inode是否变化
ls -i "$LOG_FILE"
# 检查进程持有的文件描述符
lsof -p $(pidof app) | grep "$LOG_FILE"
6.2 轮转脚本不执行
排查路径:
- 检查crontab服务状态
- 验证脚本可执行权限
- 检查脚本输出是否被重定向
bash复制# 在crontab中捕获输出
0 0 * * * /path/to/script.sh >> /var/log/rotate.log 2>&1
7. 高级应用场景
7.1 多日志文件统一管理
使用配置文件批量管理:
bash复制#!/bin/bash
LOG_FILES=(
"/var/log/service1.log"
"/var/log/service2.log"
)
for log in "${LOG_FILES[@]}"; do
# 执行轮转逻辑
done
7.2 云环境适配
在Kubernetes中,需要处理日志符号链接:
bash复制real_log=$(readlink -f "$LOG_FILE")
rotate "$real_log"
8. 实测性能数据对比
在4核8G的测试机上处理1GB日志文件:
| 操作方式 | 耗时(秒) | CPU占用 | 磁盘IO |
|---|---|---|---|
| 直接mv | 0.02 | 1% | 低 |
| cp+清空 | 12.7 | 35% | 高 |
| gzip压缩 | 58.3 | 90% | 高 |
9. 我的实践心得
-
测试环境验证:首次部署前务必用
fallocate创建测试日志文件验证:bash复制
fallocate -l 100M /var/log/test.log -
文件描述符泄漏:遇到过Java应用在轮转后仍向旧文件写入的情况,最终通过
lsof发现未关闭的文件句柄 -
监控关键指标:建议记录这些数据:
- 每次轮转时的文件大小
- 轮转执行耗时
- 压缩率变化
-
异常处理:在脚本开头添加这些防护措施:
bash复制set -euo pipefail trap 'echo "错误发生在第 $LINENO 行"' ERR