logrotate copytruncate 模式滚动切割日志
问题
最近我们遇到了一个问题,我们有的Job是常驻的,输出的日志是固定为了,例如如下命令(once.sh 是一个死循环的脚本),这样所有的命令都要输出日志到 notice_wechat.log
{ /bin/sh jobs/bin/once.sh notice/wechat ;} >> /data/logs/jobs/notice_wechat.log 2>&1
如果使用默认的logrotate【logrotate基础传送门】的配置,就会重命名然后生产一个新文件,例如 notice_wechat.log_20221020 。但是进程是常驻的,切割之后日志会输出到 notice_wechat.log_20221020,不会到notice_wechat.log。
那我们如何解决这个问题?这就要从logrotate的两种配置方案说起了
配置方案
方案一:默认方案create
默认方案没有名字,姑且叫它create吧。因为这个方案会创建一个新的日志文件给程序输出日志
这个方案的思路是重命名原日志文件,创建新的日志文件。详细步骤如下:
重命名程序当前正在输出日志的程序。因为重命名只会修改目录文件的内容,而进程操作文件靠的是inode编号,所以并不影响程序继续输出日志。
创建新的日志文件,文件名和原来日志文件一样。虽然新的日志文件和原来日志文件的名字一样,但是inode编号不一样,所以程序输出的日志还是往原日志文件输出。
通过某些方式通知程序,重新打开日志文件。程序重新打开日志文件,靠的是文件路径而不是inode编号,所以打开的是新的日志文件。
什么方式通知程序我重新打开日志呢,简单粗暴的方法是杀死进程重新打开。很多场景这种作法会影响在线的服务,于是有些程序提供了重新打开日志的接口,比如可以通过信号通知nginx。各种IPC方式都可以,前提是程序自身要支持这个功能。
有个地方值得一提,一个程序可能输出了多个需要滚动的日志文件。每滚动一个就通知程序重新打开所有日志文件不太划得来。有个sharedscripts的参数,让程序把所有日志都重命名了以后,只通知一次。
方案二:copytruncate
如果程序不支持重新打开日志的功能,又不能粗暴地重启程序,怎么滚动日志呢?copytruncate的方案出场了。
这个方案的思路是把正在输出的日志拷(copy)一份出来,再清空(trucate)原来的日志。详细步骤如下:
拷贝程序当前正在输出的日志文件,保存文件名为滚动结果文件名。这期间程序照常输出日志到原来的文件中,原来的文件名也没有变。
清空程序正在输出的日志文件。清空后程序输出的日志还是输出到这个日志文件中,因为清空文件只是把文件的内容删除了,文件的inode编号并没有发生变化,变化的是元信息中文件内容的信息。
结果上看,旧的日志内容存在滚动的文件里,新的日志输出到空的文件里。实现了日志的滚动。但是在操作的这段时间内可能会丢日志的。