logrotate 速查:配置结构、copytruncate 的取舍、怎么强制测试一次
logrotate 的配置只有两层:写在块外的全局指令,和写在块内的每条日志规则。真正容易出错的是它怎么"换文件"——默认是 rename(老文件改名、再按 create 新建),另一条路是 copytruncate(复制一份、再把原文件截断)。这两条路的差别在文件描述符上:rename 之后进程手里的 fd 仍指向被改名的旧文件,copytruncate 则保持 inode 不变。选错模式,日志会"消失"或"写进 .1"。
配置结构
| 位置 | 作用 | 例子 |
|---|---|---|
| 块外(全局) | 后续所有块继承 | weekly、rotate 4、compress、create、include /etc/logrotate.d |
| 日志块 | 一条规则,可覆盖全局 | /var/log/app/*.log { ... } |
| 块内指令 | 只对本块生效 | size 100M、missingok、copytruncate、postrotate ... endscript |
/etc/logrotate.conf # 主配置,通常含 include /etc/logrotate.d
/etc/logrotate.d/<package> # 每个服务一个文件
/var/lib/logrotate.status # 状态文件,记录每个日志上次轮转时间
一份可以直接用的块:
/var/log/app/*.log {
daily
rotate 7
size 100M
compress
delaycompress
missingok
notifempty
create 0640 app app
sharedscripts
postrotate
/bin/kill -HUP $(cat /run/app.pid)
endscript
}
size 与 daily 同时写时,以最后出现的那个为准(手册明确"最后指定的选项优先")。想表达"每天检查,超过 100M 才转",用 maxsize 100M 配合 daily,而不是把 size 和 daily 并列。
强制测试一次
logrotate -d /etc/logrotate.conf # 预演:不写任何文件,只看它打算做什么
logrotate -f -v /etc/logrotate.conf # 强制轮转一次,并打印过程
logrotate -f -v -s /tmp/lr.state /tmp/lr.conf # 用独立 state 与独立配置做实验,不污染线上
-d 是 debug 模式,手册写得很明确:不做任何修改,也不更新状态文件。-f 才是真的强制执行,用于"刚加完配置需要立刻验证"或"旧日志被人手工删过"的场景。
状态文件是第二次运行没反应的常见原因。实测同一份配置不加 -f 再跑一次:
rotating pattern: /tmp/lr/app.log 1024 bytes (3 rotations)
considering log /tmp/lr/app.log
log does not need rotating (log size is below the 'size' threshold)
状态文件本身是纯文本,长这样:
logrotate state -- version 2
"/tmp/lr/app.log" 2026-11-10-6:5:24
rename 与 copytruncate 的实测对照
用一个持久 fd 的写入进程(exec 3>>file,之后只 write 不重开),两种模式下的 inode 变化:
| 模式 | app.log inode | app.log.1 inode | 写入进程的 fd 指向 |
|---|---|---|---|
| rename + create | 45797 → 45836(新文件) | 45797(老 inode) | app.log.1 |
| rename,未配 create | 文件不存在 | 45797(老 inode) | app.log.1 |
| copytruncate | 45836 → 45836(不变) | 47522(新复制的文件) | app.log |
第一行是标准 rename 行为:老 inode 变成 .1,新文件按 create 重建。第二行是没写 create 的后果——rotate 之后 app.log 直接不存在了(实测 stat 报 No such file or directory),写进程继续往 .1 里写,应用侧看起来"日志没了"。第三行是 copytruncate:inode 不变,进程无感,代价是多一次完整复制,并且复制到截断之间有一个时间窗。
四个坑
第一,copytruncate 会丢一点日志。手册原文是"复制文件与截断之间有一个非常小的时间片,因此可能丢失部分日志数据"。对审计类日志这是不可接受的,对访问日志通常可以接受。取舍标准是"这段日志能不能容忍丢几十毫秒"。
第二,copytruncate 与 create 互斥。手册明确:用 copytruncate 时 create 不起作用,因为原文件留在原地。所以从 rename 切到 copytruncate 时,块里遗留的 create 0640 app app 不会报错,也不会生效——权限变化需要另外处理。
第三,只测 -d 不测 -f。-d 只打印计划,不会真的移动文件,所以它验证不了 postrotate 脚本、验证不了权限、也验证不了"rotate 后应用还能不能继续写"。加完配置的正确验证是:先 -d 看计划,再用独立 state 跑一次 -f -v,最后确认应用仍在写新文件(ls -l 看 inode 是否变化、tail -f 是否有新行)。
第四,状态文件的锁。默认会对 state 文件加锁,拿不到锁时以退出码 3 结束(手册明确)。这在 cron 与手工同时触发时会出现,表现为"手工执行没反应"。要么用 -s 指定另一个 state 文件做实验,要么先确认没有并发的 logrotate 在跑。
日志文件被手工删掉之后
有人 rm 了正在写的日志文件,此时 df 不降(inode 还被进程持有),而 logrotate 下次运行会看到"文件不存在"。加 missingok 的块会安静跳过,不加则报错并以非零码退出(手册默认是 nomissingok,即文件不存在时报错)。恢复动作是让应用重开日志文件,然后手工跑一次 logrotate -f,让它按 create 重建并接管后续轮转。这个顺序不能反:先让应用重开,再强制轮转,否则新文件会被下一次轮转再改一次名。
同一份块里同时写 size 和 maxsize 是很常见的误配。maxsize 的意思是"即便时间条件没到,超过这个大小也转",size 则是"只按大小,不看时间间隔"。想要"每天检查、超过 100M 就提前转",正确组合是 daily + maxsize 100M;写成 daily + size 100M 会退化成纯大小触发。
小结
配置层面只需要记住两层结构和一个优先级:块内覆盖全局、后出现的覆盖先出现的、size 与时间条件互斥。执行层面记住 -d 预演、-f 强制、-s 隔离状态文件,三者组合就能安全地验证任何改动。
模式选择看应用能否重开文件:能(支持 SIGHUP 或重启代价小)就用 rename + create + postrotate,日志完整、无丢失窗口;不能就用 copytruncate,接受一次复制开销和极小的丢失窗口,并把 delaycompress 加上,避免压缩正在被写的 .1。
数据来源与检索时间
| 来源 | URL | 对应数据 |
|---|---|---|
| man7.org · logrotate(8) | https://man7.org/linux/man-pages/man8/logrotate.8.html | -f/-d/-s 选项语义、state 文件加锁失败退出码 3、配置文件的全局与块结构、include 与 tabooext |
| github.com/logrotate · logrotate.8.in | https://raw.githubusercontent.com/logrotate/logrotate/main/logrotate.8.in | copytruncate 的"复制与截断之间存在时间片、可能丢日志"原文、create 在 copytruncate 下不生效、create 在 postrotate 之前创建文件、size 与时间条件"最后指定优先" |
检索时间:2026-11-10(Asia/Shanghai)。
核验说明
- 真跑:
logrotate 3.21.0(Debian 12 容器内),三组实验的 inode 与大小均为原始输出——rename+create(45797→45836,.1保留 45797)、未配 create(app.log不存在、fd 指向.1)、copytruncate(45836 不变,.1为 47522 的新文件)。 - 真跑:不加
-f时因size阈值未达而输出log does not need rotating (log size is below the 'size' threshold);-d前后app.log大小不变(150→150);state 文件内容为实测。 - 未验证:
postrotate脚本的执行、su指令切换用户、olddir跨设备复制、以及 systemd 下logrotate.service的ProtectSystem=full影响,均按手册记录,未在本机构造。 - 环境限制:本次在容器内以 root 直接运行 logrotate,未经过 cron 或
logrotate.timer触发,因此"每日自动执行"这一层未验证。