Shell脚本如何轮转日志避免撑爆磁盘

wen 实用脚本 1

Shell脚本如何轮转日志避免撑爆磁盘:从原理到生产级配置全解析

目录导读

  1. 为什么需要日志轮转?——磁盘撑爆的代价
  2. 日志轮转的核心原理:切割、压缩与清理
  3. 实战:Shell脚本实现三种日志轮转方案
  4. 高频问题FAQ:轮转脚本常见陷阱与解决方案
  5. 生产环境最佳实践:结合logrotate与系统级优化

Shell脚本如何轮转日志避免撑爆磁盘

为什么需要日志轮转?——磁盘撑爆的代价

问题场景:某金融系统后台每天生成约2GB的access_log,运行30天后,/var/log分区直接爆满,导致服务无法写入新日志,最终数据库连接池耗尽,业务中断超4小时。

核心风险

  • 日志文件持续增长(单个日志可达百GB)
  • 磁盘空间耗尽后,应用可能崩溃(如MySQL、Nginx)
  • 运维人员无法及时发现告警(凌晨突发概率高)

轮转的本质:将单一日志文件切割为多个带时间戳的小文件,并按策略清理最旧的归档日志,确保磁盘占用始终在可控阈值内。


日志轮转的核心原理:切割、压缩与清理

1 三个关键动作

动作 说明 典型实现
切割 将当前日志重命名,创建新空日志文件 mv access.log access_2025-03-01.log; touch access.log
压缩 对归档文件进行gzip/bzip2压缩,节省80%空间 gzip access_2025-03-01.log
清理 删除超过保留天数的归档文件 find ./ -name "access_*.gz" -mtime +30 -delete

2 两种主流轮转策略

  • 按大小轮转:日志达到指定size(如100MB)即触发切割
  • 按时间轮转:每小时/天/周固定时间点切割(更可控)

生产建议:优先采用按时间轮转,配合大小兜底(如当日日志超1GB强制切割),避免单文件过大。


实战:Shell脚本实现三种日志轮转方案

1 方案一:基于日期的简单轮转(适合中小型应用)

#!/bin/bash
# rotate_date.sh - 按日期轮转,保留最近30天
LOG_PATH="/var/log/myapp"
LOG_FILE="app.log"
RETENTION_DAYS=30
DATE=$(date +%Y%m%d)
cd $LOG_PATH || exit 1
# 切割:将当前日志移动到带日期的归档文件
[ -f "$LOG_FILE" ] && mv "$LOG_FILE" "${LOG_FILE}.${DATE}"
# 创建新空日志文件(保持权限)
touch "$LOG_FILE"
chmod 644 "$LOG_FILE"
# 压缩旧日志(跳过前一天的日志,因为可能正在写入)
find . -name "${LOG_FILE}.*" -mtime +0 -exec gzip {} \; 2>/dev/null
# 删除超过保留天数的压缩日志
find . -name "${LOG_FILE}.*.gz" -mtime +${RETENTION_DAYS} -delete
# 发送SIGHUP给监听进程(如syslog-ng)
if [ -f /var/run/syslog-ng.pid ]; then
    kill -HUP $(cat /var/run/syslog-ng.pid)
fi

适用场景:自定义应用日志(Java/Python),无需重载服务即可继续写入。

2 方案二:带大小检测的混合轮转(避免单文件过大)

#!/bin/bash
# rotate_mixed.sh - 每天轮转一次,但当日志超1GB时立即触发
MAX_SIZE=$((1024*1024*1024))  # 1GB
while true; do
    CURR_SIZE=$(stat --format=%s "$LOG_PATH/$LOG_FILE" 2>/dev/null || echo 0)
    if [ $CURR_SIZE -gt $MAX_SIZE ]; then
        echo "[$(date)] 日志超1GB,强制执行大小轮转" >> /var/log/rotate_history.log
        # 执行切割(使用当前时间戳)
        TIMESTAMP=$(date +%Y%m%d_%H%M%S)
        mv "$LOG_PATH/$LOG_FILE" "$LOG_PATH/${LOG_FILE}.${TIMESTAMP}"
        touch "$LOG_PATH/$LOG_FILE"
        # 压缩旧文件(排除刚刚切割的)
        gzip "$LOG_PATH/${LOG_FILE}.${TIMESTAMP}"
    fi
    sleep 300  # 每5分钟检查一次
done

关键优化:使用while sleep实现守护进程化,结合cron每天固定时间轮转一次。

3 方案三:集成压缩与磁盘告警(生产级)

