日志 HOWTO

Author:Vinay Sajip <vinay_sajip at red-dove dot com>

基础logging教程

日志是跟踪软件运行时所发生的事件的一种方法。软件开发者在代码中调用日志函数,表明发生了特定的事件。事件由消息描述,可以包含可变的数据(即每次事件发生时都可能会不一样的数据)。事件有开发者给予的重要性;重要性也被叫做级别 或者 严重程度

什么时候使用logging

logging提供了一组便利的函数,用来做简单的日志。它们是 debug()info()warning()error()critical()要决定什么时候使用logging,见下表,它描述了常见的任务及对应的最佳工具。

你想完成的任务 完成任务的最佳工具
在控制台上显示命令行脚本或者程序的常规用法说明 print()
报告程序一般操作时所发生的事件(如监视状况或者调查错误) logging.info() (或者 logging.debug() ,非常详细的输出,用于诊断目的)
对于特定的运行时事件发出警告

在库代码中使用warnings.warn() ,表明问题是可以避免的,且客户应用应该修改代码以消除警告

使用logging.warning() 表示客户应用对此问题无能为力,但是还是应该注意该事件。

对于特定的运行时事件报告错误 抛出异常
报告对错误的抑制,而不是抛异常(长期运行的服务进程的错误处理器) 根据特定的错误和应用领域,使用合适的logging.error()logging.exception() 或者 logging.critical()

logging函数根据它们用来跟踪的事件的级别或严重程度来命名。标准级别及其适用性描述如下(以严重程度递增排序):

级别 何时使用
DEBUG 详细信息,典型地调试问题时会感兴趣。
INFO 证明事情按预期工作。
WARNING 表明发生了一些意外,或者不久的将来会发生问题(如‘磁盘满了’)。软件还是在正常工作。
ERROR 由于更严重的问题,软件已不能执行一些功能了。
CRITICAL 严重错误,表明软件已不能继续运行了。

默认级别为 WARNING,表示只有该级别及其以上的事件会被跟踪,除非另外配置了logging包。

被跟踪的事件能以不同的方式被处理。最简单的处理方法就是把他们打印到控制台。另一种常见的方法就是写入磁盘文件。

一个简单的例子

一个非常简单的例子:

import logging
logging.warning('Watch out!') # will print a message to the console
logging.info('I told you so') # will not print anything

输入这些行并运行脚本,可以看到:

WARNING:root:Watch out!

被打印到控制台。没有看到 INFO 消息是因为默认级别为 WARNING消息包含了级别和日志函数调用时所提供的对于事件的描述,即“Watch out!”。不用担心 ‘root’ 部分:随后会有解释。如果需要,可以非常灵活地格式化输出;格式化选项随后也有解释。

记录到文件

一个常见的场景是将事件记录到文件,随后再看。在新打开的Python解释器中尝试下面的代码,不要在上文中的会话后面继续:

import logging
logging.basicConfig(filename='example.log',level=logging.DEBUG)
logging.debug('This message should go to the log file')
logging.info('So should this')
logging.warning('And this, too')

打开文件,可以看到:

DEBUG:root:This message should go to the log file
INFO:root:So should this
WARNING:root:And this, too

这个例子也显示了如何设置日志级别,就好比是跟踪的阀值。在这个例子中,由于我们将阀值设置到 DEBUG,所有的消息都打印出来了。

如果你希望在命令行选项设置日志级别,像这样:

--log=INFO

并且你将--log的参数值保存在变量loglevel中,你可以这样:

getattr(logging, loglevel.upper())

得到要传给basicConfig() level参数的值。你也许希望对于用户的输入做错误检查,如下:

# assuming loglevel is bound to the string value obtained from the
# command line argument. Convert to upper case to allow the user to
# specify --log=DEBUG or --log=debug
numeric_level = getattr(logging, loglevel.upper(), None)
if not isinstance(numeric_level, int):
    raise ValueError('Invalid log level: %s' % loglevel)
logging.basicConfig(level=numeric_level, ...)

对于 basicConfig() 的调用要 早于 任何对 debug()info() 等的调用。因为它被设计为一次性的简单的配置设施,只有最先调用它才有用:在其它调用之后再调用它本质上来说不起作用。

