Linux 服务器日志不再撑爆磁盘:logrotate + Python logging 日志轮转实战
一、问题从哪里来
很多人把 Python Web 应用部署到云服务器后,前几周一切正常,某天突然发现服务挂了,一查才知道磁盘被日志写满了。应用日志、访问日志、错误日志每天都在增长,没有人清理,几个月下来就是几十 GB。
这篇文章用一台普通的 Linux 云服务器,演示两种互补的做法:
- 在应用内部用 Python 标准库 logging 自带的轮转处理器控制日志大小;
- 在系统层用 logrotate 统一管理所有服务的日志,按天切分、压缩、自动删除旧文件。
二、方案一:Python logging 自带轮转
Python 标准库里有两个现成的处理器:
- RotatingFileHandler:按文件大小轮转;
- TimedRotatingFileHandler:按时间轮转(每天、每小时等)。
按天轮转、保留 14 天的写法如下(入口文件 app 中):
import os
import logging
from logging.handlers import TimedRotatingFileHandler
handler = TimedRotatingFileHandler(
filename=os.environ.get("LOG_FILE", "/var/log/myapp/app_log"),
when="midnight",
interval=1,
backupCount=14,
encoding="utf-8",
)
handler.setFormatter(logging.Formatter(
"%(asctime)s %(levelname)s %(name)s %(message)s"
))
logger = logging.getLogger("myapp")
logger.setLevel(logging.INFO)
logger.addHandler(handler)
logger.info("service started")
要点:
- when 设为 midnight 表示每天零点切分;
- backupCount 决定保留多少份历史文件,超过的自动删除;
- 一定要指定 encoding,否则中文日志在某些环境下会乱码。
这个方案的局限:如果用 Gunicorn 起了多个 worker 进程,多个进程同时写同一个文件并各自轮转,会出现日志丢失或文件互相覆盖。多进程场景更推荐把日志交给系统层统一处理。
三、方案二:用 logrotate 统一管理
大部分 Linux 发行版自带 logrotate,并由系统定时任务每天执行一次。我们只需要在其配置目录下为应用新建一份配置。
- 在 /etc 下的 logrotate 配置目录里新建一个名为 myapp 的配置文件,内容如下:
/var/log/myapp/* {
daily
rotate 14
missingok
notifempty
compress
delaycompress
dateext
copytruncate
create 0640 www-data www-data
}
逐行解释:
- daily:每天轮转一次,也可以用 weekly、monthly;
- rotate 14:保留 14 份,更旧的直接删除;
- missingok:日志文件不存在时不报错;
- notifempty:文件为空时不轮转;
- compress / delaycompress:旧日志用 gzip 压缩,但最近一份暂不压缩,方便排查;
- dateext:历史文件名带日期后缀,比数字后缀更好找;
- copytruncate:先复制再清空原文件,应用不需要重新打开文件句柄;
- create:指定新文件的权限和属主。
- 先做一次“演练”,确认配置没写错:
sudo logrotate -d 配置文件路径
-d 是调试模式,只打印将要执行的动作,不会真的改文件。
- 强制执行一次,看效果:
sudo logrotate -f 配置文件路径
ls -lh /var/log/myapp/
能看到多出一份带日期后缀的文件,原日志被清空,说明配置生效。
四、copytruncate 和 postrotate 怎么选
copytruncate 简单省事,但在“复制”和“清空”之间的极短时间里写入的日志可能丢失。对日志完整性要求高的服务,可以改用让应用重新打开日志文件的方式:
/var/log/myapp/* {
daily
rotate 14
compress
delaycompress
dateext
sharedscripts
postrotate
systemctl kill -s USR1 myapp
endscript
}
Gunicorn 收到 USR1 信号后会重新打开日志文件,不会中断服务。Nginx 也有类似机制,它自带的 logrotate 配置就是这么做的。
五、顺手检查一下 systemd 日志
如果服务是用 systemd 托管、日志走 journald 的,还要限制 journal 的总大小,否则它同样会慢慢吃掉磁盘。
查看当前占用:
journalctl --disk-usage
在 journald 的配置文件(/etc/systemd 目录下的 journald 配置)中设置上限,例如:
SystemMaxUse=500M
MaxRetentionSec=2week
然后重启 journald:
sudo systemctl restart systemd-journald
也可以一次性清理:
sudo journalctl --vacuum-size=500M
六、最后加一道保险:磁盘告警
日志轮转之外,建议再加一个简单的磁盘检查脚本,用系统定时任务每小时跑一次,使用率超过 85% 就写一条告警日志或发通知:
#!/bin/bash
USAGE=$(df / | awk 'NR==2 {print $5}' | tr -d '%')
if [ "$USAGE" -gt 85 ]; then
logger -t disk-check "root disk usage ${USAGE}%"
fi
在云服务器上,也可以直接使用云监控服务配置磁盘使用率告警,更省心。
七、总结
- 单进程小应用:Python logging 的 TimedRotatingFileHandler 就够用;
- 多进程或多服务:交给 logrotate 统一管理,配合 postrotate 信号或 copytruncate;
- 别忘了 journald 也要设上限;
- 再加一道磁盘告警,问题出现前就能发现。
把这几步做完,服务器就不会再因为日志撑爆磁盘而半夜宕机了。
- 点赞
- 收藏
- 关注作者
评论(0)