登录
推荐 文章 Go 技术 课程 下载 专题 AI
首页 >  文章 >  linux

Linux logrotate rotate 后应用仍写旧文件怎么办

来源:17golang原创

时间:2026-09-11 16:11:00 285浏览 收藏

执行 logrotate -f 后,如果新建的 app.log 一直是空的,而 app.log.1 还在变大,通常不是 rotate 没有执行,而是应用进程仍握着轮替前文件的文件描述符。路径被改名后,已经打开的描述符不会自动跟随新路径。

先查进程的 /proc//fd,确认它写的是带有 deleted 或旧后缀的 inode;能安全重载就用 postrotate 让应用重新打开日志,无法重载时才考虑 copytruncate
要点速览
  • rename 只改变目录项,进程已打开的文件描述符仍指向原 inode。
  • 优先使用应用支持的 reload、HUP 或专用 reopen 动作,而不是盲目重启。
  • copytruncate 兼容不能重载的程序,但复制和清空之间存在极短的数据窗口。

为什么 rotate 后应用还写旧文件

普通轮替大致分成三个动作:把当前日志改名、按原路径创建新文件、执行 postrotate。logrotate 手册明确说明,新文件会在 postrotate 之前创建。对应用来说,打开日志时拿到的是一个文件描述符和 inode,不是每次写入都重新按字符串路径查找文件。因此,旧描述符仍可能落到 app.log.1,新建的 app.log 则无人写入。

这也解释了“目录里明明有新文件,应用却写旧文件”的现象。不要先把问题归咎于权限;如果旧文件的大小持续变化,第一判断应是应用没有重新打开日志。

Linux logrotate 轮替后旧 inode、应用文件描述符和新日志路径的静态关系
图1:查看目录项、旧 inode 与应用文件描述符的关系,理解 rotate 后写入目标为何不会自动切换。

先用文件描述符确认是不是旧 inode

先找出实际写日志的进程,再查看它的打开文件。下面的命令只读系统状态,不会触碰日志内容:

# 找出应用主进程,并列出它打开的日志描述符
pid=$(pgrep -xo myapp)  # 替换为实际进程名,-x 避免匹配相似名称
ls -l "/proc/$pid/fd" | grep '/var/log/myapp/app.log'  # 查看描述符最终指向

# 也可以按 inode 对比当前文件与轮替文件
stat -c '%i %s %n' /var/log/myapp/app.log /var/log/myapp/app.log.1  # 输出 inode、大小和路径

如果 ls 结果显示描述符指向 app.log.1,或出现“已删除”的旧路径,就说明应用还没有 reopen。Linux 内核文档把 /proc//fd 定义为进程当前打开文件的符号链接;这比只看文件名更接近真实写入目标。若找不到日志描述符,再检查应用是否经由 journald、syslog 或容器标准输出记录,不能把所有“旧文件”都归为 logrotate。

能重载应用时用 postrotate 让它重新打开

应用支持日志重开时,推荐让它完成一次无损或低影响的 reload。常见做法是在轮替完成后调用服务自己的重载命令:

/var/log/myapp/app.log {
    daily                 # 按天轮替,实际周期按业务吞吐调整
    rotate 14             # 保留 14 个归档文件
    compress              # 轮替后的文件再压缩
    missingok             # 文件暂时不存在时不让整轮任务失败
    notifempty            # 空文件不触发轮替
    create 0640 app app   # 用原路径创建新文件并设置属主
    postrotate
        systemctl reload myapp  # 仅使用该服务已实现的 reload 动作
    endscript
}

systemctl reload 是服务级操作,是否真的重新打开日志取决于 unit 的 ExecReload 和应用实现;它不等同于 systemctl daemon-reload。如果应用文档明确要求发送 HUP,也可以在 postrotate 中使用目标主进程的 HUP,但不要把 HUP 当作所有程序通用的 reopen 信号。多日志共用一个服务时再评估 sharedscripts,避免同一轮重复触发重载。

Linux logrotate postrotate 与应用 reload 重新打开日志文件的静态模块关系
图2:把 logrotate、postrotate、服务重载、应用日志句柄和新日志文件放在同一静态关系中,检查配置责任边界。

不能重载时用 copytruncate 但接受窗口风险

如果程序没有 reopen 能力,或者重载会造成不可接受的中断,可以使用 copytruncate。它先复制当前内容,再把原文件原地截断为零;应用继续持有的描述符仍对应同一个 inode,所以后续写入会回到原路径。

/var/log/legacy/app.log {
    weekly                    # 低频写入的遗留程序可按周轮替
    rotate 8                  # 保留八份归档
    copytruncate              # 复制后原地清空,避免依赖应用 reopen
    compress                  # 归档文件压缩保存
    delaycompress             # 把刚轮替的文件留到下一轮再压缩
    missingok                 # 兼容程序偶尔不创建日志的情况
}

这个方案的代价不能省略:复制与截断之间存在很小的时间片,恰好写入的内容可能没有进入归档;高并发、大日志或不能接受任何丢行的审计日志不宜默认采用它。能改应用时,增加显式 reopen 或把日志交给 journald、syslog 等专门收集器通常更稳。

场景优先选择需要接受的边界
应用有可靠的日志重载postrotate + 服务 reloadreload 必须确实关闭并重新打开日志
应用不能被通知 reopencopytruncate复制与截断之间可能丢少量写入
日志由 journald/syslog 接管按收集器的轮替策略处理不要再对错误的文件路径重复轮替

验证新文件与归档文件都在增长

修完配置后先用调试模式确认匹配到正确的文件,再强制轮替一次。调试输出只用于查看计划,不会真正改文件:

# 先确认规则匹配,-d 只打印计划,不执行轮替
logrotate -d /etc/logrotate.conf  # 检查路径、周期和 postrotate 是否被读取

# 确认无误后再在维护窗口强制轮替
logrotate -f /etc/logrotate.conf  # 生产环境应先确认状态文件和权限

# 轮替后再次查看当前文件的 inode、大小和最近修改时间
stat -c '%i %s %y %n' /var/log/myapp/app.log /var/log/myapp/app.log.1  # 对比新旧文件
tail -n 5 /var/log/myapp/app.log  # 只抽查新文件是否出现新的日志行

使用 reopen 路径时,新的 app.log 应出现新的 inode,应用的 /proc//fd 应重新指向它;使用 copytruncate 时,inode 通常保持不变,但文件会先降到较小体积后继续增长。若两者都没有发生,回到三项基础检查:规则是否被主配置 include、运行 logrotate 的用户是否有权限、应用实际日志出口是否真的是这个文件。

常见问题

只加 create 为什么不能解决旧文件问题?

create 只负责按原路径建立新文件,不会替应用关闭旧描述符;仍需 reload、HUP 或 copytruncate。

postrotate 里应该用 reload 还是 restart?

优先用应用官方支持且能重新打开日志的 reload;没有可靠 reload 行为时才评估 restart,并把中断、连接和状态恢复纳入维护窗口。

看到 deleted 就一定是 logrotate 配置错了吗?

不一定。deleted 说明目录项已被移除但进程仍持有描述符,常见于轮替,也可能来自应用自身的清理逻辑;应结合轮替时间和配置一起判断。

排查这类问题的关键不是反复执行 logrotate -f,而是确认“谁持有 inode、谁负责 reopen”。先用文件描述符拿到证据,再按应用能力选择 postrotatecopytruncate,最后用新旧文件的 inode 和增长情况收口。

声明:本文转载于:17golang原创 如有侵犯,请联系study_golang@163.com删除
相关阅读
更多>
最新阅读
更多>
课程推荐
更多>