在 Linux 系统中,logrotate 是几乎所有发行版默认提供的日志轮转工具。但在实际使用中,很多配置项“看起来懂了”,实际行为却经常出乎意料

本文基于实际验证与行为分析,逐条解析一组典型的 logrotate 配置,重点说明:

  • 配置项真正的语义

  • logrotate 的执行模型

  • 常见的误解与反直觉行为

  • 不同选项组合后的真实效果


示例配置

/path/to/logs/*.log {
    daily
    dateyesterday
    dateext
    rotate 15
    nocopytruncate
    nocreate
    compress
    notifempty
    missingok
}

下面将围绕这份配置展开说明。


一、daily:并不是“每天 0 点轮转”

1.1 常见误解

很多人理解 daily 为:

  • logrotate 每天 00:00 执行一次

  • 或“按日志时间每天切一个文件”

这两种理解都是错误的。

1.2 logrotate 的真实执行模型

logrotate 本身不具备调度能力,它的工作流程是:

  1. cron / systemd timer 触发执行

  2. 读取状态文件 /var/lib/logrotate/status

  3. 对比:

    • 上一次轮转时间

    • 当前执行时间

  4. 判断是否满足轮转条件

对于 daily,判断条件只有一个:

当前日期 ≠ 状态文件中记录的日期

1.3 实际验证结果

  • 同一天内多次执行 logrotate

    • 只有第一次会发生轮转

    • 后续执行直接跳过

  • 状态文件不会发生变化

验证:

  • 同⼀天内多次执⾏:

image-20260104225704640

image-20260104225727548

结果:

  • 每天仅第⼀次发⽣轮转

  • 第⼆次直接跳过

状态⽂件⽆变化

image-20260104225855073

结论:

daily 是一种「按天去重的轮转条件」,而不是调度指令。

1.4 与 hourly 的对比

  • hourly 是 logrotate 支持的最小轮转频率

  • 同一小时内多次执行只会轮转一次

  • 跨小时后再次执行,才会触发新一轮轮转

验证:

  • 修改配置为hourly,第⼀次执⾏logrotate正常轮转并记录轮转时间到状态⽂件

  • 同⼀⼩时重复执⾏logrotate,提⽰已经轮转过⽆需轮转

    image-20260104230114057

  • 修改时间到下⼀⼩时,执⾏logrotate正常轮转

    image-20260104230124429


二、dateext + dateyesterday:修正文件名的时间语义

2.1 仅使用 dateext 的问题

如果 logrotate 在凌晨执行,例如:

2025-12-26 01:00

轮转结果为:

app.log-2025-12-26

但该文件内容几乎全部来自 12-25 的日志

验证:

  • 单独使⽤dateext,⽇志轮转⽂件命名为执⾏时间当天⽇期

    image-20260104230302110

文件名日期 ≠ 实际日志覆盖时间

2.2 dateyesterday 的作用

dateext
dateyesterday

启用后:

  • 文件名使用 日志实际覆盖的日期

  • 与 logrotate 执行时间解耦

image-20260104230427278

2.3 与 daily 的组合语义

daily
dateext
dateyesterday

这一组合的真实含义是:

配置项

含义

daily

轮转判断按天

dateext

文件名携带日期

dateyesterday

日期指向日志内容所属日

明确以“按自然日分析日志”为目标设计的轮转策略


三、rotate 15:不是“保留 15 天”

3.1 rotate 的真实含义

rotate 15

表示:

  • 最多保留 15 次成功轮转产生的历史文件

  • 而不是 15 天

3.2 边界行为说明

  • 如果某天日志为空:

    • notifempty 生效

    • 不发生轮转

    • 不会消耗 rotate 计数

  • 删除旧文件的时机:

    • 新轮转完成之后

    • 删除超出数量限制的最旧文件

验证:

  • 修改rotate 5

    image-20260104230646106

  • 当前已轮转5次,grafana因为空⽂件存在2个轮转⽂件,ng存在5个⽂件

    image-20260104230650829

  • 下次轮转删除了ng 20251201的⽂件,grafana还是2个轮转⽂件

    image-20260104230700413


四、nocopytruncate:选择哪种轮转机制

4.1 两种轮转模型对比

模型

行为

copytruncate

拷贝文件 → 清空原文件

rename(nocopytruncate)

原文件被 rename,写入方重新创建

copytruncate

  • 拷贝当前日志为历史文件

  • 清空原文件

  • inode 不变

  • 适合:进程持续写同一个文件名

image-20260104230735165

nocopytruncate

  • 通过 rename 完成轮转

  • 不清空正在写入的文件

  • 需要写日志的进程能自行切换文件

image-20260104230745345

4.2 使用 nocopytruncate 的典型场景

  • 日志文件名本身带时间(如按小时生成)

  • 写入进程在时间边界会自动关闭旧文件

  • 不需要保持 inode 不变


五、nocreate:logrotate 是否负责创建新文件

nocreate

含义:

  • logrotate 不负责创建新的日志文件

  • 新日志文件由业务进程自行创建

实践验证表明:

  • 即使未启用 nocreate

  • nocopytruncate 场景下

  • logrotate 也不会主动创建空日志文件

image-20260104230831103

因此在多数场景下:

nocreate 更像是责任边界的明确声明


六、compress:历史日志压缩行为

compress

行为说明:

  • 轮转完成后 立即压缩历史文件

  • 不影响当前正在写入的新日志

  • 默认使用 gzip

  • 可通过 compresscmd 指定其他压缩工具

验证:

  • gzip压缩轮转⽂件

    image-20260104230904307

  • 如果不使⽤,不压缩保留带⽇期轮转⽂件

    image-20260104230911272

    image-20260104230921973


七、notifempty / missingok:边界条件控制

7.1 notifempty

  • 空文件不轮转

  • 不记录状态

  • 避免产生无意义的历史日志

验证:

  • 使用该配置

    image-20260104231017403

    image-20260104231025296

  • 不使用该配置

    image-20260104231157399

7.2 missingok

  • 日志文件不存在时不报错

  • 避免整个 logrotate 任务失败

验证:

  • 使用该配置

    image-20260104231247028

  • 不使⽤该配置

    image-20260104231300300


八、一个非常反直觉但重要的机制

logrotate 的“首次只记录状态”行为

logrotate 对一个从未见过的日志文件

  1. 第一次执行:

    • 不轮转

    • 只在状态文件中记录时间

  2. 第二次执行:

    • 才会根据配置判断是否轮转

这意味着:

新生成的日志文件,第一次不会被轮转

这一点在日志动态生成、服务重启场景下尤为重要。

验证:

  • 当前状态⽂件

    image-20260104231417652

  • 触发服务重启让pingpong产⽣⼀个新的⽇志⽂件

    image-20260104231426253

  • 修改时间到第⼆天凌晨,触发logrotate,看到只有⼀个pingpong的⽇志被轮转,新产⽣的⼀个⽇ 志只记录状态未被轮转

    image-20260104231433831

    image-20260104231440112

    image-20260104231446689

    image-20260104231454650


总结

logrotate 的配置项本身并不复杂,但:

  • 语义依赖状态文件

  • 行为依赖执行时间

  • 多个参数组合后才形成真实效果

理解它的关键不在于“记住参数”,而在于:

理解 logrotate 是如何做判断的

希望这篇文章能帮你在设计日志轮转策略时,少踩一些已经被踩过的坑。