当前位置:首页 > 文章列表 > 文章 > linux > Linux logrotate rotate 后应用仍写旧文件怎么办

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

来源:17golang原创 2026-09-11 16:11:00 0浏览 收藏

执行 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删除
Go time.ParseInLocation Parse 和 ParseInLocation 读取同一文本为何不同Go time.ParseInLocation Parse 和 ParseInLocation 读取同一文本为何不同
上一篇
Go time.ParseInLocation Parse 和 ParseInLocation 读取同一文本为何不同
Go http.Transport 禁用 KeepAlives 后为什么吞吐下降
下一篇
Go http.Transport 禁用 KeepAlives 后为什么吞吐下降
查看更多
最新文章
查看更多
课程推荐
  • 前端进阶之JavaScript设计模式
    前端进阶之JavaScript设计模式
    设计模式是开发人员在软件开发过程中面临一般问题时的解决方案,代表了最佳的实践。本课程的主打内容包括JS常见设计模式以及具体应用场景,打造一站式知识长龙服务,适合有JS基础的同学学习。
    543次学习
  • GO语言核心编程课程
    GO语言核心编程课程
    本课程采用真实案例,全面具体可落地,从理论到实践,一步一步将GO核心编程技术、编程思想、底层实现融会贯通,使学习者贴近时代脉搏,做IT互联网时代的弄潮儿。
    516次学习
  • 简单聊聊mysql8与网络通信
    简单聊聊mysql8与网络通信
    如有问题加微信:Le-studyg;在课程中,我们将首先介绍MySQL8的新特性,包括性能优化、安全增强、新数据类型等,帮助学生快速熟悉MySQL8的最新功能。接着,我们将深入解析MySQL的网络通信机制,包括协议、连接管理、数据传输等,让
    500次学习
  • JavaScript正则表达式基础与实战
    JavaScript正则表达式基础与实战
    在任何一门编程语言中,正则表达式,都是一项重要的知识,它提供了高效的字符串匹配与捕获机制,可以极大的简化程序设计。
    487次学习
  • 从零制作响应式网站—Grid布局
    从零制作响应式网站—Grid布局
    本系列教程将展示从零制作一个假想的网络科技公司官网,分为导航,轮播,关于我们,成功案例,服务流程,团队介绍,数据部分,公司动态,底部信息等内容区块。网站整体采用CSSGrid布局,支持响应式,有流畅过渡和展现动画。
    485次学习
查看更多
AI推荐
  • H2O EvalGPT:开源LLM大模型评估与排行榜工具
    H2O EvalGPT
    H2O EvalGPT是H2O.ai推出的开源LLM评估平台,提供详细的大模型性能排行榜、行业特定基准测试及A/B测试功能,助您快速选择最适合项目的高性能大语言模型。
    82次使用
  • OpenCompass大模型评测体系详解:功能、使用指南与应用场景
    OpenCompass
    OpenCompass是上海AI实验室推出的开源大模型评测平台,提供CompassKit、CompassHub和CompassRank三大核心组件,支持LLM及多模态模型的一站式标准化评估与排行榜查询。
    11次使用
  • SuperCLUE中文大模型评测基准:功能、能力维度与应用指南
    SuperCLUE
    SuperCLUE是权威的中文大语言模型综合评测基准,涵盖语言理解、知识应用、AI Agent智能体及安全性等12项核心能力。通过多轮对话与客观测试,定期发布榜单与技术报告,为模型研发、优化及行业选型提供科学依据。
    243次使用
  • C-Eval中文评测基准:大语言模型多学科能力评估指南
    C-Eval
    深入了解C-Eval中文评估套件,涵盖52个学科与4级难度。本文详解其功能特点、Zero-shot/Few-shot使用方法及代码示例,助您全面评测LLM中文理解与泛化能力。
    166次使用
  • AI Prompt Library:免费AI提示词库,助力ChatGPT高效创作与营销
    AI Prompt Library
    探索AI Prompt Library免费资源库,涵盖营销、写作及多场景AI提示词。兼容ChatGPT、Claude等工具,一键复制优化输出,提升工作效率。
    100次使用
微信登录更方便
  • 密码登录
  • 注册账号
登录即同意 用户协议隐私政策
返回登录
  • 重置密码