Python 日志模块logging分析及使用-2

本文做为Python日志模块的补充,主要介绍日志回滚RotatingFileHandlerTimedRotatingFileHandler的使用,以及其所带来的问题、Logger对象的日志等级是如何其做用的等内容。css

内容目录

1、小总结2、Logger对象的日志等级3、使用多个处理器和多种格式化4、日志回滚1. RotatingFileHandler2. TimedRotatingFileHandler5、RotatingHandler存在的问题6、从多个进程记录至单个文件html

1、小总结

经过前面介绍logging模块的博文Python 日志模块 logging 分析及使用 - 掘金,基本上能够正确使用日志模块。须要注意的几点以下:python

  1. 直接使用logging模块的接口函数,无任何其余操做,如logging.info()来进行日志的输出是默认输出到控制台,默认的日志等级是logging.WARNING,且不能够设置日志等级。
  2. 使用logging模块的接口函数,内部实现是:判断root.handlers是否为空,为空则内部调用basicConfig()函数,默认建立StreamHandler
  3. 使用logging.basicConfig()函数能够知足基本使用,能够输出到文件或控制台中。内部是根据参数来建立FileHandlerStreamHandler来实现。
  4. Logger不能够直接实例化,须要使用logging.getLogger()获取Logger对象。
  5. 一个logger对象能够添加多个handler对象,经过addHandler()函数来添加。
  6. 每一个handler对象能够有一个Formatter对象来指定格式,经过setFormatter()函数来设置。
  7. handlerlogger对象都须要设置一个日志等级,经过setLevel()函数来设置。
  8. logger的名称是一个以'.' 分割的层级结构,每一个'.'后面的logger都是'.'前面的loggerchildren
  9. 没必要为一个应用程序中所使用的全部loggers定义和配置handlers,只须要为一个顶层的logger配置handlers,而后按照须要建立child loggers就足够。
  10. logger有一个"有效等级(effective level)"的概念若是一个logger上没有被明确设置level,那么该logger就是使用它parentlevel;若是它的parent也没有明确设置level则继续向上查找parentparent的有效level,依次类推,直到找到个一个明确设置了level的祖先为止。

2、Logger对象的日志等级

由前文已知,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
复制代码
  1. 循环Logger本身实例,直到获取到其祖辈,found来计数其handlers
  2. 判断found个数,若是为0,判断lastResort

这个lastResort是什么?以下:
它是_StderrHandler(WARNING)类的初始化对象,且默认传递的日志等级是WARNING,没法指定。
_StderrHandler类继承自StreamHandler,使用sys.stderr相似于StreamHandlerpost

 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()函数时,内部默认建立了FileHandlerStreamHandler对象,则咱们再设置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.
复制代码

3、使用多个处理器和多种格式化

日志记录器是普通的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')
复制代码

上述代码的结果是,控制台只输出errorcritical的日志信息,而文件中则包含全部5个日志信息。

4、日志回滚

经过前面的分析,咱们能够将日志信息输出到一个文件中,随着时间的流逝,日志文件会变得愈来愈大,如何处理这种状况?

咱们但愿当日志文件不断记录增加至必定大小或增加到必定时间时,打开一个新的文件接着记录。你可能但愿只保留必定数量的日志文件,当不断的建立文件到达该数量时,又覆盖掉最开始的文件造成循环。 对于这种使用场景,日志包提供了 logging.hanlders.RotatingFileHandlerlogging.hanlders.TimedRotatingFileHandler

在上篇文章中讲到过:

描述
logging.handlers.RotatingFileHandler 将日志消息发送到磁盘文件,并支持日志文件按大小切割
logging.hanlders.TimedRotatingFileHandler 将日志消息发送到磁盘文件,并支持日志文件按时间切割

1. RotatingFileHandler

默认状况下,文件会无限增加。能够指定maxBytesbackupCount的特定值,以容许文件以预约的大小滚动。

当当前日志文件的长度接近maxBytes时,就会发生翻转。若是backupCount为>= 1,则系统将连续建立与基本文件路径名相同、但具备扩展名的新文件".1"".2"等附于其后。

例如,若是backupCount5,而且基本文件名为“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
复制代码

5、RotatingHandler存在的问题

Python 的logging模块提供了两个支持日志回滚的FileHandler类,分别是RotatingFileHandlerTimedRotatingFileHandler

logging是线程安全的,将单个进程中的多个线程日志记录至单个文件没有问题。但当有多个进程向同一个日志文件写入日志的时候,这两个RotatingHandler就会带来问题,好比日志丢失。

具体可参考如下博文:
Python TimedRotatingFileHandler 多进程环境下的问题和解决方法 - 喵酱的书架 - OSCHINA

6、从多个进程记录至单个文件

logging是线程安全的,将单个进程中的多个线程日志记录至单个文件也是受支持的;但将多个进程中的日志记录至单个文件则不受支持,由于在Python中并无在多个进程中实现对单个文件访问的序列化的标准方案。

若是你须要将多个进程中的日志记录至单个文件,有一个方案是让全部进程都将日志记录至一个 SocketHandler,而后用一个实现了套接字服务器的单独进程一边从套接字中读取一边将日志记录至文件。(若是愿意的话,你能够在一个现有进程中专门开一个线程来执行此项功能。) 这一部分文档对此方式有更详细的介绍,并包含一个可用的套接字接收器,你本身的应用能够在此基础上进行适配。

这部份内容还未细看。

相关文章
相关标签/搜索