日志HOWTO

作者:Vinay Sajip

日志基础教程

日志是跟踪软件运行时所发生的事件的一种方法。软件开发者在代码中调用日志函数,表明发生了特定的事件。事件由描述性消息描述,该描述性消息可以可选地包含可变数据(即,对于事件的每次出现都潜在地不同的数据)。事件还具有开发者归因于事件的重要性;重要性也可以称为级别严重性

何时使用日志

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模块被用来做其它事情。

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

A simple example

一个非常简单的例子:

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被打印的信息包括等级指示和调用logging的时间描述,例如:'小心!'。不用担心 ‘root’ 部分:随后会有解释。如果你需要的话,还可以灵活地格式化实际输出;格式化选项将在下文解释。

Logging to a file

一个常见的场景是将事件记录到文件,随后再看。在新打开的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)

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

Logging from multiple modules

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

# 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!

如你所见,将变量合并到事件消息中使用了老的,%风格的字符串格式化。这是为了向后兼容性:日志包包含更新的格式化选项,如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 attributes, 但是对于简单的使用,你只需要 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提供了更多,你需要花点时间阅读后续的章节。如果准备好了,来点你最喜欢的饮料,然后继续。

如果您的日志记录需求很简单,那么请使用上面的示例将日志记录合并到自己的脚本中,如果遇到问题或不了解某些内容,请在compenet.com上发布一个问题(网址 https://groups.google.com/group/comp.lang.python),您应该在不久之后就会得到帮助。

还在吗?你可以继续阅读下面的章节,它提供了更高级的/有深度的教程。在那之后,你可以看看 Logging 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() and critical()函数, 它们将会检查是否有目的地被设置;如果没有被设置, 它将会设置为控制台(sys.stderr)并且默认格式是在委派给跟logger做实际输出的展示信息。

默认信息格式由 basicConfig()设置为:

severity:logger name:message

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

Logging Flow

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

Loggers

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

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

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

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

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

  • Logger.debug(), Logger.info(), Logger.warning(), Logger.error(), and 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/有效级别的概念。如果一个记录器没有显式地设置级别,那它父节点的级别被用作有效级别。如果父节点也没有明确设置级别,那么检查父节点的父节点,如此反复,所有的父节点都会被搜索,直至找到一个有明确地设置级别。root logger总是有一个明确的等级设置 (WARNING是默认等级).。在决定是否要处理事件时,记录器的有效级别用来判断是否要将事件传递给记录器的处理器。

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

Handlers

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

标准库包括相当多的处理程序类型(参见Useful Handlers);教程的示例中主要使用StreamHandlerFileHandler

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

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

应用代码不应该实例化Handler并使用它的实例。相反, Handler类应该作为一个基类来定义所有的handlers应该建立的默认设置,这个默认设置子类应该使用或覆盖它。

Formatters

格式化器对象决定了日志消息最终的顺序,结构和内容。与基本的logging.Handler类不同,应用程序代码可以实例化格式化程序类,但如果应用程序需要特殊行为,则可能会对其进行子类化。构造函数接受三个可选参数 - 消息格式字符串,日期格式字符串和样式指示器。

logging.Formatter.__init__(fmt=None, datefmt=None, style='%')

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

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

结尾有毫秒值。style,'{'或'$'之一。如果未指定其中一个,则将使用“%”。

如果style是'%',则消息格式字符串使用%(< dictionary key>)s 风格字符串替换;可能的键记录在LogRecord attributes中。如果样式是'{',消息格式字符串被假定为与str.format()(使用关键字参数)兼容,而如果样式是'$',则消息格式字符串符合string.Template.substitute()所期望的。

在版本3.2中已更改:添加了style参数。

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

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

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

Configuring Logging

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

  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 3.2中,引入了一种新的配置日志记录的方法,使用字典来保存配置信息。它提供的功能是上述的基于文件的配置的功能的超集,推荐在新的应用和部署中使用。因为用Python的字典来保存配置信息,而你可以用不同的方法来产生字典,所以你有更多的配置选择。例如你可以使用JSON格式的配置文件,或者使用YAML格式的文件来产生配置字典。或者你可以用Python代码来构造字典,从socket中接受它的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

What happens if no configuration is provided

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

对于3.2之前的Python版本,行为如下:

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

在Python 3.2及更高版本中,行为如下:

  • 使用logging.lastResort中存储的“最后手段的处理程序”输出事件。这个内部处理程序不与任何记录器相关联,并且像StreamHandler一样,它将事件描述消息写入到sys.stderr的当前值(因此尊重任何重定向有效)。不对消息执行格式化 - 只打印裸事件描述消息。处理程序的级别设置为WARNING,因此将输出此级别和更大级别的所有事件。

要获得3.2之前的行为,可以将logging.lastResort设置为

Configuring Logging for a Library

当开发库的时候需要使用日志,你需要注意写文档记录下库是如何使用日志的——例如,使用的记录器的名字。配置日志的时候也要考虑一些情况。如果使用的应用程序不使用日志,但是库的代码又调用了日志功能, (正如前面部分描述)WARNING安全事件和更高级的事件将会被打印到sys.stderr这被认为是最好的默认行为。

如果出于某些原因你想在没有配置日志的情况下打印信息,在库中你可以将什么都不做的handler加入到最高等级的logger。这个避免了消息被打印出来,因为总能给库的事件找到一个处理器:它只是什么都不输出而已。如果库的使用者在应用中配置了日志,可以假定配置会添加一些处理器,只要级别合适,库代码中的日志调用就会把输出传输至这些处理器,就和正常情况一样。

什么都不做的handler对象存在logging包中:NullHandler (since Python 3.1).如果你想防止在没有配置日志时将信息输出到 sys.stderr ,你可以把NullHandler的实例加入到最高等级的logging名字空间的logger。如果库foo的所有日志记录都使用名称与'foo.x','foo.x.y'等匹配的日志记录器完成那么代码:

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

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

注意

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

记录级别

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

级别数字值
CRITICAL50
ERROR40
WARNING30
INFO20
DEBUG10
NOTSET0

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

logging信息被编码成LogRecord类的实例。当一个logger决定记录一个事件时,LogRecord实例就会根据logging的信息被创建。

Logging信息会经过 Handler的子类的实例 handlers调度机制处理。Handlers负责确保日志信息(LogRecord格式)会在某个位置结束,这种方式对与目标用户是很有用的 (such as end users, support desk staff, system administrators, developers).Handlers获得LogRecord实例后计划发送的某个地点。每个logger可以有零或多handlers相关联,通过Logger的方法addHandler()除了记录器直接关联的处理器,记录器祖先的处理器也被用来分发消息(除非记录器的propagate标志位设为假,这时就不会将日志传递给记录器祖先的处理器)。

和记录器一样,处理器也可以有级别。处理器级别的作用和记录器级别的作用一样。如果一个handler决定分发一个事件,emit()方法就会被用来发送信息到它的目的地。多数用户定义的Handler需要重写emit()方法。

Custom Levels

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

Useful Handlers

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

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

版本3.1中的新功能: NullHandler类。

版本3.2中的新功能: QueueHandler类。

The NullHandler, StreamHandler and FileHandler类定义在核心logging包中。其它的handlers定义在一个子模块logging.handlers.(也还有另外一些子模块logging.config用来配置日志功能)

日志记录的信息格式是Formatter实例的格式。它们用格式化字符串初始化,可以使用%操作符和字典。

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

当基本的定义在logger等级或handler等级不够时,Filter可以被加入到Logger and Handler 实例中(通过它们的addFilter() 方法).在决定是否要进一步处理消息之前,记录器和处理器都会参考过滤器的许可。如果任一过滤器返回假,消息将不会被进一步处理。

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

Exceptions raised during logging

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

SystemExit and KeyboardInterrupt异常不会被吞掉。其它出现在Handler子类的emit()方法被传递给它的handleError()方法。

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

注意

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

Using arbitrary objects as messages

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

Optimization

格式化消息参数被推迟到不得不做的时候。尽管如此,计算传递给日志方法的参数仍然有可能很昂贵,你希望能避免这样,如果记录器只是会丢弃该事件。如果是这样,可以调用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() ),如果和当PyPy支持Python 3.x.
线程信息logging.logThreads设置为0
进程信息logging.logProcesses设置为0

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

参见

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

A logging cookbook