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