从零搭建自动化日志管理方案
📚 目录导读
为什么需要日志轮转?
任何运行中的服务(如 Nginx、Java 应用、数据库)都会持续输出日志,如果不加控制,日志文件会无限增长,最终导致:

- 磁盘空间耗尽:导致服务崩溃甚至系统宕机
- 日志读取困难:巨型文件无法用
tail、grep快速检索 - 影响性能:持续的 I/O 写入会抢占系统资源
日志轮转(Log Rotation) 通过“切分+归档+清理”机制,让日志文件保持在一个可控的大小和时间范围内,每天生成一个新日志文件,将旧文件压缩,并删除 30 天前的文件。
日志轮转脚本的核心原理
一个标准的轮转脚本通常包含以下步骤:
- 重命名(Rotate):将当前日志文件如
app.log重命名为app.log.1,或按日期命名app.log-2025-01-25 - 通知进程:向应用进程发送信号(如
kill -USR1 <PID>),让程序重新打开日志文件句柄 - 压缩归档:将旧日志文件用
gzip或bzip2压缩,节省空间 - 清理过期文件:删除超过保留天数的日志文件
关键点:必须正确处理文件句柄,否则服务会继续向旧文件写入,导致轮转失败。
手写一个完整的日志轮转脚本
以下是一个生产可用的 Shell 脚本示例,支持按日期轮转、压缩和自动清理:
#!/bin/bash
# 配置区
LOG_PATH="/var/log/myapp"
LOG_FILE="app.log"
RETENTION_DAYS=30
COMPRESS=true
# 获取当前日期
DATE=$(date +%Y%m%d)
PID_FILE="/var/run/myapp.pid"
# 1. 检查日志文件是否存在
if [ ! -f "${LOG_PATH}/${LOG_FILE}" ]; then
echo "[$(date)] 日志文件不存在,跳过轮转"
exit 0
fi
# 2. 重命名日志文件(按日期)
mv "${LOG_PATH}/${LOG_FILE}" "${LOG_PATH}/${LOG_FILE}-${DATE}"
# 3. 通知应用重新打开文件句柄
if [ -f "$PID_FILE" ]; then
PID=$(cat "$PID_FILE")
kill -USR1 "$PID" 2>/dev/null || echo "警告:无法通知 PID $PID"
fi
# 4. 压缩旧日志
if $COMPRESS; then
find "${LOG_PATH}" -name "${LOG_FILE}-*" -not -name "*.gz" -exec gzip {} \;
fi
# 5. 删除过期文件(保留今天文件)
find "${LOG_PATH}" -name "${LOG_FILE}-*" -mtime +${RETENTION_DAYS} -delete
echo "[$(date)] 日志轮转完成:${LOG_FILE}-${DATE}"
使用方法:将脚本加入 crontab 每天凌晨执行:
0 0 * * * /usr/local/bin/rotate_logs.sh >> /var/log/rotation.log 2>&1
使用系统工具 logrotate 实现轮转
手动维护脚本虽然灵活,但 Linux 已有成熟工具 logrotate,对于大多数场景,建议优先使用:
典型配置(以 Nginx 为例)
创建配置文件 /etc/logrotate.d/nginx:
/var/log/nginx/*.log {
daily # 每天轮转
rotate 30 # 保留 30 个旧文件
compress # 压缩旧文件
delaycompress # 延迟一天再压缩(方便调试)
missingok # 日志文件不存在时忽略
notifempty # 空文件不轮转
dateext # 用日期命名归档文件
postrotate # 轮转后执行命令
[ -f /var/run/nginx.pid ] && kill -USR1 `cat /var/run/nginx.pid`
endscript
}
手动测试轮转
sudo logrotate -d /etc/logrotate.d/nginx # 调试模式(模拟运行) sudo logrotate -f /etc/logrotate.d/nginx # 强制立即轮转
logrotate 的优势在于:
- 统一管理所有服务的轮转策略
- 内置大小阈值(
size 100M)、时间轮转、错误处理 - 支持多文件模式(如
*.log)
常见问题与故障排查
问题 1:轮转后日志丢失
- 原因:脚本移动文件后未通知进程,导致进程继续向旧文件写入
- 解决:确保发送了正确的信号(如
SIGHUP或USR1),不同信号含义不同,需参考应用文档
问题 2:压缩后原文件未删除
- 原因:
delaycompress选项使压缩推迟一天 - 解决:确认配置中
delaycompress和compress是否组合正确
问题 3:crontab 脚本不执行
- 排查步骤:
- 检查 crontab 中路径是否写全(如
/usr/local/bin/rotate.sh) - 查看邮件记录:
grep rotate /var/log/cron或journalctl -u crond - 确保脚本有执行权限:
chmod +x rotate.sh
- 检查 crontab 中路径是否写全(如
QA 问答精选
Q1:日志轮转脚本中重命名和通知进程的顺序重要吗?
A:非常重要。必须先重命名文件,再通知进程,如果先通知进程,进程会立即打开新文件,但旧文件还未移动,可能导致两个进程都写同一个文件,正确流程是:移动旧文件 → 发送信号 → 进程创建新文件句柄 → 后续写入新文件。
Q2:如果应用不支持信号(如 kill -USR1)怎么办?
A:要么修改应用代码(添加信号处理),要么使用替代方案:
- 重启应用(但会导致短暂中断)
- 使用
copytruncate方式(logrotate 选项,先复制日志为空,再truncate原文件,但可能丢失少量实时数据)
Q3:日志文件大小超过 10GB,轮转脚本执行很慢怎么办?
A:建议:
- 对超大文件使用
truncate+cp方式(注意数据一致性) - 调整轮转频率,如改为每 6 小时轮转一次
- 使用异步压缩:
gzip &后台执行,但需防止多个压缩任务堆积
Q4:如何测试轮转脚本是否正确,而不实际影响生产日志?
A:创建测试脚本:
- 复制一份日志到测试目录
- 修改脚本中的
LOG_PATH为测试路径 - 手动执行
./rotate.sh - 检查归档文件命名、内容完整性、进程是否收到信号(可通过
strace监控)
通过本文的脚本示例和 logrotate 配置,你可以根据实际业务需求选择手动轮转或工具轮转,对于生产环境,建议采用 logrotate 并结合 systemd 服务管理,它比 crontab 更健壮,且能更好处理进程生命周期,如果日志量极大(如每天超过 100GB),还可以考虑引入日志中心化系统(如 ELK、Loki)来彻底减轻本地存储压力。