日志滚动是用来避免日志文件过大吃满硬盘的情况。对于已经内置了日志滚动能力的程序(如 Django 的 logging 可以做滚动),不在此文讨论范围内。对于没有内建日志滚动的程序,可能有这几个场景需要做日志滚动:
- 对于常驻的、不停打日志的程序
- 对于非常驻的程序
- 将标准输出重定向到文件并需要日志滚动
对于常驻的程序,如果想做日志滚动,需要程序支持重新打开日志文件,以便在老文件做完操作后,程序可以在新文件打日志。比如 Gunicorn 就支持接收 USR1 信号来 重新打开日志。
除了标准输出的场景,其他两个可以 logrotate 工具来做。
logrotate
Linux 上有个内置的工具,logrotate,可以用来做 log rotation。man logrotate 可以看文档。这个是 1990 年就存在的项目,至今还在更新。
logrotate 需要你提供一个配置文件,指导它怎么做 rotation。这个配置文件语法古老,多看看 manpage 再下手。
sharedscripts
sharedscripts 有用,比如你的 Gunicorn 会有两个文件 access.log, error.log。你这样写:
/path/to/access.log /path/to/error.log {
    rotate 5
    daily
    sharedscripts
    postrotate
        /bin/kill -USR1 `cat /path/to/gunicorn.pid`
    endscript
}此时就算 access.log, error.log 同时被 rotate,也只会调用一次 kill 命令。不带 sharedscripts 则会被调用两次。
运行方式
命令行:
logrotate -s <path-to-logrotate.status> <path-to-logrotate.conf>可以搭配 crontab 或者 Systemd Timer。
内部原理
logrotate 在每一次运行时,会将它将要做 rotate 的文件时间,写入 /var/lib/logrotate.status (默认位置)文件中,如:
logrotate state -- version 2
"/usr/local/services/radio_api_server-1.0/log/access.log" 2018-1-3
"/usr/local/services/radio_api_server-1.0/log/error.log" 2018-1-3如果你的文件第一次被 logrotate 遇到,那么除非 size 达到要求被 rotate,否则用 daily, weekly, monthly 这些是不会做 rotate 的,只会将这个文件的信息纪录进 logrotate.status 中。logrotate 在判断时,只看这个 status 文件,并不在乎文件本身的 Access Time / Modified Time 等等。
这导致你写 crontab 时要注意时间。比如你想做一个 daily 的 rotation,写的要求每天 23:59 分执行。但是 crond 可能到了次日 0 点才执行。这时候 rotation 出来的文件列表,可能会少一天:
access.log-20180101.gz
access.log-20180102.gz
access.log-20180104.gz   # 0103 那天 23:59,没来得及执行 logrotate,导致备份这里的日期是 4 号的这时候,4 号 23:59 分执行的 logratote,就不会对 access.log 做 ratation 了。
对标准输出做日志滚动
需要在程序启动时将标准输出重定向到其他工具上,再打到文件上。
SuperUser 上这篇 帖子 给出了非常多的工具参考。