如果多次运行上面的脚本,后续运行的消息会附加到文件example.log 如果你希望每次运新都重新开始,而不是记住先前运行的消息,你可以指明filemode参数,将上述例子做如下修改:

logging.basicConfig(filename='example.log', filemode='w', level=logging.DEBUG)

输出和以前一样,只是文件不再追加,早期运行的消息就丢失了。

从多个模块记录日志

如果程序由多个模块组成,这里是如何组织日志的例子:

# myapp.py
import logging
import mylib

def main():
    logging.basicConfig(filename='myapp.log', level=logging.INFO)
    logging.info('Started')
    mylib.do_something()
    logging.info('Finished')

if __name__ == '__main__':
    main()
# mylib.py
import logging

def do_something():
    logging.info('Doing something')

运行myapp.py,可以在myapp.log中看到:

INFO:root:Started
INFO:root:Doing something
INFO:root:Finished

这可能是你想看到的。 你可以将此推广到多个模块,使用mylib.py中的模式。注意在这种简单的使用模式中,除了查看事件描述外,你无法得知消息来自于程序的哪个部分如果你想跟踪消息的位置,你需要参考高级的日志教程

记录可变的数据

要记录可变的数据,使用格式化字符串作为事件消息,后面附加变量作为参数。例如:

import logging
logging.warning('%s before you %s', 'Look', 'leap!')

会显示:

WARNING:root:Look before you leap!

如你所见,将变量合并到事件消息中使用了老的,%风格的字符串格式化。这是为了向后兼容:logging包早于更新的格式化方式如 str.format()string.Template。这些更新的格式化方式支持的,只是超出了该教程的范围。

改变显示消息的格式

要改变显示消息的格式,你要指明你想用的格式:

import logging
logging.basicConfig(format='%(levelname)s:%(message)s', level=logging.DEBUG)
logging.debug('This message should appear on the console')
logging.info('So should this')
logging.warning('And this, too')

将会打印:

DEBUG:This message should appear on the console
INFO:So should this
WARNING:And this, too

注意早期例子中的'root'不见了。有哪些可以出现在格式化字符串中,可以参考 LogRecord 属性, 但是对于简单的使用,你只需要 levelname (严重程度), message (事件描述,包括变量)以及事件何时发生的。这个在下一节描述。

在消息中显示日期/时间

要显示事件的日期和时间,在格式化字符串中放置 ‘%(asctime)s’:

import logging
logging.basicConfig(format='%(asctime)s %(message)s')
logging.warning('is when this event was logged.')

将会打印:

2010-12-12 11:41:42,612 is when this event was logged.

默认用于显示日期/时间的格式为ISO8601(如上所示)。如果你需要更多的对于日期/时间格式的控制,提供datefmt参数给basicConfig,如下:

import logging
logging.basicConfig(format='%(asctime)s %(message)s', datefmt='%m/%d/%Y %I:%M:%S %p')
logging.warning('is when this event was logged.')

将会打印:

12/12/2010 11:46:36 AM is when this event was logged.

datefmt 参数的格式和time.strftime()所支持的格式相同。

下一步

到此基本的教程就结束了。它足已让你开始记录日志。logging提供了更多,你需要花点时间阅读后续的章节。如果准备好了,来点你最喜欢的饮料,然后继续。

