一、小总结二、Logger对象的日志等级三、使用多个处理器和多种格式化四、日志回滚1. RotatingFileHandler2. TimedRotatingFileHandler五、RotatingHandler存在的问题
本文作为Python日志模块的补充,主要介绍日志回滚
RotatingFileHandler和TimedRotatingFileHandler的使用,以及其所带来的问题、Logger对象的日志等级是如何其作用的等内容。
一、小总结
通过前面介绍logging模块的博文【Python】—日志模块logging使用详解_ling620的专栏-CSDN博客,基本上可以正确使用日志模块。需要注意的几点如下:
- 直接使用
logging模块的接口函数,无任何其他操作,如logging.info()来进行日志的输出是默认输出到控制台,默认的日志等级是logging.WARNING,且不可以设置日志等级。 - 使用
logging模块的接口函数,内部实现是:判断root.handlers是否为空,为空则内部调用basicConfig()函数,默认创建StreamHandler。 - 使用
logging.basicConfig()函数可以满足基本使用,可以输出到文件或控制台中。内部是根据参数来创建FileHandler或StreamHandler来实现。 Logger不可以直接实例化,需要使用logging.getLogger()获取Logger对象。- 一个
logger对象可以添加多个handler对象,通过addHandler()函数来添加。 - 每个
handler对象可以有一个Formatter对象来指定格式,通过setFormatter()函数来设置。 handler和logger对象都需要设置一个日志等级,通过setLevel()函数来设置。logger的名称是一个以'.'分割的层级结构,每个'.'后面的logger都是'.'前面的logger的children。- 不必为一个应用程序中所使用的所有
loggers定义和配置handlers,只需要为一个顶层的logger配置handlers,然后按照需要创建child loggers就足够。 logger有一个 "有效等级(effective level)" 的概念如果一个logger上没有被明确设置level,那么该logger就是使用它parent的level;如果它的parent也没有明确设置level则继续向上查找parent的parent的有效level,依次类推,直到找到个一个明确设置了level的祖先为止。
二、Logger对象的日志等级
由前文已知,Logger不可以直接实例化,需要使用logging.getLogger(name)来获取Logger的对象。通过setLevel()来设置日志等级。
在测试过程中,发现了如下问题,设置了日志等级为logging.DEBUG,输出Logger对象的level属性,得到的结果是10,但仍然不输出DEBUG等级的信息,这是为什么呢?如下:
1>>> import logging
2
3>>> logger = logging.getLogger('example')
4>>> logger.level
50
6>>> logger.debug('this is a debug msg.')# 无输出
7>>> logger.warning('this is a warning msg.')
8this is a warning msg.
9>>> logger.setLevel(logging.DEBUG)
10>>> logger.level
1110
12>>> logger.debug('this is a debug msg.') # 无输出
13>>> logger.warning('this is a warning msg.')
14this is a warning msg.
为什么设置了日志等级而没有起作用呢?-_-
首先分析一下,上面的代码中获取了Logger的对象logger,但是并没有添加任何handler对象。当logging.getLogger()做了什么呢?接口函数又做了哪些?
1def getLogger(name=None):
2 """
3 Return a logger with the specified name, creating it if necessary.
4 If no name is specified, return the root logger.
5 """
6 if name:
7 return Logger.manager.getLogger(name)
8 else:
9 return root # 若未指定name,则返回root
10
11# root是什么?
12root = RootLogger(WARNING) # RootLogger的对象,日志等级为WARNING
13Logger.root = root
14Logger.manager = Manager(Logger.root)
getLogger()函数内部根据是否指定name返回对应的root logger。即Logger的初始化对象,handler,filter参数等都为None。可见默认的日志等级是WARNING。
以logger.info()接口函数为例,看看又做了些什么?
函数调用关系:
分析下
Logger.callHandlers()函数:
1def callHandlers(self, record):
2 if (found == 0):
3 if lastResort:
4 if record.levelno >= lastResort.level:
5 lastResort.handle(record)
6 elif raiseExceptions and not self.manager.emittedNoHandlerWarning:
7 sys.stderr.write("No handlers could be found for logger"
8 " \"%s\"\n" % self.name)
9 self.manager.emittedNoHandlerWarning = True
- 循环
Logger自己实例,直到获取到其祖辈,found来计数其handlers。 - 判断
found个数,如果为0,判断lastResort
这个lastResort是什么?如下:
它是_StderrHandler(WARNING)类的初始化对象,且默认传递的日志等级是WARNING,无法指定。_StderrHandler类继承自StreamHandler,使用sys.stderr类似于StreamHandler。
1_defaultLastResort = _StderrHandler(WARNING) # 注意此处是WARNING
2lastResort = _defaultLastResort
3
4class _StderrHandler(StreamHandler):
5 """
6 This class is like a StreamHandler using sys.stderr, but always uses
7 whatever sys.stderr is currently set to rather than the value of
8 sys.stderr at handler construction time.
9 """
10 def __init__(self, level=NOTSET):
11 """
12 Initialize the handler.
13 """
14 Handler.__init__(self, level)
15
16 @property
17 def stream(self):
18 return sys.stderr
看到这里已经明白了,之所以在获取Logger对象,设置日志等级后,依然没有生效的原因是,我们没有添加任何handler,程序内部默认调用指定等级为WARNING的_StderrHandler,且该日志等级无法修改(使用setLevel()不影响该handler对象的等级)
当我们手动添加了handler对象后,则会调用添加的handler对象的等级或者root Logger的等级。
当调用logging.basicConfig()函数时,内部默认创建了FileHandler或StreamHandler对象,则我们再设置setLevel()可生效。如下:
1import loging
2logging.basicConfig(level=logging.DEBUG)
3logger = logging.getLogger()
4logger.debug('this is a debug msg.')
5# 输出
6INFO:root:this is a debug msg.
三、使用多个处理器和多种格式化
日志记录器是普通的Python对象。addHandler()方法没有限制可以添加的日志处理器数量。有时候,应用程序需要将严重类的消息记录在一个文本文件,而将错误类或其他等级的消息输出在控制台中。要进行这样的设定,只需多配置几个日志处理器即可,在应用程序代码中的日志记录调用可以保持不变。
1import logging
2
3logger = logging.getLogger('simple_example')
4logger.setLevel(logging.DEBUG)
5# 创建文件handler ,其等级为debug
6fh = logging.FileHandler('example.log')
7fh.setLevel(logging.DEBUG)
8# 创建控制台handler,日志等级为ERROR
9ch = logging.StreamHandler()
10ch.setLevel(logging.ERROR)
11# 创建formatter并添加至handlers
12formatter = logging.Formatter('%(asctime)s - %(name)s - %(levelname)s - %(message)s')
13ch.setFormatter(formatter)
14fh.setFormatter(formatter)
15# 将handlers添加至logger
16logger.addHandler(ch)
17logger.addHandler(fh)
18
19# 应用代码
20logger.debug('debug message')
21logger.info('info message')
22logger.warning('warn message')
23logger.error('error message')
24logger.critical('critical message')
上述代码的结果是,控制台只输出error和critical的日志信息,而文件中则包含所有5个日志信息。
四、日志回滚
通过前面的分析,我们可以将日志信息输出到一个文件中,随着时间的流逝,日志文件会变得越来越大,如何处理这种情况?
我们希望当日志文件不断记录增长至一定大小或增长到一定时间时,打开一个新的文件接着记录。你可能希望只保留一定数量的日志文件,当不断的创建文件到达该数量时,又覆盖掉最开始的文件形成循环。 对于这种使用场景,日志包提供了 logging.hanlders.RotatingFileHandler和logging.hanlders.TimedRotatingFileHandler。
在上篇文章中讲到过:
| 类 | 描述 |
|---|---|
| logging.handlers.RotatingFileHandler | 将日志消息发送到磁盘文件,并支持日志文件按大小切割 |
| logging.hanlders.TimedRotatingFileHandler | 将日志消息发送到磁盘文件,并支持日志文件按时间切割 |
1. RotatingFileHandler
默认情况下,文件会无限增长。可以指定maxBytes和backupCount的特定值,以允许文件以预定的大小滚动。
当当前日志文件的长度接近maxBytes时,就会发生翻转。如果backupCount为>= 1,则系统将连续创建与基本文件路径名相同、但具有扩展名的新文件".1"、".2"等附于其后。
例如,如果backupCount为5,并且基本文件名为“app.log”,则会得到“app.log”、“app.log.1”、“app.log.2”,…,“app.log.5”。
被写入的文件总是“app.log”
当它被填满时,它被关闭并重命名为“app.log.1”,如果文件“app.log.1”、“app.log.2”等存在,然后将它们重命名为“app.log.2”、“app.log.3”等。
- 如果
maxBytes为零,则不会发生翻转。 - 如果想要翻转功能,则
mode='a'。
初始化函数定义:
1__init__(self, filename, mode='a', maxBytes=0, backupCount=0, encoding=None, delay=False)
初始化参数:
| 参数名 | 含义 |
|---|---|
| filename | 文件名 |
| mode | 文件模式。使用回滚功能时,设置为a |
| maxBytes | 文件大小,最大比特数,如1024*1024*1024表示一个G |
| backupCount | 文件回滚个数,如设为3,则会保留3个备份文件,一共4个日志文件 |
| encoding | 文件编码格式,如果包含中文,则使用utf-8编码 |
| delay | 构建Handler,用来设置等级,格式等项。若为True,构建Handler对象,否则构建StreamHandler对象。默认False |
2. TimedRotatingFileHandler
参数when决定了时间间隔的类型,参数interval决定了多少的时间间隔。如when=‘D’,interval=2,就是指两天的时间间隔,backupCount决定了能留几个日志文件。超过数量就会丢弃掉老的日志文件。
初始化函数定义
1__init__(self, filename, when='h', interval=1, backupCount=0, encoding=None, delay=False, utc=False, atTime=None)
初始化参数:
| 参数名 | 含义 |
|---|---|
| filename | 文件名 |
| when | 时间间隔的类型 |
| interval | 时间间隔, |
| backupCount | 文件回滚个数,如设为3,则会保留3个备份文件,一共4个日志文件 |
| encoding | 文件编码格式,如果包含中文,则使用utf-8编码 |
| delay | 构建Handler,用来设置等级,格式等项。若为True,构建Handler对象,否则构建StreamHandler对象。默认False |
| utc | UTC时区的时间 |
参数when:
| 符号 | 含义 |
|---|---|
| S | 秒 |
| M | 分钟 |
| H | 小时 |
| D | 天 |
| W | 周 |
| W0-W6 | 周一到周日 |
| midnight | 在午夜,即每天凌晨 |
示例:
1import glob
2import logging
3import logging.handlers
4
5my_logger = logging.getLogger('MyLogger')
6my_logger.setLevel(logging.DEBUG)
7
8# Add the log message handler to the logger
9handler = logging.handlers.RotatingFileHandler(
10 'logs/logging_demo.log', maxBytes=1024*1024, backupCount=5)
11
12my_logger.addHandler(handler)
得到的日志文件如下,共6个文件:
1logging_demo.log
2logging_demo.log.1
3logging_demo.log.2
4logging_demo.log.3
5logging_demo.log.4
6logging_demo.log.5
五、RotatingHandler存在的问题
Python 的logging模块提供了两个支持日志回滚的FileHandler类,分别是RotatingFileHandler和 TimedRotatingFileHandler。
logging是线程安全的,将单个进程中的多个线程日志记录至单个文件没有问题。但当有多个进程向同一个日志文件写入日志的时候,这两个RotatingHandler就会带来问题,比如日志丢失。
该部分内容涉及从多个进程记录至单个文件的方法,参考下一篇文章: