copytruncate不可靠,因复制与截断间存在时间窗口,高频写入时日志易丢失;应改用默认重命名轮转+应用reopen机制,如Nginx通过SIGUSR1重新打开日志文件。

Logrotate 的 copytruncate 在高频写入场景下容易丢失日志,因为“复制 + 清空”之间存在时间窗口,应用可能在清空前写入新内容,导致这部分日志既不在旧文件也不在新文件中。而 nocopytruncate 本身不是 Logrotate 的内置指令——它并不存在。你真正需要的是**避免使用 copytruncate,改用基于重命名(rename)的可靠轮转方式,并配合应用自身支持日志 reopen 或信号通知机制**。
为什么 copytruncate 在高频写入时不可靠
copytruncate 的执行流程是:
- 将当前日志文件复制为归档名(如
app.log.1) - 立即将原日志文件(
app.log)截断(truncate(2)或清空)
问题在于:若应用以 O_APPEND 模式持续写入,内核会把写指针保持在文件末尾;但截断操作会让文件长度变为 0,而应用并不知道——它仍往原偏移位置写,结果可能写入空洞或被丢弃(取决于文件系统和写法)。更严重的是,复制和截断之间有微小延迟,期间新日志可能已写入,却未被复制,也未保留在原文件中,直接丢失。
正确做法:用 rename 轮转 + 应用配合 reopen
Logrotate 默认行为就是 重命名而非复制截断(即不加 copytruncate)。前提是应用能响应日志文件被 rename 后的“重新打开”动作。典型方案如下:
- Logrotate 将
app.log重命名为app.log.1,再新建空的app.log - 向应用发送信号(如
SIGUSR1、SIGHUP),让它关闭旧 fd、重新打开app.log - 应用必须实现信号处理逻辑,调用
fclose()+fopen()(或等价的 open/close)
例如 Nginx 默认支持 kill -USR1 $(cat nginx.pid) 重开日志;Supervisor 管理的进程可通过 supervisorctl signal HUP programname 触发 reopen。
Logrotate 配置示例(无 copytruncate)
在 /etc/logrotate.d/myapp 中写:
/var/log/myapp/app.log {
daily
missingok
rotate 30
compress
delaycompress
notifempty
create 644 myuser mygroup
sharedscripts
postrotate
# 向应用主进程发信号,触发日志 reopen
if [ -f /var/run/myapp.pid ]; then
kill -USR1 $(cat /var/run/myapp.pid)
fi
endscript
}关键点:
– 不写 copytruncate;
– 使用 create 确保新日志文件权限正确;
– postrotate 中发信号,而非依赖应用自动检测文件变化(多数应用不监听 inotify)。
如果应用不支持 reopen 怎么办?
只能退而求其次,但需接受一定风险或改造:
-
改用 syslog 协议输出日志:让应用写到
localhost:514或/dev/log,由 rsyslog/syslog-ng 统一收集并轮转(它们原生支持原子重命名+reopen) - 用 stdbuf + tee + 命名管道(FIFO)做缓冲层:应用写 FIFO,另一端用脚本读取并按需切分写入带时间戳的文件(较复杂,适合定制化强的场景)
-
升级/修改应用代码:加入 SIGHUP 处理,或使用 logrotate 提供的
prerotate通知应用准备切换(需应用主动配合)
硬扛 copytruncate 只适用于低频、可容忍少量丢失的调试日志,生产环境应避免。


















