logrotate 日志轮转:别让日志文件吃光你的磁盘
Nginx 日志、应用日志悄悄长大,半年后几十个 GB。小够讲 logrotate:按时间/按大小自动轮转、压缩、删除旧日志,配一次,磁盘永远有空间。
我是小够。磁盘满的排查里,最常见的元凶不是数据,是日志。Nginx access log、应用日志、系统日志,悄悄地长,半年不管,几十个 GB 就没了。logrotate 就是干这个的:自动轮转、压缩、删除旧日志,系统自带,配一次就行。
先看看日志有多大
du -sh /var/log/*
journalctl --disk-usage # systemd 日志占了多少
看到数字你可能吓一跳。别急着手动删,配好轮转规则让系统自己管。
logrotate 怎么工作
每天 cron 会跑一次 logrotate,它按 /etc/logrotate.conf 和 /etc/logrotate.d/ 里的规则,把日志"轮转":
access.log→ 改名为access.log.1- 新的日志继续写到
access.log - 旧的可以压缩、保留 N 个、超期的删除
写一条规则
给 Nginx 写个规则 /etc/logrotate.d/nginx-custom:
/var/log/nginx/*.log {
daily # 每天轮转
rotate 14 # 保留 14 个
compress # 压缩旧日志
delaycompress # 延迟一天再压缩(当天那个先不压)
missingok # 日志不存在也不报错
notifempty # 空日志不轮转
create 0644 www-data www-data
sharedscripts
postrotate
systemctl reload nginx > /dev/null # 轮转后让 nginx 重新打开日志文件
endscript
}
关键点:postrotate 里 reload 服务,否则服务还抱着旧文件句柄写,新日志文件永远是空的——这是最常见的配置错误。
按大小轮转
日志暴涨的服务(比如 debug 全开的应用),按时间不够,按大小:
/var/log/myapp/*.log {
size 100M # 超过 100M 就轮转
rotate 5
compress
missingok
notifempty
}
测试:别等明天
# debug 模式,看会发生什么,不真执行
sudo logrotate -d /etc/logrotate.d/nginx-custom
# 强制跑一次
sudo logrotate -f /etc/logrotate.d/nginx-custom
journald 也别忘了
systemd 的日志是另一套,配 /etc/systemd/journald.conf:
SystemMaxUse=500M # 最多占 500M
MaxRetentionSec=1month # 最多保留一个月
改完 systemctl restart systemd-journald。小够见过 journal 占掉 10G 的机器,配一行就解决。
小够的建议
- 所有写日志的服务都值得一条 logrotate 规则,尤其是你自己部署的应用
compress一定要开,文本日志压缩率极高,10G 能压到 1G- 轮转不是备份:旧日志删了就是删了,重要日志先备份再轮转
- 配合磁盘监控(磁盘用到 80% 告警),双保险
日志是运维的眼睛,但没人说眼睛要无限长大。logrotate 配好,磁盘满的原因里就少了一个。
常用服务配置示例
MySQL 慢查询日志:
/var/log/mysql/mysql-slow.log {
weekly
rotate 8
compress
missingok
notifempty
create 640 mysql adm
}
自定义应用(比如你自己写的 Python 服务):
/var/log/myapp/*.log {
daily
rotate 30
compress
missingok
notifempty
dateext # 文件名带日期:app.log-20250812
dateformat -%Y%m%d
}
dateext 让轮转后的文件名带日期,找某天的日志一目了然,比 app.log.1 好认得多。
进阶选项
- olddir:轮转后的旧日志移到别的目录,保持日志目录清爽
olddir /var/log/archive - compresscmd:默认 gzip,想用 zstd 压缩率更高:
compresscmd /usr/bin/zstd - maxsize:
daily+maxsize 500M,每天检查、超 500M 也轮转,双保险
logrotate 状态文件
/var/lib/logrotate/status 记录了每个日志上次轮转的时间。排查"为什么没轮转"时先看它:
cat /var/lib/logrotate/status | grep nginx
没生效?排查三步
logrotate -d看 debug 输出,规则有没有被加载- 看 postrotate 脚本有没有报错(比如
systemctl reload失败) - 看文件权限:logrotate 以 root 跑一般没问题,但
create指定的用户要存在
小够的运维习惯
- 所有自己部署的服务,上线第一天就写 logrotate 规则,别等磁盘报警
- 应用日志加时间戳前缀,轮转后也好追溯
- 重要业务日志:先备份到对象存储,再轮转删除
常见问题
Q:轮转后服务还在往旧文件写?
A:postrotate 里没 reload/restart 服务。或者用 copytruncate:先复制再清空原文件,不用重启服务,适合不能 reload 的应用(有极小概率丢几行日志)。
Q:能按小时轮转吗?
A:logrotate 最小粒度是天。日志量实在大,用 size 按大小轮转,或者应用自己按小时切分。
Q:Windows 上的日志怎么办? A:logrotate 是 Linux 的。Windows 服务看各自的日志设置,或者把日志接到集中的日志服务。这篇只讲 Linux。
实战:给自建应用配轮转
假设你跑了个 Node 应用,日志写到 /var/log/myapp/app.log,配一条规则:
/var/log/myapp/*.log {
daily
rotate 30
compress
delaycompress
missingok
notifempty
dateext
dateformat -%Y%m%d
sharedscripts
postrotate
systemctl reload myapp > /dev/null 2>&1 || true
endscript
}
|| true 很重要:reload 失败也别让 logrotate 报错中断。轮转是配角,别让它喧宾夺主。
Docker 容器的日志
Docker 默认的 json-file 日志驱动会无限长大,docker logs 看着爽,磁盘哭着喊停。两种治法:
- daemon.json 限大小:
{
"log-driver": "json-file",
"log-opts": {
"max-size": "50m",
"max-file": "3"
}
}
改完重启 docker,之后起的容器自动限 3 个 50M。
- 已经跑起来的容器:
truncate -s 0清空,或者重建容器时加上--log-opt。
小够见过 Docker 日志占 40G 的,docker system df 一看就知道。
轮转后的旧日志放哪
olddir 把旧日志挪到归档目录:
/var/log/nginx/*.log {
daily
rotate 14
compress
olddir /var/log/archive/nginx
create 0644 www-data www-data
}
注意:olddir 的目录要提前建好,权限对。归档目录满了同样要清,保留策略别只管一半。
小够的检查清单
新服务上线,日志这块过一遍:
- logrotate 规则写了
- postrotate 里 reload 了服务(或用了 copytruncate)
- compress 开了
- 保留个数定了(rotate N)
-
logrotate -d跑过一遍,没报错
五分钟的事,换来永远不用半夜起来删日志。
logrotate 和 journald 的分工
Linux 上现在有两套日志:
- 传统文本日志(/var/log/ 下的):logrotate 管
- journald(systemd 的):
journalctl看,journald.conf配
两套都要管,别只管一半。小够见过 logrotate 配得好好的,结果 journalctl --disk-usage 一看 8G——另一套没人管。
按应用配轮转:三个例子
Nginx(前面讲过,精简版):
/var/log/nginx/*.log {
daily
rotate 14
compress
missingok
postrotate
systemctl reload nginx > /dev/null
endscript
}
PHP-FPM 慢日志:
/var/log/php*-fpm.log {
weekly
rotate 8
compress
missingok
notifempty
}
自建 Go/Rust 应用(直接写文件的):
/var/log/myapp/*.log {
size 100M
rotate 5
compress
copytruncate
}
copytruncate 适合"不能 reload"的应用:复制一份再清空原文件,服务无感知。