本文做为Python日志模块的补充,主要介绍日志回滚
RotatingFileHandler
和TimedRotatingFileHandler
的使用,以及其所带来的问题、Logger
对象的日志等级是如何其做用的等内容。css
1、小总结2、Logger对象的日志等级3、使用多个处理器和多种格式化4、日志回滚1. RotatingFileHandler2. TimedRotatingFileHandler5、RotatingHandler存在的问题6、从多个进程记录至单个文件html
经过前面介绍logging
模块的博文Python 日志模块 logging 分析及使用 - 掘金,基本上能够正确使用日志模块。须要注意的几点以下:python
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
不能够直接实例化,须要使用logging.getLogger(name)
来获取Logger
的对象。经过setLevel()
来设置日志等级。web
在测试过程当中,发现了以下问题,设置了日志等级为logging.DEBUG
,输出Logger
对象的level
属性,获得的结果是10
,但仍然不输出DEBUG
等级的信息,这是为何呢?以下:c#
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()
接口函数为例,看看又作了些什么?app
函数调用关系:Logger.info()
调用self._log(INFO, msg, args, **kwargs)
调用Logger._log()
调用self.handle(record)
调用self.callHandlers(record)
。
函数
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
。post
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 | 将日志消息发送到磁盘文件,并支持日志文件按时间切割 |
默认状况下,文件会无限增加。能够指定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 |
参数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
复制代码
Python 的logging
模块提供了两个支持日志回滚的FileHandler
类,分别是RotatingFileHandler
和 TimedRotatingFileHandler
。
logging
是线程安全的,将单个进程中的多个线程日志记录至单个文件没有问题。但当有多个进程向同一个日志文件写入日志的时候,这两个RotatingHandler
就会带来问题,好比日志丢失。
具体可参考如下博文:
Python TimedRotatingFileHandler 多进程环境下的问题和解决方法 - 喵酱的书架 - OSCHINA
logging
是线程安全的,将单个进程中的多个线程日志记录至单个文件也是受支持的;但将多个进程中的日志记录至单个文件则不受支持,由于在Python
中并无在多个进程中实现对单个文件访问的序列化的标准方案。
若是你须要将多个进程中的日志记录至单个文件,有一个方案是让全部进程都将日志记录至一个 SocketHandler
,而后用一个实现了套接字服务器的单独进程一边从套接字中读取一边将日志记录至文件。(若是愿意的话,你能够在一个现有进程中专门开一个线程来执行此项功能。) 这一部分文档对此方式有更详细的介绍,并包含一个可用的套接字接收器,你本身的应用能够在此基础上进行适配。
这部份内容还未细看。