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信号后,会执行以下操作:

  1. 重新打开配置中指定的日志文件(按照原来的文件名创建新文件)
  2. 关闭旧的日志文件描述符
  3. 将后续日志写入新打开的文件

这就是为什么切割日志后必须发送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;如果需要自定义复杂逻辑,可以参考优化版脚本自行实现。


—— 日志切割看似简单,但原理值得深入理解。如有问题欢迎交流~

posted @ 2017-09-08 14:38  一起走过的路  阅读(280)  评论(0)    收藏  举报