Linux 日志轮转 logrotate 从入门到实战:彻底告别磁盘被日志塞满


Linux 日志轮转 logrotate 从入门到实战:彻底告别磁盘被日志塞满

VPS 跑久了最怕什么?不是 CPU 飙高,而是磁盘被日志悄悄塞满。Nginx 的 access.log、MySQL 的 error.log、应用的 stdout 输出,几个月不清理就可能吃掉几十 GB。本文将带你系统掌握 Linux 下最常用的日志轮转工具 logrotate,从原理、配置到排错一次讲透。

一、为什么要做日志轮转

日志文件是持续增长的,如果不加控制会出现三类问题:

磁盘空间耗尽:最直接的后果,服务写入日志失败甚至崩溃,网站出现 500 错误。

日志文件过大:单个日志文件达到几个 GB 后,用 tail/grep 排查问题会变得极慢,vi 直接打不开。

排查困难:问题往往发生在几天前,只有一个巨型文件时难以按时间段定位。

logrotate 通过"切割 + 压缩 + 轮转 + 删除"四个动作解决以上问题:每天或按大小把当前日志改名备份,重新生成空日志文件让应用继续写入,旧日志按策略压缩归档,超过保留数量的自动删除。

二、logrotate 核心配置语法

logrotate 的主配置文件是 /etc/logrotate.conf,针对单个应用的配置通常放在 /etc/logrotate.d/ 目录下。先看一个最常见的 Nginx 日志轮转配置:

/var/log/nginx/*.log {
    daily
    rotate 14
    compress
    delaycompress
    missingok
    notifempty
    create 0640 nginx adm
    sharedscripts
    postrotate
        if [ -f /var/run/nginx.pid ]; then
            kill -USR1 `cat /var/run/nginx.pid`
        fi
    endscript
}

逐条解释各指令的含义:

daily:按天轮转,可选 weekly、monthly。

rotate 14:保留最近 14 个备份,更早的自动删除。

compress:用 gzip 压缩旧日志,可大幅节省空间。

delaycompress:上一轮的日志暂不压缩,下一轮再压缩。因为 Nginx 可能在轮转后仍向旧文件句柄写入少量日志,延迟压缩避免丢日志。

missingok:日志文件不存在时不报错。

notifempty:日志为空则不轮转,避免产生大量空文件。

create 0640 nginx adm:轮转后按指定权限和属主重建空日志文件。

sharedscripts + postrotate:所有匹配的日志轮转完成后执行一次脚本,这里向 Nginx 主进程发送 USR1 信号,让 Nginx 重新打开日志文件句柄。这是日志轮转后日志"不写新文件"的关键。

三、按大小轮转的配置写法

生产环境更常见的需求是"日志超过 100MB 就轮转",并且一天可能切多次。此时使用 size 指令:

/var/log/myapp/app.log {
    size 100M
    rotate 10
    compress
    missingok
    notifempty
    copytruncate
}

这里用到了 copytruncate:先复制当前日志内容到备份文件,再把原文件截断为空。它不需要应用配合重开文件句柄,适合那些不响应 USR1 信号的程序(如 Java 应用)。缺点是两次操作之间有极小的时间窗口可能丢几行日志,但对大多数场景可接受。

注意:size 与 daily 可以同时存在,logrotate 会取"先满足"的条件触发轮转。

四、journald 日志的清理与限额

使用 systemd 的现代发行版,应用日志大多进了 journald。journald 日志默认占用量为文件系统的 10%,小磁盘 VPS 上这依然不小。查看当前占用:

journalctl --disk-usage
# 输出示例:Archived and active journals take up 1.2G on disk.

推荐显式限制 journald 的容量和保留时长,编辑 /etc/systemd/journald.conf:

[Journal]
SystemMaxUse=200M
SystemMaxFileSize=50M
MaxRetentionSec=7day
Compress=yes

修改后重启 journald 生效:

sudo systemctl restart systemd-journald

需要立即清理历史日志时,使用 vacuum 命令:

# 只保留最近 3 天
sudo journalctl --vacuum-time=3d
# 只保留 100MB
sudo journalctl --vacuum-size=100M

五、自定义应用的日志轮转:从零到上线

假设我们的 Python/Golang 应用把日志写到 /data/app/run.log,并且应用会监听 SIGUSR1 重开日志句柄(很多框架支持),完整配置流程如下:

第一步,创建配置:

sudo vim /etc/logrotate.d/myapp

/data/app/run.log {
    daily
    dateext
    dateformat -%Y%m%d
    rotate 30
    compress
    missingok
    notifempty
    sharedscripts
    postrotate
        # 向应用发送 USR1 信号重开日志
        pkill -USR1 -f "myapp-server"
    endscript
}

dateext:备份文件名带日期后缀(如 run.log-20261015.gz),而不是递增序号,便于按天检索。配合 dateformat 自定义日期格式。

第二步,手动测试配置是否正确:

# -d 为 debug 模式,只打印将要执行的动作,不实际执行
sudo logrotate -d /etc/logrotate.d/myapp

# 实际执行一次并显示详细输出
sudo logrotate -v -f /etc/logrotate.d/myapp

第三步,加入定时任务。logrotate 本身由 cron.daily 驱动,也可以用 systemd timer 指定精确执行时间:

sudo systemctl edit logrotate.timer

[Timer]
OnCalendar=daily
Persistent=true
RandomizedDelaySec=3600

六、logrotate 排错手册

日志轮转"没生效"是最常见的问题,按下面的清单逐一排查:

1. 使用 debug 模式看真实意图

sudo logrotate -d /etc/logrotate.conf

如果配置写错,这里会直接报出语法错误及行号。

2. 日志被反复轮转 / 文件权限不对

报错 "error: failed to rename ... Permission denied" 通常是日志目录不可写,或者 create 指定的属主不存在。检查目录权限:

ls -ld /var/log/nginx
# 应确认 nginx 用户对目录有写权限

3. 轮转后应用不再写日志

多半是应用仍持有旧文件句柄。确认 postrotate 里的信号是否正确,或用 lsof 验证:

# 查看哪个进程还在写被轮转的旧文件
lsof +D /var/log/nginx/ 2>/dev/null | grep deleted

输出中有 "deleted" 字样说明进程句柄未释放,需要补上 USR1 信号或改用 copytruncate。

4. 日志没有压缩

检查是否配置了 delaycompress(会推迟一轮)、是否 gzip 未安装:

which gzip || sudo apt install -y gzip

5. 轮转没有按时发生

确认 cron 服务在运行,且 logrotate 脚本有执行权限:

systemctl status cron --no-pager
ls -l /etc/cron.daily/logrotate

七、总结与最佳实践

日志轮转的核心要点归纳如下:需要应用配合重开句柄的程序(Nginx、Apache)用 sharedscripts + USR1 信号;无法配合的程序用 copytruncate;journald 单独限额;写完配置务必先用 -d 和 -v -f 手动验证再交给 cron。

建议把 logrotate 的检查加入日常巡检:定期执行 logrotate -d 确认无报错,并监控 / 分区使用率,双保险才能真正避免"日志写爆磁盘"的故障。


手机扫码阅读

微信或手机浏览器扫一扫,随时随地随心阅读与分享

MySQL 数据库备份实战:从 mysqldump 到异地同步的完整方案

Linux 网络故障排查实战:ping、traceroute、mtr、dig 与 tcpdump 全链路定位指南

评 论
更换验证码