shell切割nginx日志
Nginx日志切割方案:从脚本到最佳实践
📌 为什么要切割日志?
Nginx运行时间越长,access.log和error.log就会越大。单个巨大的日志文件不仅占用磁盘空间,还会导致日志分析缓慢、检索困难。通过每天切割日志,可以按日期归档,便于管理和清理。本文分享一个完整的日志切割脚本,并深入讲解其原理。
📜 一、原始脚本解析
1.1 脚本代码
#!/bin/bash # 作者:GuoYabin # 功能:Nginx日志每日切割与清理 # 获取nginx主进程PID nginxpid=`/bin/ps aux|grep nginx |awk /master/'{print $2}'` # 通过lsof获取当前access.log的完整路径 accesslog=`/usr/sbin/lsof -p $nginxpid |awk /access.log/'{print $9}'` LOGS_PATH=`/usr/bin/dirname $accesslog` # 获取昨天的日期(格式:20260310) YESTERDAY=$(date -d "yesterday" +%Y%m%d) # 按天切割日志 mv ${LOGS_PATH}/access.log ${LOGS_PATH}/access_${YESTERDAY}.log mv ${LOGS_PATH}/error.log ${LOGS_PATH}/error_${YESTERDAY}.log # 向nginx主进程发送USR1信号,重新打开日志文件 kill -USR1 `ps axu | grep "nginx: master process" | grep -v grep | awk '{print $2}'` # 删除30天前的旧日志 cd ${LOGS_PATH} find . -mtime +30 -name "*20[1-9][0-9]*" | xargs rm -f exit 0
1.2 核心原理:为什么需要kill -USR1?
Linux文件系统的特点:
在Linux系统中,进程是通过文件描述符(File Descriptor)来访问文件的,而不是直接通过文件名。当一个文件被移动(mv)后,如果进程仍然持有该文件的文件描述符,它会继续向移动后的文件写入数据——这就是为什么直接mv日志文件后,nginx还会继续往旧的日志文件里写。
USR1信号的作用:
Nginx主进程接收到USR1信号后,会执行以下操作:
- 重新打开配置中指定的日志文件(按照原来的文件名创建新文件)
- 关闭旧的日志文件描述符
- 将后续日志写入新打开的文件
这就是为什么切割日志后必须发送kill -USR1的原因。
🔍 二、脚本逐行详解
| 命令/部分 | 作用 | 技术要点 |
|---|---|---|
ps aux|grep nginx|awk '/master/{print $2}' |
获取nginx主进程PID | 匹配包含"master"的行,避免匹配到worker进程 |
lsof -p $nginxpid |
列出该进程打开的所有文件 | lsof可以查看进程的文件描述符信息 |
dirname $accesslog |
获取日志目录路径 | 从完整文件路径中提取目录部分 |
date -d "yesterday" +%Y%m%d |
获取昨天的日期 | 格式化为20260310这样的数字格式 |
kill -USR1 <PID> |
通知nginx重新打开日志文件 | USR1是用户自定义信号,nginx专门用它来重新打开日志 |
find . -mtime +30 -name "*20[1-9][0-9]*" | xargs rm -f |
删除30天前的旧日志 | -mtime +30:修改时间超过30天;文件名匹配包含年份的日志 |
⚙️ 三、优化版脚本
原始脚本功能完整,但有一些可以优化的地方。以下是优化后的版本:
#!/bin/bash # ============================================ # Nginx日志切割脚本 v2.0 # 功能:每日切割日志、自动清理旧日志 # 支持:自定义日志路径、保留天数、压缩归档 # ============================================ # 配置参数(可根据实际情况修改) NGINX_PID_FILE="/usr/local/nginx/logs/nginx.pid" # nginx PID文件路径 LOG_PATH="/usr/local/nginx/logs" # 日志目录 LOG_FILES="access.log error.log" # 需要切割的日志文件 RETENTION_DAYS=30 # 保留天数 COMPRESS=1 # 是否压缩旧日志(1压缩,0不压缩) DATE_FORMAT=$(date -d "yesterday" +%Y%m%d) # 日期格式 # 检查PID文件是否存在 if [ ! -f $NGINX_PID_FILE ]; then echo "错误: Nginx PID文件 $NGINX_PID_FILE 不存在" exit 1 fi # 读取nginx主进程PID NGINX_PID=$(cat $NGINX_PID_FILE) # 检查进程是否存活 if ! kill -0 $NGINX_PID 2>/dev/null; then echo "错误: Nginx进程 $NGINX_PID 不存在" exit 1 fi # 进入日志目录 cd $LOG_PATH || { echo "无法进入目录 $LOG_PATH"; exit 1; } # 切割日志文件 for logfile in $LOG_FILES; do if [ -f $logfile ]; then mv $logfile ${logfile%.log}_$DATE_FORMAT.log echo "已切割: $logfile -> ${logfile%.log}_$DATE_FORMAT.log" fi done # 重新打开日志文件 kill -USR1 $NGINX_PID echo "已发送USR1信号,nginx重新打开日志文件" # 压缩旧日志(可选) if [ $COMPRESS -eq 1 ]; then find . -name "*.log" -mtime +1 -not -name "*.gz" | xargs gzip echo "已压缩昨日前的日志文件" fi # 删除超过保留天数的旧日志 find . -name "*.log*" -mtime +$RETENTION_DAYS -exec rm -f {} \; echo "已删除 $RETENTION_DAYS 天前的旧日志" exit 0
🔧 四、优化点详解
4.1 使用PID文件替代ps+grep
# 原方案:ps+grep(可能匹配到多个进程) nginxpid=`ps aux|grep nginx |awk '/master/{print $2}'` # 优化方案:从PID文件读取(更可靠) NGINX_PID=$(cat /usr/local/nginx/logs/nginx.pid)
4.2 进程存活检查
# 使用kill -0检查进程是否存在(不发送信号)
if ! kill -0 $NGINX_PID 2>/dev/null; then
echo "错误: Nginx进程不存在"
exit 1
fi
4.3 参数化配置
# 将可变参数提取到脚本开头,便于修改
LOG_PATH="/usr/local/nginx/logs"
RETENTION_DAYS=30
COMPRESS=1
4.4 循环处理多个日志
# 使用循环,可以轻松添加更多日志文件
LOG_FILES="access.log error.log admin.log"
for logfile in $LOG_FILES; do
mv $logfile ${logfile%.log}_$DATE_FORMAT.log
done
4.5 旧日志压缩
# 压缩昨日前的日志,节省磁盘空间
find . -name "*.log" -mtime +1 -not -name "*.gz" | xargs gzip
📅 五、添加到计划任务
5.1 创建日志切割脚本
# 保存脚本
vim /usr/local/bin/nginx_log_rotate.sh
chmod +x /usr/local/bin/nginx_log_rotate.sh
5.2 添加到crontab
# 编辑计划任务 crontab -e # 每天0点0分执行 0 0 * * * /usr/local/bin/nginx_log_rotate.sh > /var/log/nginx_rotate.log 2>&1 # 查看计划任务 crontab -l
⚠️ 六、注意事项
🔴 1. 日志路径一致性
确保脚本中的日志路径与nginx配置完全一致。如果不确定,可以用以下命令查看:
nginx -T | grep access_log
🔴 2. 权限问题
脚本执行用户需要有日志目录的读写权限。如果crontab是用root执行的,一般没问题。如果用普通用户,需确保该用户有权限:
ls -ld /usr/local/nginx/logs
🔴 3. 测试运行
正式加入crontab前,先手动运行脚本测试:
bash /usr/local/bin/nginx_log_rotate.sh
🔴 4. 磁盘空间监控
即使有自动清理,也建议监控磁盘空间,防止日志暴增导致磁盘满:
df -h /var/log du -sh /usr/local/nginx/logs
🔄 七、进阶:使用logrotate工具
其实Linux系统自带了专业的日志切割工具logrotate,无需自己写脚本。以下是使用logrotate管理nginx日志的配置:
# 创建nginx的logrotate配置文件 vim /etc/logrotate.d/nginx # 添加以下内容 /usr/local/nginx/logs/*.log { daily # 每天切割 rotate 30 # 保留30个旧日志 missingok # 日志不存在不报错 notifempty # 空日志不切割 compress # 压缩旧日志 delaycompress # 延迟压缩(先切割,下次再压缩) sharedscripts # 所有日志切割完再执行脚本 postrotate [ -f /usr/local/nginx/logs/nginx.pid ] && kill -USR1 `cat /usr/local/nginx/logs/nginx.pid` endscript }
logrotate会自动在每天凌晨执行切割(由cron管理),配置更简洁、更标准。
📝 八、总结
本文从原始脚本出发,深入讲解了:
- ✅ 日志切割的核心原理(为什么需要kill -USR1)
- ✅ 脚本逐行解析(每个命令的作用)
- ✅ 优化版脚本(参数化、PID文件、压缩、循环处理)
- ✅ crontab配置(每天0点执行)
- ✅ 专业工具logrotate(更标准的解决方案)
对于生产环境,推荐使用logrotate;如果需要自定义复杂逻辑,可以参考优化版脚本自行实现。
—— 日志切割看似简单,但原理值得深入理解。如有问题欢迎交流~

浙公网安备 33010602011771号