logrotate —— 日志切割与清理

运维工具手册 09-日志与监控 4.9 KB 原文

logrotate —— 日志切割与清理

分类:09 日志与监控 适用:所有 Linux,由 cron/systemd timer 每日触发 一句话:把不断变大的日志按天/按大小切开、压缩、删旧的,防止磁盘被写满。

1. 它解决什么问题

  • /var/log 涨到 100%,服务写不进日志甚至崩溃。
  • 一份 app.log 几十 GB,grep 都打不开。
  • 需要保留最近 30 天,自动删更早的。
  • 切割后要让应用重新打开日志文件(否则还在写旧 inode)。

2. 工作方式

# 由 systemd timer 触发(新系统)
systemctl list-timers | grep logrotate
systemctl status logrotate.timer

# 老系统由 cron 触发
cat /etc/cron.daily/logrotate

配置文件:

/etc/logrotate.conf              # 主配置
/etc/logrotate.d/*               # 各软件自己的配置(推荐放这里)

logrotate.conf 里的全局默认值会被 logrotate.d/ 中的配置覆盖。

3. 一个完整示例

# /etc/logrotate.d/myapp
/var/log/myapp/*.log {
    daily
    rotate 14
    size 100M
    compress
    delaycompress
    missingok
    notifempty
    create 0640 app app
    sharedscripts
    postrotate
        systemctl reload myapp >/dev/null 2>&1 || true
    endscript
}

逐项解释:

指令 作用
daily / weekly / monthly / hourly 切割周期
rotate 14 保留 14 份(含当前)
size 100M 超过 100M 也切(与周期取"或"关系)
maxsize 500M 超过立即切(不等周期)
minsize 10M 小于 10M 不切
compress gzip 压缩旧文件
delaycompress 延迟一轮再压(配合仍在写旧文件的程序)
missingok 文件不存在不报错
notifempty 空文件不切
create 0640 app app 切完新建文件并设权限属主
copytruncate 复制后清空原文件(不重建 inode)
sharedscripts 多个文件只执行一次脚本
postrotate ... endscript 切割后执行(通常让程序重开日志)
prerotate 切割前执行
dateext 用日期做后缀(app.log-20261105)而不是数字
dateformat -%Y%m%d 配合 dateext 自定义格式
olddir /var/log/old 归档到别的目录(需同文件系统)
su app app 以指定用户身份切割(父目录属主非 root 时必需)
nomail / mail 是否邮件通知

4. create vs copytruncate

方式 原理 优点 缺点
create(默认) 重命名旧文件,新建一个 不丢日志,inode 切换干净 程序必须支持重开文件(postrotate 发信号)
copytruncate 复制内容后 truncate 原文件 程序无需配合 复制与清空之间写入的日志会丢;大文件耗时长

选择原则:

  • 应用支持 SIGHUP/reopen(nginx、rsyslog、多数守护进程)→ 用 create + postrotate。
  • 应用完全不支持重开(某些自研脚本 >> 追加)→ 用 copytruncate,接受少量丢失。
# nginx(支持 USR1 重开)
postrotate
    [ -f /var/run/nginx.pid ] && kill -USR1 $(cat /var/run/nginx.pid)
endscript

# 通用 systemd 服务
postrotate
    systemctl reload myapp >/dev/null 2>&1 || true
endscript

5. 测试与执行

# 调试:只显示会做什么,不实际执行
logrotate -d /etc/logrotate.d/myapp

# 强制立即切割(忽略周期)
logrotate -f /etc/logrotate.d/myapp

# 指定状态文件(多实例隔离)
logrotate -s /var/lib/logrotate/myapp.state /etc/logrotate.d/myapp

# 详细模式
logrotate -v /etc/logrotate.d/myapp

-d 是最重要的排障手段,能看到"为什么没切"(周期未到、文件不存在、权限不足)。

查看上次执行时间:

cat /var/lib/logrotate/logrotate.status
grep myapp /var/lib/logrotate/logrotate.status

6. 实战场景

6.1 应用日志按天切,保留 30 天

/opt/app/logs/*.log {
    daily
    rotate 30
    compress
    delaycompress
    missingok
    notifempty
    copytruncate
    dateext
    dateformat -%Y%m%d
}

6.2 大流量日志按大小切(不等一天)

/var/log/nginx/*.log {
    size 500M
    rotate 10
    compress
    delaycompress
    missingok
    notifempty
    sharedscripts
    postrotate
        [ -f /var/run/nginx.pid ] && kill -USR1 $(cat /var/run/nginx.pid)
    endscript
}

6.3 systemd journal 的清理

logrotate 不管 journald,用 journalctl:

journalctl --vacuum-size=1G
journalctl --vacuum-time=14d
# 或在 /etc/systemd/journald.conf 配 SystemMaxUse=1G

6.4 切割后压缩并异地归档

/var/log/app/*.log {
    daily
    rotate 7
    compress
    olddir /var/log/app/old
    postrotate
        rsync -a /var/log/app/old/ backup@host:/archive/app/ 2>/dev/null || true
    endscript
}

olddir 必须与日志同在一个文件系统,否则 logrotate 会报错。

6.5 非 root 目录的日志

日志在 /home/app/logs 下,属主是 app:

/home/app/logs/*.log {
    daily
    rotate 7
    su app app
    create 0644 app app
    copytruncate
}

不加 su 会报 skipping ... because parent directory has insecure permissions。

7. 常见坑

  1. 只配了 daily 没配 size:日志一天涨 50G,还没到切的时候就满了。加 maxsize。
  2. copytruncate 丢日志:高写入场景必然丢一部分,能改 create 就改。
  3. postrotate 没生效:脚本里命令不存在、路径错、没加 || true 导致整个 rotate 失败。
  4. sharedscripts 遗漏:多个日志文件时 postrotate 执行多次,重复 reload。
  5. create 权限不对:新建文件属主变成 root,应用写不进去。
  6. su 缺失:日志目录属主非 root 时报 insecure permissions。
  7. rotate 0:直接删除,不保留。想保留但少占空间用 rotate 1 + compress。
  8. 状态文件不同步:手工改了系统时间或迁移机器后,logrotate.status 里的时间会导致"该切不切",-f 强制或删状态条目。
  9. -d 与 -f 别混用:-d 只调试不执行。
  10. SELinux 拦截:olddir 到非标准目录可能被拒,restorecon 或调策略。
  11. 容器内日志:Docker 的 json 日志由 daemon 自己的 max-size 管,logrotate 无效。
  12. logrotate 是"每天一次":不是实时监控,磁盘可能在两次之间被写满。关键业务配合监控告警。

8. 速查表

logrotate -d /etc/logrotate.d/x       # 调试
logrotate -f /etc/logrotate.d/x       # 强制切
logrotate -v /etc/logrotate.d/x       # 详细
cat /var/lib/logrotate/logrotate.status

# 模板
/path/*.log {
    daily
    rotate 14
    size 100M
    compress
    delaycompress
    missingok
    notifempty
    create 0640 user group
    sharedscripts
    postrotate
        systemctl reload app >/dev/null 2>&1 || true
    endscript
}

# journald
journalctl --vacuum-size=1G
journalctl --vacuum-time=14d
logrotate —— 日志切割与清理 - 知识库 - 起航社区