首页 > Sysadmin > 正文

Linux 日志太大怎么轮转:logrotate 配置与实测

huyuanwen 2026-10-29 Sysadmin

轮转不生效多半卡在条件上:size、maxsize、minsize 谁起作用,看它们在配置里的书写顺序。

一、先确认是谁在跑:Ubuntu 24.04 用的是 logrotate.timer

systemctl is-enabled logrotate.timer; systemctl list-timers logrotate.timer
systemctl cat logrotate.timer logrotate.service
head -8 /etc/cron.daily/logrotate
cat /etc/logrotate.conf

Ubuntu 24.04 装的 logrotate 是 3.21.0-2build1 [2]。本机实测 logrotate.timer 状态为 enabled、active,单元里写的是 OnCalendar=daily、AccuracySec=1h、Persistent=true:每天一次,但允许在一小时窗口内挑时间触发,错过的开机后补跑。实测本机上一次触发是 19:30,下一次排在次日 00:00,所以别指望它准点。

很多人以为轮转是 cron.daily 干的。实测 /etc/cron.daily/logrotate 前几行就是判断 /run/systemd/system 这个目录存在就直接 exit 0,注释原文写着 skip in favour of systemd timer:只要 systemd 在跑,这个 cron 脚本自己就退出了。ArchWiki 的口径一致,logrotate 在多数发行版上靠 logrotate.timer 触发 [3]。

/etc/logrotate.conf 的出厂默认也值得先看一眼:weekly、rotate 4、create,而 compress 是注释掉的——也就是说默认轮转出来的旧日志不压缩,想省磁盘要自己加 compress。全局还有 su root adm。

service 单元里有两条容易被忽略的实测细节:ConditionACPower=true 和 Nice=19、IOSchedulingClass=best-effort、ProtectSystem=full。这台 WSL 的 /sys/class/power_supply 下有 AC1 与 BAT1,说明它认得电池;按单元定义,纯电池供电时这个 oneshot 会被条件跳过。这条只核对了单元文件与手册口径,没有真机拔电验证。

二、轮转条件:谁写在后面,谁说了算

# 配置 A:size 写在 daily 之前
cat > /etc/logrotate.d/appA <<'EOF'
/var/log/app/app.log {
  size 1k
  daily
  rotate 3
  missingok
  create
}
EOF
# 配置 B:size 写在 daily 之后
cat > /etc/logrotate.d/appB <<'EOF'
/var/log/app/app.log {
  daily
  size 1k
  rotate 3
  missingok
  create
}
EOF
chmod 644 /etc/logrotate.d/appA /etc/logrotate.d/appB

手册对这三个指令的措辞很硬:size 与时间条件是互斥的,如果写在时间条件之后,就无视上次轮转时间;后写的条件优先 [1]。maxsize 是超过阈值就轮转,minsize 是超过阈值但不早于时间间隔,两者都会同时看大小和时间戳 [1]。

