上个月就想总结写一篇关于日志切割的文章,后来跟进了一篇python logging RotatingFileHandler的日志切割实现,今天再补充一个linux logrotate的实现原理。 有兴趣看logging RotatingFileHandler的文章,附带连接 http://xiaorui.cc/?p=3114
别问我 logrotate 是啥? 一边去 ! logrotate 是个功能强大的日志切割服务,可以根据自定义的时间及文件大小进行切割打包日志。 并且logrotate也可以做一些收尾的动作。 我想关于logrotate的用法大家都很熟悉,但你们有没有关注过他的实现方法? logrotate怎么做到日志切割的情况下还不影响业务。
该文章写的有些乱,欢迎来喷 ! 另外文章后续不断更新中,请到原文地址查看更新。http://xiaorui.cc/?p=3275
简单看了下logrotate的源码,虽然logrotate.c有2500行,对于我们来说只关心logrotate是如何切割日志的. 下面copyTruncate函数其实就两个动作,一个是copy源日志到新的日志文件里,然后清空源日志文件。
static int copyTruncate(char *currLog, char *saveLog, struct stat *sb, int flags) { int fdcurr = -1, fdsave = -1; message(MESS_DEBUG, "copying %s to %s\n", currLog, saveLog); if (!debug) { if ((fdcurr = open(currLog, ((flags & LOG_FLAG_COPY) ? O_RDONLY : O_RDWR) | O_NOFOLLOW)) < 0) { message(MESS_ERROR, "error opening %s: %s\n", currLog, strerror(errno)); return 1; } 省略... ... ... if (flags & LOG_FLAG_COPYTRUNCATE) { message(MESS_DEBUG, "truncating %s\n", currLog); if (!debug) { fsync(fdsave); if (ftruncate(fdcurr, 0)) { message(MESS_ERROR, "error truncating %s: %s\n", currLog, strerror(errno)); close(fdcurr); close(fdsave); return 1; } } ... if (fdcurr >= 0) { close(fdcurr); } if (fdsave >= 0) { close(fdsave); } return 0; }
这是logrotate的源码地址, https://github.com/logrotate/logrotate/blob/master/logrotate.c
下面是我用python写的测试代码,逻辑很简单就是写日志, 不停的让日志变大变膨胀.
#xiaorui.cc import logging import requests import time def get_logger(LOGFILE): logger = logging.getLogger('mylogger') logger.setLevel(logging.INFO) fh = logging.FileHandler(LOGFILE) fh.setLevel(logging.DEBUG) formatter = logging.Formatter('%(asctime)s - %(name)s - %(levelname)s - %(message)s') fh.setFormatter(formatter) logger.addHandler(fh) return logger logger = get_logger('debug.log') r = requests.get("http://xiaorui.cc") while 1: logger.info(r.content) time.sleep(0.1)
首先我们要知道在linux下所有的文件都有inode号,一个inode号也只能对应一个文件。
第一种方法,学名 CopyTruncate:
1. Copy entire log file 2. Truncate log file
当我们运行程序不停的写入日志,在我运行logrotate切割日志后,我们通过lsof查看进程的文件描述符,发现debug.log inode还是一个,没有被换掉。
#xiaorui.cc
[ruifengyun@wx-weixin-2 ~]ll -i -h
total 656M
11665580 -rw-rw-r-- 1 ruifengyun ruifengyun 6.7M Apr 6 18:18 debug.log
11665833 -rw-rw-r-- 1 ruifengyun ruifengyun 646M Apr 6 18:18 debug.log-20160406
[ruifengyun@wx-weixin-2 ~] sudo lsof -p 37001
python 37001 ruifengyun 3w REG 8,2 579223355 11665580 /home/ruifengyun/debug.log
[ruifengyun@wx-weixin-2 ~]$ ll -i -h
total 656M
11665580 -rw-rw-r-- 1 ruifengyun ruifengyun 6.7M Apr 6 18:18 debug.log
11665833 -rw-rw-r-- 1 ruifengyun ruifengyun 646M Apr 6 18:18 debug.log-20160406
那么他是如何切割的日志,难道是复制过去的? 你说的对! 他还真是copy复制的方式实现的日志切割,copy完成之后,会清空debug.log文件。 对于程序来说是无感知的,照样写入。
会丢失数据么?
会的,本来日志有100w行,程序的日志还在不停的写入,当我copy并别名后,再去清空源日志的时候,源日志很有可能现在是100w+了。 后面你懂得….
为什么要copy复制日志文件,而不是mv ?
copy / mv没有改变源文件的信息以及 inode 属性
第二种方法, 方法叫 send signal :
上面的方法保证了文件的inode不变化,这样我们的程序不需要有任何的变动。 其实logrotate还有比较优美的方法, 简单说send signal的实现原理是让logrotate给日志生产者发送重载信号。 在继续详细描述logrotate send signal之前,我们先看看nginx是如何处理日志切割的问题。
#xiaorui.cc 1. mv access.log access.log.0 2. kill -USR1 `cat master.nginx.pid`
nginx master处理了signal USR1信号,当nginx收到这信号后会重新创建日志对象,确保日志往新的access.log写入。
logrotate跟nginx的实现思路是一样的,但logrotate只是个日志消费者,而不是生产者,所以他做了跟上面逻辑一样的事情,针对日志做别名,然后给日志的消费者发送特定信号。 logrotate可以自定义脚本来发送信号。
postrotate指令可以自定义命令,另外如果你的服务端压根不支持日志的重载,那么你发了信号也是徒劳的。
/usr/local/nginx/logs/*.log { daily dateext compress rotate 7 sharedscripts postrotate kill -USR1 `cat /var/run/nginx.pid` endscript }
在github和stackoverflow搜到了关于Logrotate lost data的话题。 跟我的想法一样,这些老外也认为反正都是日志,丢就丢吧. 一般日志切割的crontab周期是半夜三更,这时候访问本来就不多,可以说足够规避丢日志的情况. 如果你的日志写入一直都是那么着急忙慌的,又不想丢失日志,可以用第二种的方法。
求友链 http://www.tianfeiyu.com/ 行吗?
OK