在python的
logging.handlers
模块中,提供了很多有用的日志处理程序对象,其中比较常用的有:
RotatingFileHandler
、
TimedRotatingFileHandler
。这两个处理程序主要是用来对输出的日志文件进行切割。
一、RotatingFileHandler 介绍
RotatingFileHandler
是 Python 自带的日志处理器之一,用于将日志写入到指定的文件中,并控制文件大小和数量,可实现日志轮转(即备份旧日志并创建新的日志)。
class logging.handlers.RotatingFileHandler(filename, mode='a', maxBytes=0, backupCount=0, encoding=None, delay=False)
RotatingFileHandler
的各参数解释:
-
filename
:文件名,指定日志文件的路径和名称; -
mode
:模式,可选值为 ‘a’ 或 ‘w’。当为 ‘a’ 时,表示以追加方式写入日志;当为 ‘w’ 时,表示以覆盖方式写入日志。默认值为 ‘a’; -
maxBytes
:单个日志文件的最大大小,单位为字节。当日志文件达到该大小时,会自动备份旧日志并创建新的日志文件。默认值为 0,表示不限制日志文件大小。; -
backupCount
:备份文件数目。当生成的日志文件数量超过该数目时,会自动删除旧的备份日志文件。默认值为 0,表示不备份; -
encoding
:日志文件的编码格式,默认为 None,表示使用系统默认编码; -
delay
:是否延时打开文件,可选值为 True 和 False。当为 True 时,表示延时打开文件,即在第一次写入日志时才打开日志文件。当为 False 时,表示在初始化时即打开日志文件。默认值为 False
默认情况下,文件无限增长。您可以指定
maxBytes
和
backupCount
的特定值,以允许文件以预定的大小滚动。
需要注意的是,如果指定了为
maxBytes
指定了
>0
的值,则
mode
指定为
w
是没有意义的,
RotatingFileHandler
初始化时会自动将
mode
强制改为
a
示例1:
import logging
from logging.handlers import RotatingFileHandler
logger = logging.getLogger('mylogger')
logger.setLevel(logging.INFO)
handler = RotatingFileHandler(filename='test.log', maxBytes=6, backupCount=3)
logger.addHandler(handler)
for i in range(10):
logger.info("Hello%d" % i)
输出4个日志文件,文件名及文件内容分别如下:
# test.log
Hello9
# test.log.1
Hello8
# test.log.2
Hello7
# test.log.3
Hello6
解释:
-
文件最新内容始终输出在
test.log
基本日志文件中; -
test.log
大小达到设定的
maxBytes
时,如果再有日志写入,则新生成一个
test.log
,并将原来的
test.log
重命名为
test.log.1
,将原来的
test.log.1
(如果有)重命名为
test.log.2
,将原来的
test.log.2
(如果有)重命名为
test.log.3
,将原来的
test.log.3
(如果有)删除(因为只备份3个)
二、TimedRotatingFileHandler 介绍
TimedRotatingFileHandler
是 Python 自带的日志处理器之一,与
RotatingFileHandler
类似,也用于将日志写入到指定的文件中,并对文件进行切割。不同的是
TimedRotatingFileHandler
可以按照时间轮转日志,例如每天或每小时生成一个新的日志文件。
class logging.handlers.TimedRotatingFileHandler(filename, when='h', interval=1, backupCount=0, encoding=None, delay=False, utc=False, atTime=None)
TimedRotatingFileHandler
的各参数的详解:
-
filename
:文件名,指定日志文件的路径和名称; -
when
:日志轮转的时间间隔,可选值为 ‘S’、‘M’、‘H’、‘D’、‘W’ 和 ‘midnight’,分别表示秒、分、时、天、周和每天的午夜;默认值为 ‘midnight’,即每天的午夜轮转,值不区分大小写; -
interval
:时间间隔的数量,默认为 1;例如,当 when=‘D’ 且 interval=7 时,表示每周轮转一次; -
backupCount
:备份文件数目;当生成的日志文件数量超过该数目时,会自动删除旧的备份日志文件;默认值为 0,表示不备份; -
encoding
:日志文件的编码格式,默认为 None,表示使用系统默认编码; -
delay
:是否延时打开文件,可选值为 True 和 False;当为 True 时,表示延时打开文件,即在第一次写入日志时才打开日志文件;当为 False 时,表示在初始化时即打开日志文件;默认值为 False; -
utc
:是否使用 UTC 时间,默认为 False,表示使用本地时间 -
atTime
:用来设置轮转时间,格式为 ‘%H:%M:%S’,默认为午夜 12 点;需要注意的是该参数仅在
when
为
W
/
midnight
时有效;
当使用基于星期的轮换时,星期一为 ‘W0’,星期二为 ‘W1’,以此类推直至星期日为 ‘W6’。 在这种情况下,传入的
interval
值不会被使用。
示例1:
日志文件备份规则
import logging
from logging.handlers import TimedRotatingFileHandler
import time
logger = logging.getLogger('mylogger')
logger.setLevel(logging.INFO)
handler = TimedRotatingFileHandler(
filename="./test.log",
when="S",
interval=3,
backupCount=5,
encoding="utf8"
)
ffmt = logging.Formatter("%(asctime)s - %(message)s")
handler.setFormatter(ffmt)
logger.addHandler(handler)
for i in range(10):
logger.info("Hello%d" % i)
logger.info(handler.suffix)
logger.info(handler.extMatch)
time.sleep(3)
输出文件:
部分文件内容:
# test.log
2023-08-17 22:06:45,635 - Hello9
2023-08-17 22:06:45,635 - %Y-%m-%d_%H-%M-%S
2023-08-17 22:06:45,635 - re.compile('^\\d{4}-\\d{2}-\\d{2}_\\d{2}-\\d{2}-\\d{2}(\\.\\w+)?$', re.ASCII)
# test.log.2023-08-17_22-06-30
2023-08-17 22:06:30,615 - Hello4
2023-08-17 22:06:30,615 - %Y-%m-%d_%H-%M-%S
2023-08-17 22:06:30,615 - re.compile('^\\d{4}-\\d{2}-\\d{2}_\\d{2}-\\d{2}-\\d{2}(\\.\\w+)?$', re.ASCII)
解释:
-
代码中,
when='S'
,
interval=3
,表示每3s备份一次文件,
backupCount=5
,表示最多保留5份旧日志数据; - 从输出结果来看,每3s备份一次数据,只保留5份旧的数据,符合预期;
-
另外,按秒切割的日志文件,备份文件是在原日志文件名后添加
.%Y-%m-%d_%H-%M-%S
的后缀,而删除旧日志文件使用的正则表达式为
re.compile('^\\d{4}-\\d{2}-\\d{2}_\\d{2}-\\d{2}-\\d{2}(\\.\\w+)?$', re.ASCII)
示例2:
轮换时间的计算则仅在轮换发生时执行
还是示例1的代码,这里做一个小改动,将最后一行的
time.sleep(3)
改为`time.sleep(2)
将已有日志文件全删除,再次运行代码。
输出文件:
部分文件内容:
# test.log
2023-08-17 22:24:16,973 - Hello8
2023-08-17 22:24:16,973 - %Y-%m-%d_%H-%M-%S
2023-08-17 22:24:16,973 - re.compile('^\\d{4}-\\d{2}-\\d{2}_\\d{2}-\\d{2}-\\d{2}(\\.\\w+)?$', re.ASCII)
2023-08-17 22:24:18,984 - Hello9
2023-08-17 22:24:18,984 - %Y-%m-%d_%H-%M-%S
2023-08-17 22:24:18,984 - re.compile('^\\d{4}-\\d{2}-\\d{2}_\\d{2}-\\d{2}-\\d{2}(\\.\\w+)?$', re.ASCII)
# test.log.2023-08-17_22-24-12
2023-08-17 22:24:12,957 - Hello6
2023-08-17 22:24:12,957 - %Y-%m-%d_%H-%M-%S
2023-08-17 22:24:12,957 - re.compile('^\\d{4}-\\d{2}-\\d{2}_\\d{2}-\\d{2}-\\d{2}(\\.\\w+)?$', re.ASCII)
2023-08-17 22:24:14,960 - Hello7
2023-08-17 22:24:14,960 - %Y-%m-%d_%H-%M-%S
2023-08-17 22:24:14,960 - re.compile('^\\d{4}-\\d{2}-\\d{2}_\\d{2}-\\d{2}-\\d{2}(\\.\\w+)?$', re.ASCII)
解释:
-
与
示例1
结果不同的是,备份的日志文件名后添加的后缀时间间隔是4,不是
interval=3
设置的间隔。这是因为:初始轮换时间的计算是在处理程序被初始化时执行的。 后续轮换时间的计算则仅在轮换发生时执行,而只有当提交输出时轮换才会发生。例如,如果设置时间间隔为“每分钟”,那并不意味着你总会看到(文件名中)带有间隔一分钟时间的日志文件;如果在应用程序执行期间,日志记录输出的生成频率高于每分钟一次,那么你可以预期看到间隔一分钟时间的日志文件。 但如果日志记录消息每五分钟才输出一次,那么你将看到的是间隔五分钟时间的日志文件。
示例3:
自定义备份文件的后缀
import logging
from logging.handlers import TimedRotatingFileHandler
import time
import re
logger = logging.getLogger('mylogger')
logger.setLevel(logging.INFO)
handler = TimedRotatingFileHandler(
filename="./test.log",
when="S",
interval=3,
backupCount=5,
encoding="utf8"
)
ffmt = logging.Formatter("%(asctime)s - %(message)s")
handler.setFormatter(ffmt)
handler.suffix = "%Y%m%d_%H%M%S"+".log"
extMatch = r"^\d{4}\d{2}\d{2}_\d{2}\d{2}\d{2}(\.\w+)?$"
handler.extMatch = re.compile(extMatch, re.ASCII)
logger.addHandler(handler)
for i in range(10):
logger.info("Hello%d" % i)
time.sleep(5)
文件输出:
解释:
-
使用
handler.suffix = "%Y%m%d_%H%M%S"+".log"
自定义备份文件的后缀,与此同时使用
extMatch = r"^\d{4}\d{2}\d{2}_\d{2}\d{2}\d{2}(\.\w+)?$"
、
handler.extMatch = re.compile(extMatch, re.ASCII)
重新定义了删除旧备份文件使用的正则表达式,以保证新后缀的旧的备份文件能正常按备份数量进行删除。
示例4:
由于
when
=
'M'
、
'H'
、
'W0'~'W6'
、
midnight
时,测试起来不太方便,我这里只解释规则,部分给出关键代码。
# when='M'时,suffix、extMatch默认值为
handler.suffix = "%Y-%m-%d_%H-%M"
extMatch = r"^\d{4}-\d{2}-\d{2}_\d{2}-\d{2}(\.\w+)?$"
handler.extMatch = re.compile(self.extMatch, re.ASCII)
# when='H'时,suffix、extMatch默认值为
handler.suffix = "%Y-%m-%d_%H"
extMatch = r"^\d{4}-\d{2}-\d{2}_\d{2}(\.\w+)?$"
handler.extMatch = re.compile(self.extMatch, re.ASCII)
# when='W'时,suffix、extMatch默认值为
handler.suffix = "%Y-%m-%d"
extMatch = r"^\d{4}-\d{2}-\d{2}(\.\w+)?$"
handler.extMatch = re.compile(self.extMatch, re.ASCII)
# when='midnight'时,suffix、extMatch默认值为
handler.suffix = "%Y-%m-%d"
extMatch = r"^\d{4}-\d{2}-\d{2}(\.\w+)?$"
handler.extMatch = re.compile(self.extMatch, re.ASCII)
-
when='M'
,
interval=1
时,表示日志按分钟进行切割,但需要注意的是下次轮换时间是:处理器初始化时间+
60s
,比如:日志处理器初始化是在
10:00:25
,那么下次备份文件的生成是在
10:01:25
,而不是
10:01:00
(当然前提是
10:01:25
这个时间有日志提交,如果这个时间没日志提交,那么备份文件的生成时间为这个时间之后日志首次提交的时间) -
when='M'
,
interval=1
时,表示日志按每小时进行切割,但同样需要注意下次轮换时间是:处理器初始化时间+
60*60s
,比如:日志处理器初始化是在
10:20:30
,那么下次备份文件生成是在
11:20:30
(当然前提是
11:20:30
这个时间有日志提交,如果这个时间没日志提交,那么备份文件的生成时间为这个时间之后日志首次提交的时间) -
when='w2'
时,如果不指定
atTime
,则意思为每周三,午夜12点进行日志轮转,即 周四 00:00:00 进行日志轮转; -
when='w3'
时,如果指定了
atTime
,如
atTime=datetime.time(22,00,00)
,表示每周四,晚上22:00:00 进行日志轮转;
from logging.handlers import TimedRotatingFileHandler
from datetime import time
# 每周五,早上 02:00:00 进行日志轮换
handler = TimedRotatingFileHandler(filename, when='w4', atTime=time(02, 20, 00))
-
when='midnight'
时,如果未指定
atTime
,则意思为每天午夜12点进行日志轮转; -
when='midnight'
时,如果指定了
atTime
,如
atTime=datetime.time(23,00,00)
,则意思为每天晚上23:00:00,进行日志轮转;
from logging.handlers import TimedRotatingFileHandler
from datetime import time
# 每天,早上 10:00:00 进行日志轮换
handler = TimedRotatingFileHandler(filename, when='midnight', atTime=time(10, 00, 00))
参考资料:
logging.handlers — 日志处理