logrotate

 20th August 2020 at 2:19pm

日志滚动是用来避免日志文件过大吃满硬盘的情况。对于已经内置了日志滚动能力的程序(如 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 上这篇 帖子 给出了非常多的工具参考。

参考