轮转不生效多半卡在条件上: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。
五、排错速查
AccuracySec=1h 让它晚于预期。处理:logrotate -d 看原因行,再 logrotate -f 验证。notifempty。实测带 notifempty 的空文件不轮转,不带的会生成一份 0 字节的备份。nomissingok 是默认行为 [1]。处理:显式加 missingok。su nobody nogroup 之后报 stat ... Permission denied、退出码 1。原因:logrotate 先切到该用户再访问路径,切过去之后没权限。处理:su 的属主属组要和日志及父目录匹配。unknown option 'bogusdirective' -- ignoring line,其余行照常生效。六、回滚、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 实测。
参考资料
- [1] logrotate(8) Linux manual page(logrotate 手册)
- [2] logrotate 3.21.0-2build1 — Ubuntu noble source package(Ubuntu 包页)
- [3] Logrotate – ArchWiki(ArchWiki)
- [4] logrotate upstream project on GitHub(logrotate 项目)
文中主要命令已在 WSL Ubuntu(24.04)实测,个别条目(未装工具/安装类命令)仅核对官方文档,未实际执行。