#!/bin/bash
# rotate_prod.sh - 带磁盘阈值警告的完整轮转脚本
THRESHOLD_WARN=80  # 磁盘使用率超过80%触发告警
THRESHOLD_CRIT=90  # 超过90%强制删除非必要日志
check_disk_usage() {
    local usage=$(df /var/log | awk 'NR==2{print $5}' | sed 's/%//')
    if [ $usage -ge $THRESHOLD_CRIT ]; then
        echo "[ERROR] 磁盘使用率 ${usage}%,大于 ${THRESHOLD_CRIT}%,删除7天前日志"
        find $LOG_PATH -name "*.gz" -mtime +7 -delete
        find $LOG_PATH -name "*.bz2" -mtime +7 -delete
    elif [ $usage -ge $THRESHOLD_WARN ]; then
        echo "[WARN] 磁盘使用率 ${usage}%,接近告警阈值"
        # 可选择发送邮件:mail -s "Log Rotate Warning" admin@domain.com
    fi
}
# 主流程:先检查磁盘,再轮转
check_disk_usage
# 常规轮转逻辑(参考方案一)
# ......

为什么需要磁盘阈值:防止日志量暴增(如攻击流量)导致轮转脚本还在运行,磁盘已被撑满。


高频问题FAQ:轮转脚本常见陷阱与解决方案

Q1:为什么我轮转后,应用还在写旧文件?
A:应用持有旧文件描述符,需在切割后发送SIGHUP信号或在脚本中执行kill -USR1 <PID>(如Nginx),或使用logrotatecopytruncate指令(复制后截断原文件)。

Q2:压缩后日志无法正常查看?
A:部分应用框架(如Apache)会持续写入文件直到压缩完成,解决方案:先使用mv重命名,压缩时排除/proc目录下的文件,或等1-2分钟再压缩。

Q3:脚本执行时,日志文件正在被写入,会不会丢数据?
A:使用mv命令是原子操作,不会丢数据,但可能丢失最后几十毫秒的日志,生产环境建议在业务低谷期执行轮转(如凌晨3点)。

Q4:如何避免重复轮转?
A:在脚本开头添加锁机制:

LOCKFILE="/var/log/rotate.lock"
if [ -f $LOCKFILE ]; then
    echo "轮转脚本已在运行,退出"
    exit 0
fi
trap "rm -f $LOCKFILE" EXIT
touch $LOCKFILE

生产环境最佳实践:结合logrotate与系统级优化

1 为什么推荐使用logrotate而非纯Shell脚本?

  • logrotate是Linux标准日志轮转工具,支持:
    • 多应用预设配置(Nginx/MySQL/PostgreSQL)
    • 自动检测夏令时
    • 邮局通知(配合mail命令)
  • Shell脚本适合高度定制场景,如:
    • 需要控制云监控告警
    • 日志文件数量需精细备份

2 一个完整的logrotate配置示例(/etc/logrotate.d/myapp)

/var/log/myapp/app.log {
    daily                    # 每日轮转
    rotate 30               # 保留30个归档
    compress                # 启用压缩
    delaycompress           # 延迟一天压缩(避免正在写入时压缩)
    missingok               # 如果日志缺失,不报错
    notifempty              # 空文件不轮转
    create 0644 root root   # 创建新文件时设置权限
    postrotate              # 轮转后执行脚本
        /usr/bin/killall -HUP myapp 2>/dev/null || true
    endscript
    size 500M               # 大小超过500M也触发轮转
    maxage 30               # 删除超过30天的归档
}

3 监控报警与自动化

# 在crontab中添加每5分钟一次的磁盘检查
*/5 * * * * /root/scripts/check_disk_alert.sh
# check_disk_alert.sh内容:
#!/bin/bash
USAGE=$(df /var/log | awk 'NR==2{print $5}' | sed 's/%//')
if [ $USAGE -gt 85 ]; then
    # 发送到企业微信/钉钉机器人
    curl -X POST "https://qyapi.weixin.qq.com/cgi-bin/webhook/send?key=xxx" \
        -H "Content-Type: application/json" \
        -d '{"msgtype":"text","text":{"content":"日志磁盘使用率 '$USAGE'%,请检查轮转脚本"}}'
fi

4 性能优化建议

  • 使用异步压缩:压缩操作可能消耗CPU,建议在低负载时段执行(如cron脚本中ionice -c 3指定空闲I/O优先级)
  • 分目录存储:按应用/日期创建子目录(如/var/log/myapp/2025/03/),避免单目录文件过多
  • 限制归档数量:即使保留30天,若日志量极大,可配置rotate 90并配合maxsize 100M防止单个归档过大

抱歉,评论功能暂时关闭!