如果你的需求很简单,那么在你的脚本中使用上述例子,如果碰到了问题或者对某些事情不太理解,请在comp.lang.python新闻组中发贴(http://groups.google.com/group/comp.lang.python),不久就能得到帮助。

还在这里?你可以继续阅读下面的章节,它提供了更高级的/有深度的教程。在那之后,你可以看看 日志 Cookbook

高级的日志教程

logging库采取了模块化的设计,提供了许多组件:记录器、处理器、过滤器和格式化器。

  • Logger 暴露了应用程序代码能直接使用的接口。
  • Handler将(记录器产生的)日志记录发送至合适的目的地。
  • Filter提供了更好的粒度控制,它可以决定输出哪些日志记录。
  • Formatter 指明了最终输出中日志记录的布局。

日志事件信息以LogRecord实例的方式在记录器、处理器、过滤器和格式化器之间传递。

记录日志是通过调用Logger 的实例(后面称之为 记录器/loggers)的方法来完成的。 每个实例都有名字,它们在概念上组织成一个层级式的命名空间,使用点(.)作为分隔符。例如,名为‘scan’的Logger是Logger:‘scan.text’、‘scan.html’和‘scan.pdf’的父节点。记录器名字可以任意命名,用以表示记录的信息是在应用的哪个部分所产生的。

命名记录器的好的惯例是使用模块级的记录器,在模块中使用日志时,如下命名:

logger = logging.getLogger(__name__)

这意味着记录器的名字展示了包/模块的层级,通过记录器名字就直观的知道从哪里记录的事件。

记录器层级的根节点被叫做根记录器。它是函数 debug()info()warning()error()critical()所使用的记录器,这些函数只是调用了根记录器的同名方法。这些函数和方法有相同的参数。在日志输出中,根记录器的名字被打印为'root'。

可以将日志消息记录到不同的目的地。日志包支持将日志写入文件,发送HTTP GET/POST请求,经由SMTP发送邮件,通用套接字,或者操作系统特定的日志机制,如syslog或者Windows NT的事件日志。目的地由 处理器/handler 类来处理。如果你有特殊的日志目的地,而内置的处理器类都不能处理,那么你可以创建自己的日志目的地类(处理器)。

默认对任何日志消息都没有设置目的地。你可以使用 basicConfig() 来设置目的地(如控制台或者文件),如同教程中例子所展示的那样。如果你调用函数 debug()info()warning()error()critical(),这些函数会检查目的地是否设置;如果没有设置,它们将目的地设置为控制台(sys.stderr),同时设置显示消息的默认格式,然后调用根记录器来完成消息的输出。

 basicConfig() 所设置的默认消息格式为:

severity:logger name:message

可以通过给 basicConfig()format 关键字参数传递一个格式化字符串来改变默认格式。对于构造一个格式化字符串的选项,参见Formatter Objects

日志流程

下图展示了记录器和处理器记录事件信息的流程。

../_images/logging_flow.png

记录器

Logger 对象要做三件事情。首先,它们向应用代码暴露了许多方法,这样应用可以在运行时记录消息。其次,记录器对象通过严重程度(默认的过滤设施)或者过滤器对象来决定哪些日志消息需要记录下来。第三,记录器对象将相关的日志消息传递给所有感兴趣的日志处理器。

常用的记录器对象的方法分为两类:配置和发送消息。

这些是最常用的配置方法:

你并不是总需要在你创建的记录器上调用这些方法。参见这一节的最后两段。

记录器对象配置好了后,下面的方法可以用来创建日志消息。

  • Logger.debug()Logger.info()Logger.warning()Logger.error()Logger.critical()创建带消息的日志记录,记录的级别为对应的方法名。消息实际上是个格式化字符串,可以包含如%s%d%f之类的标准字符串替换语法。其余参数为对象列表,对应于消息中的替换域。对于**kwargs,日志方法只考虑exc_info,并使用它来决定是否要记录异常信息。
  • Logger.exception()创建类似于Logger.error()的日志消息。除此以外Logger.exception()还转储调用栈。只能在异常处理器中调用该方法。
  • Logger.log()以日志级别为显式参数。这比调用上述的带级别的方便的方法要冗长一些,但是可以用来记录自定义级别的日志。

getLogger() 返回记录器实例的引用,如果提供了名字就返回对应名字的记录器,否则返回 root (根记录器)。名字是由点分隔的层级结构。用相同的名字多次调用getLogger() 返回相同的记录器对象的引用。层级结构中较低层级的记录器是较高层级的记录器的子节点。例如,对于名为 foo的记录器,记录器 foo.barfoo.bar.bazfoo.bam 都是 foo的后代。

记录器有effective level/有效级别的概念。如果一个记录器没有显式地设置级别,那它父节点的级别被用作有效级别。如果父节点也没有明确设置级别,那么检查父节点的父节点,如此反复,所有的父节点都会被搜索,直至找到一个有明确地设置级别。根记录器总是明确地设置级别(默认为WARNING)。在决定是否要处理事件时,记录器的有效级别用来判断是否要将事件传递给记录器的处理器。

子记录器可以将消息传播给父记录器的处理器。正因为如此,没有必要给应用中用到的所有的记录器都配置处理器。完全可以只给顶级的记录器配置处理器,只在必要的时候创建子记录器。(你可以通过将记录器的propagate属性设置为False来关闭传播。)

处理器

Handler/处理器对象用来将合适的日志消息分发到处理器特定的目的地(基于日志消息的级别)。Logger通过addHandler()方法给它自己添加零个或多个处理器对象。举个例子,一个应用可以将所有的日志消息发送至日志文件,所有的错误(error)及其以上的日志消息发送至标准输出,所有的严重的(critical)日志消息发送至某个电子邮箱。在这个例子中需要三个独立的处理器,每一个负责将特定级别的消息发送至特定的位置。

标准库包含了相当多的处理器类型,(参见Useful Handlers);这里教程中主要使用StreamHandlerFileHandler做例子。

应用开发者很少关心处理器的方法。使用内置处理器对象的应用开发者只关心如下的配置方法(创建自定义处理器的除外):

  • setLevel()方法和日志对象的一样,指明了将会分发日志的最低级别。为什么会有两个setLevel()方法?记录器的级别决定了消息是否要传递给处理器。每个处理器的级别决定了消息是否要分发。
  • setFormatter()为该处理器选择一个格式化器。
  • addFilter()removeFilter()给处理器添加或移除过滤器对象。

应用代码不应该实例化Handler并使用它的实例。实际上Handler类是基类,它定义了所有的处理器应该有的接口,也定义了一些默认行为,子类可以使用这些行为,或者重写它。

格式化器

格式化器对象决定了日志消息最终的顺序,结构和内容。不象logging.Handler类,应用代码可以实例化格式化类;尽管如此,如果你的应用需要特殊的行为,你更可能会子类化一个格式化器。构造函数有两个可选的参数——消息格式化字符串和日期格式化字符串。

logging.Formatter.__init__(fmt=None, datefmt=None)

如果没有消息格式化字符串,默认使用原始消息。如果没有日期格式化字符串,默认的是:

%Y-%m-%d %H:%M:%S

结尾有毫秒值。

消息格式化字符串使用%(<dictionary key>)s风格的字符串替换。可能的键参见LogRecord attributes

下面的消息格式化字符串将顺序记录人类可读的时间,消息的级别和消息的内容。

'%(asctime)s - %(levelname)s - %(message)s'

格式化器使用用户可配的函数来将日志记录的创建时间转化为元组。默认使用time.localtime()对特定的格式化器实例改变该行为,设置该实例的converter属性为一函数,该函数和time.localtime()time.gmtime()有相同的函数签名。如果想对所有的格式化器更改该行为的话,例如如果希望所有的日志时间都以GMT格式显示的话,设置格式化器类的converter属性(设置为time.gmtime)。

配置日志

程序员可以以三种方式来配置日志:

  1. 使用Python代码显式的创建记录器、处理器和格式化器,并调用上述的配置方法。
  2. 创建日志配置文件并用fileConfig()函数来读该文件。
  3. 创建一个配置信息字典并传递给dictConfig()函数。

对于最后两个选项的参考文档,参见Configuration functions下面的例子用Python代码配置了一个非常简单的记录器,一个控制台处理器和一个简单的格式化器:

import logging

# create logger
logger = logging.getLogger('simple_example')
logger.setLevel(logging.DEBUG)

# create console handler and set level to debug
ch = logging.StreamHandler()
ch.setLevel(logging.DEBUG)

# create formatter
formatter = logging.Formatter('%(asctime)s - %(name)s - %(levelname)s - %(message)s')

# add formatter to ch
ch.setFormatter(formatter)

# add ch to logger
logger.addHandler(ch)

# 'application' code
logger.debug('debug message')
logger.info('info message')
logger.warn('warn message')
logger.error('error message')
logger.critical('critical message')

在命令行运行该模块产生如下输出:

$ python simple_logging_module.py
2005-03-19 15:10:26,618 - simple_example - DEBUG - debug message
2005-03-19 15:10:26,620 - simple_example - INFO - info message
2005-03-19 15:10:26,695 - simple_example - WARNING - warn message
2005-03-19 15:10:26,697 - simple_example - ERROR - error message
2005-03-19 15:10:26,773 - simple_example - CRITICAL - critical message

下述Python模块创建的记录器、处理器和格式化器和上述的例子几乎一样,差别在于对象的名字:

import logging
import logging.config

logging.config.fileConfig('logging.conf')

# create logger
logger = logging.getLogger('simpleExample')

# 'application' code
logger.debug('debug message')
logger.info('info message')
logger.warn('warn message')
logger.error('error message')
logger.critical('critical message')

Here is the logging.conf file:

[loggers]
keys=root,simpleExample

[handlers]
keys=consoleHandler

[formatters]
keys=simpleFormatter

[logger_root]
level=DEBUG
handlers=consoleHandler

[logger_simpleExample]
level=DEBUG
handlers=consoleHandler
qualname=simpleExample
propagate=0

[handler_consoleHandler]
class=StreamHandler
level=DEBUG
formatter=simpleFormatter
args=(sys.stdout,)

[formatter_simpleFormatter]
format=%(asctime)s - %(name)s - %(levelname)s - %(message)s
datefmt=

输出和上述的例子一样:

$ python simple_logging_config.py
2005-03-19 15:38:55,977 - simpleExample - DEBUG - debug message
2005-03-19 15:38:55,979 - simpleExample - INFO - info message
2005-03-19 15:38:56,054 - simpleExample - WARNING - warn message
2005-03-19 15:38:56,055 - simpleExample - ERROR - error message
2005-03-19 15:38:56,130 - simpleExample - CRITICAL - critical message

可以发现文件配置比起Python代码来有一些好处,主要是配置和代码的隔离,同时非程序员也可以很容易的修改日志属性。

警告

fileConfig()函数有个参数disable_existing_loggers默认为True,主要是为了向后兼容。它会使fileConfig()调用之前的所有记录器被禁用,除非这些记录器或者它们的祖先显式的出现在配置之中;这也许是/不是你需要的。请参考文档以得到更多的信息,如果需要,请指定False

传递给dictConfig()的字典可以有键disable_existing_loggers,其值为布尔类型,如果没有显式的指明,其默认值为True这会导致上述的记录器禁用行为,也许不是你希望的,这种情况下将其值设为False

注意配置文件中的类名要么是相对于logging模块,要么是绝对值,可以用import机制来解析。因此即可以使用WatchedFileHandler(相对于logging模块),也可以使用mypackage.mymodule.MyHandler(定义于mypackage包,mymodule模块中,包mypackage在Python的导入路径中)。

在Python 2.7中引入了新的配置日志的方式,它使用字典来保存配置信息。它提供的功能是上述的基于文件的配置的功能的超集,推荐在新的应用和部署中使用。因为用Python的字典来保存配置信息,而你可以用不同的方法来产生字典,所以你有更多的配置选择。例如你可以使用JSON格式的配置文件,或者使用YAML格式的文件来产生配置字典。或者你可以用Python代码来构造字典,从套接字中接受它的pickle形式(Python一种序列化机制),或者任何对你的应用有意义的方式来构造字典。

这个例子和上述配置一样,使用YAML格式:

version: 1
formatters:
  simple:
    format: '%(asctime)s - %(name)s - %(levelname)s - %(message)s'
handlers:
  console:
    class: logging.StreamHandler
    level: DEBUG
    formatter: simple
    stream: ext://sys.stdout
loggers:
  simpleExample:
    level: DEBUG
    handlers: [console]
    propagate: no
root:
  level: DEBUG
  handlers: [console]

关于使用字典来配置日志,参见Configuration functions

如果没有配置会怎么样

如果没有日志配置,存在这么一种情况,需要记录事件,但是没有定义处理器。这种情况下logging包的行为取决于Python的版本。

对于Python 2.x,行为如下:

  • 如果logging.raiseExceptionsFalse(产品模式),事件被默默地丢弃。
  • 如果logging.raiseExceptionsTrue(开发模式),输出消息‘No handlers could be found for logger X.Y.Z’(输出一次)。

为库配置日志

当开发库的时候需要使用日志,你需要注意写文档记录下库是如何使用日志的——例如,使用的记录器的名字。配置日志的时候也要考虑一些情况。如果使用库的应用并不配置日志,库的代码调用了日志方法,如前所述,会在sys.stderr中输出错误消息。

因为某种原因,你不希望这个消息在缺失日志配置的情况下被打印出来,你可以给你的库的顶级记录器绑定一个什么也不做的处理器。这个避免了消息被打印出来,因为总能给库的事件找到一个处理器:它只是什么都不输出而已。如果库的使用者在应用中配置了日志,可以假定配置会添加一些处理器,只要级别合适,库代码中的日志调用就会把输出传输至这些处理器,就和正常情况一样。

什么都不做的处理器包含于logging包:NullHandler(从Python 2.7开始)。该处理器的实例可以添加到库日志命名空间中顶级的记录器(如果你希望在缺少日志配置的情况下错误消息不被输出到sys.stderr)。如果库foo使用名为‘foo.x’, ‘foo.x.y’的记录器来记录日志,那么下面的代码:

import logging
logging.getLogger('foo').addHandler(logging.NullHandler())

将会起作用。如果一个组织产生许多库,那么特定的记录器的名字可以是“orgname.foo”,而不是只是"foo"。

注意

强烈建议给你的库的记录器只添加处理器NullHandler这是因为配置处理器是使用你的库的应用开发者的特权。应用开发者了解他们的目标用户,也知道哪些处理器最适合于他们的应用:如果你暗自添加了处理器,你也许会影响开发者做单元测试,也给他们交付适合于需求的日志带来干扰。

日志级别

下表给出了日志级别的数字值。这只有在你需要定义自己的级别,且自定义级别相对于预定义值的时候才有用。如果自定义级别和预定义级别有相同的值,它会覆盖预定义值;预定义的名字就会丢失。

级别 数字值
CRITICAL 50
ERROR 40
WARNING 30
INFO 20
DEBUG 10
NOTSET 0

级别能关联到记录器,由开发者设置或通过载入一个日志配置。当调用一个记录器的日志方法时,记录器比较它的级别和方法所关联的级别。如果记录器的级别高于方法的级别,那么没有日志消息产生。这是控制日志输出详细程度的基本机制。

日志消息以LogRecord类的实例来编码。如果记录器决定记录一个事件,它会从日志消息创建一个LogRecord的实例。

日志消息受分发机制的影响,这是通过使用handlersHandler类的子类的实例来完成的。处理器使得日志消息(以LogRecord的形式)被分发到特定的位置(或一组特定的位置),这对于消息的目标用户是有用的(如最终用户,支持人员,系统管理员,开发者)。特定的LogRecord被传递给处理器。每个记录器可以有零个、一个或者多个处理器(通过LoggeraddHandler()方法)。除了记录器直接关联的处理器,记录器祖先的处理器也被用来分发消息(除非记录器的propagate标志位设为假,这时就不会将日志传递给记录器祖先的处理器)。

和记录器一样,处理器也可以有级别。处理器级别的作用和记录器级别的作用一样。如果处理器决定分发一个事件,emit()方法被调用,将消息发送至目的地。大多数用户定义的Handler子类需要重写emit()

自定义级别

可以自定义级别,但这不是必须的;预定义的级别来自于实践的经验。尽管如此,如果你确定需要自定义级别,你要格外小心;开发库的时候自定义级别是个非常糟糕的想法这是因为如果多个库的作者都自定义了级别,同时使用这些库的时候,日志输出对使用库的使用者来说将难以控制/解读,因为对于不同的库而言,一个数字可能代表不同的东西。

有用的处理器

除了基类Handler,有许多有用的子类:

  1. StreamHandler实例将消息发送至流(类文件对象)。
  2. FileHandler实例将消息发送至磁盘文件。
  3. BaseRotatingHandler是在特定点循环使用日志文件的处理器的基类。它不应该被直接实例化。使用RotatingFileHandler或者TimedRotatingFileHandler
  4. RotatingFileHandler实例将消息发送至磁盘文集,支持最大日志文件限制和循环使用日志文件。
  5. TimedRotatingFileHandler实例将消息发送至磁盘文件,以特定时间间隔循环使用日志文件。
  6. SocketHandler 实例将消息发送至TCP/IP套接字。
  7. DatagramHandler实例将消息发送至UDP套接字。
  8. SMTPHandler实例将消息发送至指定的邮箱地址。
  9. SysLogHandler实例将消息发送至Unix syslog服务进程,它可能在远端机器上。
  10. NTEventLogHandler实例将消息发送至Windows NT/2000/XP事件日志(服务)。
  11. MemoryHandler实例将消息发送至内存缓冲,在特定条件满足时会被清掉。
  12. HTTPHandler实例将消息发送至HTTP服务器,使用 GET 或者 POST 语法。
  13. WatchedFileHandler实例观察用以记录日志的文件。如果文件改变,它将关闭然后重新打开文件名。这个处理器只在类Unix系统上有用;Windows并不支持它所使用的底层机制。
  14. NullHandler实例对错误消息什么都不做。库的开发者会使用该处理器,他们希望使用日志,同时也希望在库的使用者没有配置日志的时候避免出现‘No handlers could be found for logger XXX’消息。参见Configuring Logging for a Library

出现于版本2.7:NullHandler类。

NullHandlerStreamHandlerFileHandler类定义于核心logging包。其余的处理器定义于子模块logging.handlers(也有其它的子模块logging.config,用来配置日志。)

日志消息通过Formatter类实例来格式化。它们用格式化字符串初始化,可以使用%操作符和字典。

要一次格式化多个消息,可以使用BufferingFormatter的实例。除了格式化字符串(被应用于每一个消息),还有头/尾的格式化字符串。

如果基于记录器/处理器级别的过滤不够用,可以给LoggerHandler添加Filter实例(通过addFilter()方法)。在决定是否要进一步处理消息之前,记录器和处理器都会参考过滤器的许可。如果任一过滤器返回假,消息将不会被进一步处理。

基本的Filter允许通过特定的记录器名来过滤。如果使用该功能,发往命名的记录器及其子记录器的消息允许通过过滤器,其余的被丢弃。

记录日志时会抛出的异常

logging包被设计成吞掉在产品环境中记日志时产生的异常。这是因为发生于处理记录日志时的错误——错误的配置,网络或者类似的错误——不会导致使用日志的应用过早的结束。

SystemExitKeyboardInterrupt异常不会被吞掉。发生于Handler子类emit()方法的异常,会传递给处理器的handleError()方法。

HandlerhandleError()的默认实现检查模块级的变量raiseExceptions是否有设置。如果有设置,调用栈被打印到sys.stderr如果没有设置,异常被吞掉。

注意

raiseExceptions默认值为True因为在开发期间,你是希望得到发生异常的通知的。建议在产品中将raiseExceptions设为False

将任意对象用作消息

在前面的章节和例子中假设消息为字符串。这不是唯一的可能。你可以传递任意对象作为消息,当需要转成字符串的时候,__str__()方法会被调用。事实上如果你愿意,可以完全避免转成字符串,——例如SocketHandler可以将事件pickle(Python的一种序列化方式)并发送。

优化

格式化消息参数被推迟到不得不做的时候。尽管如此,计算传递给日志方法的参数仍然有可能很昂贵,你希望能避免这样,如果记录器只是会丢弃该事件。如果是这样,可以调用isEnabledFor()方法,它的参数为级别,如果记录器对该级别会产生事件,则该方法返回真。可以像这样写代码:

if logger.isEnabledFor(logging.DEBUG):
    logger.debug('Message with %s, %s', expensive_func1(),
                                        expensive_func2())

这样,如果记录器的级别比DEBUG高,expensive_func1()expensive_func2()就不会被调用。

注意

有时候isEnabledFor()会比你想象的要更昂贵,(例如记录器的层级较深而级别只设置在层级较高的记录器上)。如果是这种情况(或者你不想在循环中反复调用),你可以用局部/实例变量来缓存isEnabledFor()的结果,而不用反复调用该方法。这个缓存值只需要在日志配置在程序运行时动态的改变的时候才需要重新计算(这并不常见)。

对于那些需要更精准的控制如何收集日志信息的特定的应用有其它的优化方式。这里有个列表,列出了如果你不需要,可以怎么做来避免一些处理:

你不想收集什么 如何避免收集它
关于调用发生的信息 logging._srcfile设为None。这可以避免调用sys._getframe(),可以在PyPy这样的环境中加速代码,(对于使用sys._getframe()的代码则无能为力)。
线程信息 logging.logThreads设置为0
进程信息 logging.logProcesses设置为0

还需要注意的是,核心logging模块只包含基本的处理器。如果不导入logging.handlerslogging.config,它们就不会占用内存。

参见

logging模块
logging模块的API参考。
logging.config模块
logging模块的配置API。
logging.handlers模块
logging模块包含的有用的处理器。

日志cookbook