不加copytruncate时,应用若未响应信号重开日志,会持续向原inode(如app.log.1)写入,导致app.log为空、轮转失效、磁盘被悄悄撑爆;此时logrotate仅改名文件路径,而应用仍持旧fd写入,造成日志“错位”与静默丢失。

logrotate不加copytruncate时,应用还在写日志会怎样
不加 copytruncate 是 logrotate 的默认行为:重命名原日志文件(如 app.log → app.log.1),再创建一个同名新文件供应用写入。但前提是——应用必须能被通知“重新打开日志文件”,否则它仍会往旧 inode 写。
常见后果包括:
-
app.log文件大小始终为 0,而实际日志持续写入app.log.1(或更老的归档文件),导致轮转失效、磁盘悄悄被撑爆 - 应用日志“消失”:运维以为日志在
app.log,结果 grep 或 tail 都查不到最新内容 - 某些程序(如旧版 MySQL 错误日志、静态链接的 C 守护进程)压根不响应
SIGHUP或SIGUSR1,postrotate里发信号完全无效 - 容器中更隐蔽:进程被 PID 1 进程托管,信号被拦截或忽略,
kill -USR1看似执行了,实则石沉大海
这本质上不是 logrotate 的 bug,而是它和应用之间缺乏协作契约。
为什么重命名后应用还在往旧文件写
Linux 中文件删除/重命名操作不影响已打开的文件描述符(fd)。只要应用没主动 fclose() + fopen(),内核就继续把数据追加到原 inode 对应的磁盘位置。
你可以验证:
ls -i /var/log/app/app.log轮转后立刻再跑一遍,如果 inode 号没变,说明应用仍在写旧文件;如果变了,说明它已成功 reopen —— 那就不需要
copytruncate。
关键点在于:logrotate 控制的是文件系统路径,而应用控制的是 fd。两者脱节,后果就是日志“错位”。
不加copytruncate却硬配了create,会发生什么
create 会让 logrotate 在重命名后新建一个空文件,权限按配置设定(如 create 0644 www-data www-data)。但如果应用不 reopen,这个新文件根本没人写,纯属占位。
更危险的是:某些程序在启动时检查日志文件是否存在、是否可写,发现 app.log 已存在且权限正确,就直接跳过初始化逻辑,后续也不 reopen —— 结果就是新文件永远空着,旧文件越写越大。
此时你会看到:
-
app.log权限正确、大小为 0 -
app.log.1持续增长,甚至比app.log.2还大(因为轮转逻辑被绕过了) -
logrotate状态文件(/var/lib/logrotate/status)记录的时间戳正常,掩盖了问题
这种静默失败最难排查。
什么时候可以放心不用copytruncate
只有当满足全部以下条件时,才建议坚持默认方案(即不加 copytruncate):
- 应用明确支持日志 reopen,且文档写了接收哪个信号(如 Nginx 支持
USR1,OpenResty 同样) - 你验证过信号确实生效:
strace -p $(pgrep -f 'nginx: master') -e trace=openat,close能捕获到 reopen 动作 - 日志写入频率不高(例如每秒几行),且可接受短暂中断(信号处理期间可能丢少量日志)
- 不在容器中运行,或容器已显式配置
signal: SIGUSR1并透传给进程
否则,别赌应用的兼容性。尤其在生产环境面对老旧服务、闭源二进制或无法修改启动脚本的场景,copytruncate 虽有风险,但至少让轮转“看得见、控得住”。
真正容易被忽略的是:它只对单个日志路径块生效,且不能和 create 共存——配错一行,整个块就退化成默认行为,问题又回去了。


















