暗色模式

Linux logrotate 日志轮转:从轮转策略到防止磁盘写满

技术教程
2026-08-09
16
0

为什么需要日志轮转

运维服务器时,最容易被忽略的磁盘杀手就是日志文件。一个没有限制的 app.log,会从几天前的几十 KB 一路涨到几个 GB,直到把 / 分区写满:数据库连不上、SSH 登录后命令都敲不动、服务静默挂掉。而大多数情况下,日志里的旧内容是永远不会再被查看的。

所以核心思路很简单:把日志切成一段一段,旧的轮换出去,超期的直接删除。这就是 logrotate 干的事——它是 Linux 下最标准的日志轮转工具,几乎每个发行版都自带(apt install logrotatednf install logrotate),系统自己的 syslogaptufw 等日志都在用它管理。

logrotate 的轮转原理

每次轮转发生时,logrotate 对目标日志文件做三件事中的某一种组合:

  • rename:把 app.log 重命名为 app.log.1,然后新建一个空的 app.log 继续写。适合日志由服务自己持有文件描述符、重命名后继续往旧文件写也不怕的场景(配合 copytruncate 则另说)。
  • copytruncate:先把当前文件复制app.log.1,再截断原文件为 0 字节。好处是应用无需重启、文件句柄不变;坏处是复制与截断之间可能丢少量日志,且复制大文件时瞬时 I/O 高。
  • create:轮转后自动创建新的空日志文件,并设置权限与属主(create 0644 root adm)。

三种行为可以组合。默认配置用的就是 rename + create

除此之外还有几个高频参数,先混个脸熟,后面实测会用到:

参数作用
daily / weekly / monthly按时间轮转
size 100M / maxsize 100M按大小轮转(maxsize 是时间与大小取先到者)
rotate 3保留 3 份旧日志,超出的删除
compress轮转后用 gzip 压缩
delaycompress压缩延后一次轮转,保证上次的日志仍能被读取(compress 的常见搭档)
missingok日志文件不存在时不报错
notifempty空日志不轮转
dateext用日期后缀(app.log-20260809)替代数字后缀(app.log.1
postrotate ... endscript轮转后执行脚本(如 systemctl reload 让服务重新打开日志)

规则文件放哪里

配置分两层:

  • 全局配置 /etc/logrotate.conf:默认轮转频率、保留份数、是否压缩,以及一行关键的 include /etc/logrotate.d
  • 每个应用一个文件放在 /etc/logrotate.d/:Ubuntu 默认配置大致是 weekly + rotate 4 + create,并 su root adm 以正确身份执行。

在 Ubuntu 24.04 上默认的 /etc/logrotate.conf(去掉注释后)就是:

weekly
su root adm
rotate 4
create
include /etc/logrotate.d

logrotate 本体由 cronsystemd timer 每天执行(Ubuntu 是 /etc/cron.daily/logrotate),它会把全局配置和 /etc/logrotate.d/ 下所有规则合并处理。

编写自己的轮转规则

假设我们的应用把日志写进 /opt/logtest/app.log,希望它每天轮转、保留 3 份、轮转后压缩,又不希望应用重启。在 /etc/logrotate.d/ 里新建一个规则文件:

/opt/logtest/app.log {
    daily
    rotate 3
    missingok
    notifempty
    compress
    delaycompress
    copytruncate
}

几点说明:

  • copytruncate:不重命名文件,应用继续往原 app.log 写,无需重启。
  • compress + delaycompress:轮转出来的 app.log.1 这次先不压缩,等下一次轮转变成 app.log.2 时才 gzip——避免刚轮转完想看日志却打不开的尴尬。
  • notifempty:日志是空的时候跳过,避免空文件也被一遍遍轮转。

实测:跑一遍轮转看看效果

上面规则写好之后,先造点日志再手动触发轮转(-f 强制,忽略时间条件):

for i in $(seq 1 600); do
  echo "$(date '+%F %T') INFO request#$i handled" >> /opt/logtest/app.log
done
logrotate -f /etc/logrotate.d/logtest

第一次轮转后,app.log 被截断为 0 字节,原内容进了 app.log.1;等日志再次写满触发第二次轮转后,更早的那份会变成 app.log.2.gz 被压缩。完整过程如下图所示:

logrotate 轮转实测

图中可以清楚看到三个阶段:轮转前 app.log 是 27K;第一次轮转后 app.log 归零、内容变成 27K 的 app.log.1;第二次轮转后多出 app.log.2.gz(1.6K),压缩生效。注意 app.log.1 此时仍是 27K 未压缩——这正是 delaycompress 的效果,它要等下一次轮转才被压成 .2.gz

手动执行与调试

logrotate 默认由 cron 静默执行,出问题时可以手动跑,三种常用姿势:

logrotate /etc/logrotate.conf          # 正常执行,含所有规则
logrotate -v /etc/logrotate.d/logtest  # 详细输出,看它做了什么
logrotate -d /etc/logrotate.d/logtest  # debug 模式:只打印、不真正执行
logrotate -df /etc/logrotate.d/logtest # 强制 + debug,验证规则逻辑

-d 的调试输出会把每个判断都打出来,是排查"为什么没轮转"的首选。下图是对上面规则执行 logrotate -df 的输出:

logrotate 调试输出

rotating pattern 显示了保留 3 份的策略;considering log /opt/logtest/app.log 之后 log needs rotating 判定需要轮转;compressing log with /bin/gzip 对应 compress;最后的 copying ... / truncating ... 就是 copytruncate 实际执行的两个动作。

常见坑

  1. copytruncate 丢日志:复制与截断之间写入的那点数据会丢。对数据库这类绝不能丢日志的应用,用 rename + create,代价是应用需要正确处理 SIGHUP/重新打开日志,通常配合 postrotatesystemctl reload 使用。
  2. notifempty + 空日志:日志是空的时不会轮转(我们演示时就是靠它避免空轮转)。想让空日志也照转,去掉 notifempty 即可。
  3. dateext 同日覆盖dateext-20260809 这种日期后缀,同一天多次轮转会把同名文件覆盖掉。想要同一天多个备份就同时用 dateformat 带上时分(如 -%Y%m%d-%H%M),或干脆用数字后缀。
  4. 改配置后不生效logrotate 只认状态文件里的上次轮转时间。改完配置想立即验证,务必加 -f 强制,别指望改完下一分钟 cron 就替你轮转。
  5. 权限问题:规则里没写 create 的权限或 su 时,用普通用户跑日志轮转常会报 Permission denied。把轮转文件的属主写清楚:create 0644 root adm,或者像系统默认那样加 su root adm

与 journald 的配合

用 systemd 管理的服务日志走的是 journald,它不需要 logrotate:journal 自己按 SystemMaxUse(默认约占磁盘的 10%)自动清理。logrotate 主要对付的是那些还在往 /var/log/ 写文件的老式应用和第三方软件(Nginx、MySQL、PHP-FPM 等)。两者定位不同,但目标一致:别让日志写满磁盘

总结

日志轮转不是可选项,而是服务器的基础卫生习惯。logrotate 用一张配置就能同时搞定"按时间切分 + 按大小切分 + 压缩 + 保留份数 + 删除超期",配合 copytruncate 还能做到应用零重启。上手路线:先看 /etc/logrotate.conf/etc/logrotate.d/ 里的现有规则找感觉,再给自家应用写一条 daily + rotate 7 + compress + copytruncate 的规则,最后用 logrotate -df 验证一遍,就能放心交给 cron 了。

发表评论

暂无评论,快来抢沙发吧!