通常我们会通过日志记录程序运行时各种信息,如有异常,则通过日志分析用户行为、记录运行轨迹、查找程序问题。但是,磁盘空间有限,为了节省空间和方便整理,可以将日志文件按时间或大小分成多份,删除时间久远的日志文件。
这就是通常说的日志滚动 (log rotation),在本文中,就简单介绍下 Linux 中的 logrotate 程序。
简介
logrotate 是默认安装的,一般会定时运行 logrotate,通常是每天一次;会通过 crontab 运行,也就是直接运行 /etc/cron.daily/logrotate
脚本。
其中,CentOS 系统中的脚本内容如下。
#!/bin/sh
/usr/sbin/logrotate -s /var/lib/logrotate/logrotate.status /etc/logrotate.conf
EXITVALUE=$?
if [ $EXITVALUE != 0 ]; then
/usr/bin/logger -t logrotate "ALERT exited abnormally with [$EXITVALUE]"
fi
exit 0
具体执行时间可以查看 /etc/crontab
或者 /etc/anacrontab
(CentOS)。
配置文件
默认使用 /etc/logrotate.conf
文件,作为全局配置,而不同应用的具体配置则在 /etc/logrotate.d
目录下,通常以应用程序的名称命名,例如 nginx、mysql、syslog、yum 等配置。
如下,是一个配置文件的具体内容。
/var/log/nginx/*.log /var/log/tomcat/*log { # 可以指定多个路径
daily # 日志轮询周期,weekly,monthly,yearly
rotate 30 # 保存30天数据,超过的则删除
size +100M # 超过100M时分割,单位K,M,G,优先级高于daily
compress # 切割后压缩,也可以为nocompress
delaycompress # 切割时对上次的日志文件进行压缩
dateext # 日志文件切割时添加日期后缀
missingok # 如果没有日志文件也不报错
notifempty # 日志为空时不进行切换,默认为ifempty
create 640 nginx nginx # 使用该模式创建日志文件
sharedscripts # 所有的文件切割之后只执行一次下面脚本
postrotate
if [ -f /var/run/nginx.pid ]; then
kill -USR1 `cat /var/run/nginx.pid`
fi
endscript
}
当配置完成后,可以通过如下方式进行测试。
----- 可直接手动执行
$ logrotate --force /etc/logrotate.d/nginx
----- 显示详细的信息;而且--debug/-d实际上不会操作具体文件(Dry Run)
$ logrotate --debug --verbose --force /etc/logrotate.d/nginx
在 CentOS 中,默认会将日志保存 /var/lib/logrotate.status
文件中,如果需要指定到其它文件,可以通过 -s/--state
参数指定。
日志处理
首先,介绍下在 Linux 系统中是如何处理文件的。
进程每打开一个文件,系统会分配一个新的文件描述符给这个文件;这个文件描述符中保存了文件的状态信息、当前文件位置以及文件的 inode 信息。对文件的操作是通过文件描述符完成,所以文件路径发生改变不会影响文件操作。
测试
可以通过 python 脚本进行简单测试,示例如下:
#!/bin/python
import time
with open("/tmp/foobar.txt", "w") as file:
while 1:
file.write("test\n")
file.flush()
time.sleep(1)
当通过 mv /tmp/{foobar.txt,test.txt}
移动文件之后,可以看到 test.txt
文件仍然在写入。原因如上所述,Linux 在执行 mv 操作时,文件描述符是不变的,只修改了文件名。
其中 logrotate 提供了两种方式,分别为 create 和 copytruncate 。
create
这也就是默认的方案,可以通过 create 命令配置文件的权限和属组设置;这个方案的思路是重命名原日志文件,创建新的日志文件。详细步骤如下:
- 重命名正在输出日志文件,因为重命名只修改目录以及文件的名称,而进程操作文件使用的是 inode,所以并不影响原程序继续输出日志。
- 创建新的日志文件,文件名和原日志文件一样,注意,此时只是文件名称一样,而 inode 编号不同,原程序输出的日志还是往原日志文件输出。
- 最后通过某些方式通知程序,重新打开日志文件;由于重新打开日志文件会用到文件路径而非 inode 编号,所以打开的是新的日志文件。
如上也就是 logrotate 的默认操作方式,也就是 mv+create 执行完之后,通知应用重新在新文件写入即可。mv+create 成本都比较低,几乎是原子操作,如果应用支持重新打开日志文件,如 syslog, nginx, mysql 等,那么这是最好的方式。
不过,有些程序并不支持这种方式,压根没有提供重新打开日志的接口;而如果重启应用程序,必然会降低可用性,为此引入了如下方式。
copytruncate
该方案是把正在输出的日志拷 (copy) 一份出来,再清空 (trucate) 原来的日志;详细步骤如下:
- 将当前正在输出的日志文件复制为目标文件,此时程序仍然将日志输出到原来文件中,此时,原文件名也没有变。
- 清空日志文件,原程序仍然还是输出到预案日志文件中,因为清空文件只把文件的内容删除了,而 inode 并没改变,后续日志的输出仍然写入该文件中。
如上所述,对于 copytruncate 也就是先复制一份文件,然后清空原有文件。
通常来说,清空操作比较快,但是如果日志文件太大,那么复制就会比较耗时,从而可能导致部分日志丢失。不过这种方式不需要应用程序的支持即可。
FAQ
简单列举下与 logrotate 相关的常见问题。
问题:sharedscripts的作用是什么?
如上配置中,通过通配符 '*'
指定了多个日志文件,例如 access.log
、error.log
,这时会在所有的日志文件都执行完成之后,再统一执行一次脚本;如果没有这条命令,则每个日志文件执行完成后都会执行一次脚本。
问题:rotate和maxage的区别是什么?
都是用来控制保存多少日志文件的,区别在于 rotate 是以个数为单位的,而 maxage 是以天数为单位的。如果是以天来轮转日志,那么二者的差别就不大。
参考
关于 MySQL 如何安全地处理日志文件,可以查看 Rotating MySQL Slow Logs Safely 中的相关介绍,否则可能会导致异常。