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

没生效?排查三步

  1. logrotate -d 看 debug 输出,规则有没有被加载
  2. 看 postrotate 脚本有没有报错(比如 systemctl reload 失败)
  3. 看文件权限: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 看着爽,磁盘哭着喊停。两种治法:

  1. daemon.json 限大小:
{
  "log-driver": "json-file",
  "log-opts": {
    "max-size": "50m",
    "max-file": "3"
  }
}

改完重启 docker,之后起的容器自动限 3 个 50M。

  1. 已经跑起来的容器: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"的应用:复制一份再清空原文件,服务无感知。