实测四组,同一台机器、同一份 2001 字节日志、同一天跑两次(第一次带 -f,第二次不带):

  • size 1k 写在 daily 之前:第二次不轮转,日志停在 2001 字节,目录里只有 app.log 与 app.log.1。时间条件赢。
  • daily 写在 size 1k 之前:第二次照样轮转,多出 app.log.2。size 赢,间隔被无视。
  • daily 加 maxsize 1k:第二次轮转,多出 max.log.2。
  • daily 加 minsize 1k:第二次不轮转,它只负责挡住太小的文件。
  • 另一个实测结论:把间隔写成 hourly 并不会真的每小时轮转。测试里 hourly 条目强制轮转后紧接着再跑一次,没有产生第二份备份——间隔判定还在,而 timer 本身是一天一次。想按大小当天多次轮转,得同时改 timer,例如给 logrotate.timer 的 drop-in 里重写 OnCalendar,改完 systemctl daemon-reload。这类 drop-in 写法实测过:写 /etc/systemd/system/logrotate.service.d/no-acpower.conf,第一行是单元段头 Service,第二行 ConditionACPower=false,systemctl cat logrotate.service 会同时列出原单元和这个 override;回滚用 systemctl revert logrotate.service,实测它把 override 文件和目录一并删掉。logrotate 本身和它的手册都由 GitHub 上的上游项目维护,新版本的行为变化以那里为准 [4]。

    三、create 还是 copytruncate:进程一直开着日志文件时怎么选

    /var/log/app/app.log {
      daily
      rotate 3
      missingok
      create 0640 root root
      sharedscripts
      postrotate
          /usr/sbin/nginx -s reopen
      endscript
    }

    create 是把旧文件改名再新建一个,新文件是新的 inode;copytruncate 是复制一份再把原文件清零,inode 不变。手册明确警告 copytruncate 的复制与清零之间有个窗口,这段时间写进去的日志可能丢 [1]。

    实测两种写法下让一个进程全程持有同一个文件描述符:

  • 用 create:轮转前原文件 inode 12090、35 字节;轮转后当前文件换了 inode 12747 且是 0 字节,而那个进程后续写的内容全跑进了 app.log.1(inode 12090,涨到 70 字节)。也就是说,只要进程不重开日志,”活跃日志”会一直是空的,新日志全堆在备份里。
  • 用 copytruncate:inode 12895 前后不变,轮转后进程继续往同一个文件写,当前文件 7 行、备份 7 行。
  • 所以判断标准是进程会不会重开日志文件。nginx、rsyslog、MySQL 这类支持重开信号或命令的,用 create 加 postrotate 通知它重开,数据不会走偏;Ubuntu 自己给 rsyslog 的配置就是这么写的:sharedscripts 加 postrotate 调 /usr/lib/rsyslog/rsyslog-rotate。只有那种没有重开机制的老程序,才用 copytruncate 换稳定 inode,代价是接受那个丢日志的窗口。

    sharedscripts 的作用也实测过:同一小节里两个日志,postrotate 不带 sharedscripts 跑 2 次,带上跑 1 次。钩子必须写成多行并以 endscript 收尾,写成一行会直接报 prerotate, postrotate or preremove without endscript。

    四、改配置先空跑:-d 不改任何东西,-f 强制轮转,状态文件记时间

    logrotate -d /etc/logrotate.conf > /tmp/lrdbg.txt 2>&1; echo $?
    logrotate -d -s /tmp/try-status /etc/logrotate.d/appB   # 用独立状态文件试跑
    logrotate -f /etc/logrotate.d/appB                       # 确认无误再强制轮转
    grep -n "app.log" /var/lib/logrotate/status

    -d 的调试输出会逐条打印 considering log 和 log does not need rotating 加原因。实测跑完 /var/lib/logrotate/status 的修改时间前后完全一致,退出码 0,确认它只读不写。默认状态文件实测在 /var/lib/logrotate/status,权限 0640、属主 root root,格式首行是 logrotate state -- version 2,后面每条是路径加上次轮转时间。做实验时务必用 -s 指一份临时状态文件,否则会把真实日志的”上次轮转时间”改掉,当天那轮就跳过了。

    编号与压缩的实测结果:

  • rotate 3 加 compress(不带 delaycompress):连最新的 app.log.1 也立刻压成 app.log.1.gz,三份备份全是 gz。
  • 加上 delaycompress:app.log.1 保持明文,上一份 app.log.2.gz 才压缩。刚轮转完就要 grep 的场合应该带上它。
  • rotate 0:旧内容直接丢弃,当前文件被重建为 0 字节,没有 .1。
  • rotate -1:连续强制轮转 5 次,app.log.1 到 app.log.5 全留着。
  • dateext:备份名是 app.log-20261029 这种日期后缀,而不是数字。
  • 还有一个静默坑:配置文件本身不能被组或其他用户可写。实测把配置 chmod 成 0664 后,logrotate 打印 warning: Potentially dangerous mode ... 和 Ignoring ... because it is writable by group or others.,然后什么都不做,退出码还是 0。配置写完请 chmod 644。

    五、排错速查

  • 现象:配置加了却不轮转。原因:条件顺序让时间条件赢了(见第二节),或 timer 的 AccuracySec=1h 让它晚于预期。处理:logrotate -d 看原因行,再 logrotate -f 验证。
  • 现象:空日志也生成了 .1。原因:缺 notifempty。实测带 notifempty 的空文件不轮转,不带的会生成一份 0 字节的备份。
  • 现象:日志文件不存在就报错、退出码 1。原因:nomissingok 是默认行为 [1]。处理:显式加 missingok。
  • 现象:加了 su nobody nogroup 之后报 stat ... Permission denied、退出码 1。原因:logrotate 先切到该用户再访问路径,切过去之后没权限。处理:su 的属主属组要和日志及父目录匹配。
  • 现象:prerotate 里的脚本失败,日志没动。原因:prerotate 返回非 0 会中止该条目,实测文件保持原样、退出码 1。
  • 现象:postrotate 失败但备份已经生成。原因:脚本在轮转之后跑,实测此时 app.log.1 已存在、退出码 1。处理:补跑服务的重开命令,否则后续写入会继续进旧 inode。
  • 现象:配置里写错指令名。原因:logrotate 只忽略那一行,实测报 unknown option 'bogusdirective' -- ignoring line,其余行照常生效。
  • 现象:整条被跳过并提示父目录权限不安全。原因:日志所在目录权限过松会被拒。这条来自 ArchWiki 的排错条目,本机未复现 [3]。
  • 六、回滚、FAQ 与实测记录

    回滚路径:改 /etc/logrotate.d/ 下任何文件前先 cp 一份;改坏直接覆盖回去,再 logrotate -d 复查。systemd 侧的改动用 systemctl revert 撤 drop-in,实测撤完 /etc/systemd/system/logrotate.service.d 目录都不存在了。已经轮转出去的日志想合回去,可以 cat app.log.1 >> app.log 再删掉 .1,这属于手工合并,本文没有对它做额外校验。

    FAQ:logrotate 每天几点跑?按 Ubuntu 的 timer 是每天一次、允许一小时误差,错过的开机补跑,实测本机触发点是 19:30。日志涨得比一天快怎么办?用 daily 加 maxsize,同时把 timer 的 OnCalendar 改密一点,只改配置里的 maxsize 而 timer 不动是没用的。nginx 切完日志还在写旧文件怎么办?用 create 加 postrotate 发重开信号,别用 copytruncate 凑合。旧日志占空间怎么办?Ubuntu 默认没开 compress,加上它并按需带 delaycompress。怎么确认轮转真的跑过?看 /var/lib/logrotate/status 里那条路径的时间戳。

    实测记录:测试机为 WSL2 Ubuntu 24.04.4 LTS,logrotate 3.21.0-2build1,logrotate 3.21.0,2026-10-29 以 root 执行。测试用的配置、日志、临时状态文件与 systemd override 全部删除,未安装或卸载任何软件包,/var/lib/logrotate/status 与 /etc/logrotate.conf 保持原状。

    局限性:以上实测仅在 WSL2 的 Ubuntu 24.04 单实例环境完成。真机断电、纯电池供电下 ConditionACPower 的实际跳过行为、真机重启后的 Persistent 补跑、RPM 系发行版(Fedora、openSUSE)的包与单元路径、以及父目录权限被拒的具体报错文案,均只核对文档口径,本机未复现。nginx、MySQL 的重开命令按各自官方用法给出,测试环境未安装这两个服务,相关小节按 rsyslog 的实测写法类比。

    状态:草稿待审。除上面局限性列出的条目外,文中命令均已在本机 WSL2 实测。


    参考资料

    文中主要命令已在 WSL Ubuntu(24.04)实测,个别条目(未装工具/安装类命令)仅核对官方文档,未实际执行。