日志记录 HOWTO

作者:

Vinay Sajip <vinay_sajip at red-dove dot com>

此页面包含教程信息。有关参考信息和日志记录手册的链接,请参阅其他资源

基本日志记录教程

日志记录是一种跟踪软件运行时发生的事件的方法。软件开发者将日志记录调用添加到其代码中,以指示某些事件已发生。事件由描述性消息描述,该消息可以可选地包含变量数据(即,对于事件的每次发生可能不同的数据)。事件还具有开发者赋予事件的重要性;重要性也可以称为*级别*或*严重性*。

何时使用日志记录

您可以通过使用logger = getLogger(__name__)创建记录器,然后调用记录器的debug()info()warning()error()critical()方法来访问日志记录功能。要确定何时使用日志记录以及何时使用哪个记录器方法,请参阅下表。它针对一组常见任务,说明了完成该任务的最佳工具。

您要执行的任务

完成任务的最佳工具

为命令行脚本或程序的普通用法显示控制台输出

print()

报告程序正常运行期间发生的事件(例如,用于状态监控或故障调查)

记录器的info()(或debug()方法,用于诊断目的的非常详细的输出)

发出有关特定运行时事件的警告

库代码中的warnings.warn(),如果问题可避免且客户端应用程序应修改以消除警告

记录器的warning()方法,如果客户端应用程序无法对情况做任何事情,但事件仍应被记录

报告有关特定运行时事件的错误

引发异常

报告抑制错误而不引发异常(例如,长时间运行的服务器进程中的错误处理器)

记录器的error()exception()critical()方法,根据具体错误和应用领域选择

记录器方法的名称与其用于跟踪的事件的级别或严重性相对应。下面描述了标准级别及其适用性(按严重性递增顺序)

级别

何时使用

DEBUG

详细信息,通常仅在诊断问题时感兴趣。

INFO

确认一切按预期运行。

WARNING

表示发生了意外情况,或预示着近期可能出现问题(例如,“磁盘空间不足”)。软件仍按预期运行。

ERROR

由于更严重的问题,软件无法执行某些功能。

CRITICAL

严重错误,表示程序本身可能无法继续运行。

默认级别是WARNING,这意味着只有此严重级别及更高级别的事件才会被跟踪,除非日志记录包被配置为执行其他操作。

被跟踪的事件可以通过不同的方式处理。处理被跟踪事件的最简单方法是将其打印到控制台。另一种常见方法是将其写入磁盘文件。

一个简单的例子

一个非常简单的例子是

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模块上使用函数,例如logging.debug,而不是创建记录器并调用其上的函数。这些函数在根记录器上操作,但它们可能很有用,因为如果尚未调用basicConfig(),它们将为您调用它,就像此示例中一样。在较大的程序中,您通常希望明确控制日志记录配置——因此,出于这个原因以及其他原因,最好创建记录器并调用其方法。

日志记录到文件

一种非常常见的情况是将日志记录事件记录到文件中,所以接下来我们来看看这个。请务必在新启动的 Python 解释器中尝试以下操作,而不要从上面描述的会话继续

import logging
logger = logging.getLogger(__name__)
logging.basicConfig(filename='example.log', encoding='utf-8', level=logging.DEBUG)
logger.debug('This message should go to the log file')
logger.info('So should this')
logger.warning('And this, too')
logger.error('And non-ASCII stuff, too, like Øresund and Malmö')

3.9 版新增: 添加了 *encoding* 参数。在早期 Python 版本中,或者如果未指定,使用的编码是 open() 使用的默认值。虽然在上面的示例中未显示,但现在也可以传递 *errors* 参数,它确定如何处理编码错误。有关可用值和默认值,请参阅 open() 的文档。

现在如果我们打开文件并查看我们有什么,我们应该会找到日志消息

DEBUG:__main__:This message should go to the log file
INFO:__main__:So should this
WARNING:__main__:And this, too
ERROR:__main__:And non-ASCII stuff, too, like Øresund and Malmö

此示例还展示了如何设置日志记录级别作为跟踪的阈值。在此示例中,因为我们将阈值设置为DEBUG,所以所有消息都已打印。

如果您想从命令行选项设置日志级别,例如

--log=INFO

并且您在某个变量*loglevel*中存储了传递给--log参数的值,则可以使用

getattr(logging, loglevel.upper())

获取您将通过*level*参数传递给basicConfig()的值。您可能需要对任何用户输入值进行错误检查,可能如下例所示

# 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中。如果您希望每次运行都重新开始,而不记住之前运行的消息,可以通过将上述示例中的调用更改为

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

输出将与以前相同,但日志文件不再追加,因此早期运行的消息丢失。

记录变量数据

要记录变量数据,请使用事件描述消息的格式字符串,并将变量数据作为参数附加。例如

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 属性的文档,但对于简单用法,您只需要*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 或 RFC 3339。如果您需要对日期/时间格式进行更多控制,请向 basicConfig 提供 *datefmt* 参数,如下例所示

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() 支持的格式相同。

后续步骤

基本教程到此结束。它应该足以让您开始使用日志记录。日志记录包提供了更多功能,但要充分利用它,您需要花更多时间阅读以下部分。如果您已准备好,请拿起您最喜欢的饮料,继续阅读。

如果您的日志记录需求很简单,那么可以使用上述示例将日志记录集成到您自己的脚本中,如果您遇到问题或不理解任何内容,请在 Python 讨论论坛的帮助类别中发布问题,您应该很快会收到帮助。

还在吗?您可以继续阅读接下来的几节,它们提供了比上面基本教程略高级/深入的教程。之后,您可以查看日志记录食谱

高级日志记录教程

日志记录库采用模块化方法,提供以下几类组件:记录器、处理器、过滤器和格式化器。

  • 记录器公开了应用程序代码直接使用的接口。

  • 处理器将日志记录(由记录器创建)发送到适当的目标。

  • 过滤器提供更细粒度的功能,用于确定要输出哪些日志记录。

  • 格式化器指定最终输出中日志记录的布局。

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

日志记录是通过调用 Logger 类的实例(以下称为 *loggers*)上的方法来执行的。每个实例都有一个名称,它们在概念上使用点(句点)作为分隔符排列在命名空间层次结构中。例如,名为“scan”的记录器是记录器“scan.text”、“scan.html”和“scan.pdf”的父级。记录器名称可以是您想要的任何名称,并指示应用程序中日志消息的来源区域。

命名记录器时的一个好约定是,在每个使用日志记录的模块中,使用模块级记录器,命名如下

logger = logging.getLogger(__name__)

这意味着记录器名称跟踪包/模块层次结构,并且仅从记录器名称就可以直观地知道事件是在何处记录的。

记录器层次结构的根称为根记录器。这就是函数debug()info()warning()error()critical()使用的记录器,它们只是调用根记录器中同名的方法。这些函数和方法具有相同的签名。根记录器的名称在日志输出中打印为“root”。

当然,可以将消息记录到不同的目的地。该软件包包含将日志消息写入文件、HTTP GET/POST 位置、通过 SMTP 发送电子邮件、通用套接字、队列或特定于操作系统的日志记录机制(如 syslog 或 Windows NT 事件日志)的支持。目的地由*处理程序*类提供服务。如果您有任何内置处理程序类无法满足的特殊要求,您可以创建自己的日志目的地类。

默认情况下,没有为任何日志消息设置目标。您可以通过使用 basicConfig() 指定目标(例如控制台或文件),如教程示例所示。如果您调用函数 debug()info()warning()error()critical(),它们将检查是否未设置目标;如果未设置,它们将设置控制台 (sys.stderr) 作为目标,并为显示的消息设置默认格式,然后委托给根记录器执行实际的消息输出。

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

severity:logger name:message

您可以通过使用 *format* 关键字参数将格式字符串传递给 basicConfig() 来更改此设置。有关如何构造格式字符串的所有选项,请参阅格式化器对象

日志记录流程

以下图表说明了日志事件信息在记录器和处理程序中的流向。

Logger flow Create LogRecord Logging call in user code, e.g. logger.info(...) Stop Does a filter attached to logger reject the record? Pass record to handlers of current logger Is propagate true for current logger? Is there a parent logger? Set current logger to parent At least one handler in hierarchy? Use lastResort handler Handler enabled for level of record? Does a filter attached to handler reject the record? Stop Emit (includes formatting) Handler flow Logger enabled for level of call? No Yes Yes No No Yes Yes No No Yes No Yes No Yes Record passed to handler

记录器

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 的后代。

记录器具有*有效级别*的概念。如果未在记录器上明确设置级别,则使用其父级的级别作为其有效级别。如果父级没有明确设置级别,则检查*其*父级,依此类推——搜索所有祖先,直到找到明确设置的级别。根记录器始终具有明确设置的级别(默认为WARNING)。当决定是否处理事件时,记录器的有效级别用于确定事件是否传递给记录器的处理程序。

子记录器将消息传播给与其祖先记录器关联的处理程序。因此,不需要为应用程序使用的所有记录器定义和配置处理程序。只需为顶级记录器配置处理程序,并根据需要创建子记录器即可。(但是,可以通过将记录器的*propagate*属性设置为False来关闭传播。)

处理程序

Handler 对象负责根据日志消息的严重性,将相应的日志消息分派到处理程序指定的目标。Logger 对象可以使用 addHandler() 方法向自身添加零个或多个处理程序对象。例如,应用程序可能希望将所有日志消息发送到日志文件,将所有错误或更高级别的日志消息发送到 stdout,并将所有关键消息发送到电子邮件地址。此方案需要三个单独的处理程序,每个处理程序负责将特定严重性的消息发送到特定位置。

标准库包含多种处理程序类型(请参阅有用的处理程序);教程示例中主要使用StreamHandlerFileHandler

处理程序中很少有应用程序开发人员需要关注的方法。对于使用内置处理程序对象(即不创建自定义处理程序)的应用程序开发人员而言,唯一相关的处理程序方法是以下配置方法

  • 与记录器对象一样,setLevel() 方法指定将分派到相应目标的最低严重性。为什么有两个 setLevel() 方法?在记录器中设置的级别决定了它将哪些严重性消息传递给其处理程序。在每个处理程序中设置的级别决定了该处理程序将发送哪些消息。

  • setFormatter() 为此处理程序选择一个 Formatter 对象使用。

  • addFilter()removeFilter() 分别配置和取消配置处理程序上的过滤器对象。

应用程序代码不应直接实例化和使用 Handler 的实例。相反,Handler 类是一个基类,它定义了所有处理程序应该具有的接口,并建立了一些子类可以使用的(或覆盖的)默认行为。

格式化器

格式化器对象配置日志消息的最终顺序、结构和内容。与基础 logging.Handler 类不同,应用程序代码可以实例化格式化器类,尽管如果您的应用程序需要特殊行为,您很可能会子类化格式化器。构造函数接受三个可选参数——消息格式字符串、日期格式字符串和样式指示符。

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

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

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

在末尾附加毫秒。style'%''{''$' 之一。如果未指定其中之一,则将使用 '%'

如果 style'%',则消息格式字符串使用 %(<dictionary key>)s 样式字符串替换;可能的键在LogRecord 属性中进行了说明。如果样式是 '{',则假定消息格式字符串与 str.format() 兼容(使用关键字参数),而如果样式是 '$',则消息格式字符串应符合 string.Template.substitute() 的预期。

3.2 版新增: 添加了 style 参数。

以下消息格式字符串将按顺序记录可读格式的时间、消息的严重性以及消息的内容

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

格式化器使用用户可配置的函数将记录的创建时间转换为元组。默认情况下,使用 time.localtime();要为特定格式化器实例更改此设置,请将实例的 converter 属性设置为与 time.localtime()time.gmtime() 具有相同签名的函数。要为所有格式化器更改此设置,例如如果您希望所有日志记录时间都显示为 GMT,请在 Formatter 类中设置 converter 属性(对于 GMT 显示,设置为 time.gmtime)。

配置日志记录

程序员可以通过三种方式配置日志记录

  1. 使用 Python 代码显式创建记录器、处理程序和格式化程序,调用上面列出的配置方法。

  2. 创建日志记录配置文件并使用fileConfig()函数读取它。

  3. 创建一个配置信息字典并将其传递给dictConfig()函数。

有关后两个选项的参考文档,请参阅配置函数。以下示例使用 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.warning('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.warning('warn message')
logger.error('error message')
logger.critical('critical message')

这是logging.conf文件

[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

输出与非配置文件示例几乎相同

$ 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

请注意,配置文件中引用的类名需要相对于日志记录模块,或者是可以使用正常导入机制解析的绝对值。因此,您可以使用 WatchedFileHandler(相对于日志记录模块)或 mypackage.mymodule.MyHandler(对于在包 mypackage 和模块 mymodule 中定义的类,其中 mypackage 在 Python 导入路径上可用)。

在 Python 3.2 中,引入了一种新的日志记录配置方式,使用字典来保存配置信息。这提供了上述基于配置文件的方法的超集功能,并且是新应用程序和部署的推荐配置方法。因为 Python 字典用于保存配置信息,并且您可以使用不同的方式填充该字典,所以您有更多的配置选项。例如,您可以使用 JSON 格式的配置文件,或者,如果您可以访问 YAML 处理功能,则可以使用 YAML 格式的文件来填充配置字典。或者,当然,您可以在 Python 代码中构造字典,通过套接字以 pickle 形式接收它,或者使用适合您应用程序的任何方法。

这是上述相同配置的示例,采用 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]

有关使用字典进行日志记录的更多信息,请参阅配置函数

未提供配置时会发生什么

如果未提供日志配置,则可能会出现需要输出日志事件,但找不到任何处理程序来输出事件的情况。

该事件使用存储在 lastResort 中的“最后手段处理程序”输出。此内部处理程序不与任何记录器关联,其作用类似于 StreamHandler,将事件描述消息写入 sys.stderr 的当前值(因此尊重可能生效的任何重定向)。消息不进行格式化——仅打印裸露的事件描述消息。处理程序的级别设置为 WARNING,因此所有此级别及更高级别的事件都将输出。

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

  • 如果 raiseExceptionsFalse(生产模式),则事件会静默丢弃。

  • 如果 raiseExceptionsTrue(开发模式),则会打印一次消息“No handlers could be found for logger X.Y.Z”。

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

为库配置日志记录

在开发使用日志记录的库时,您应该注意记录库如何使用日志记录——例如,所使用的记录器名称。还需要考虑其日志记录配置。如果使用的应用程序不使用日志记录,并且库代码进行日志记录调用,那么(如上一节所述)严重性为 WARNING 及更高级别的事件将打印到 sys.stderr。这被认为是最佳默认行为。

如果由于某种原因,您*不*希望在没有任何日志配置的情况下打印这些消息,您可以将一个无所事事的处理程序附加到您的库的顶级记录器。这样可以避免打印消息,因为总能找到库事件的处理程序:它只是不产生任何输出。如果库用户配置应用程序使用的日志记录,那么该配置可能会添加一些处理程序,并且如果级别配置得当,那么库代码中进行的日志记录调用将像往常一样向这些处理程序发送输出。

日志记录包中包含一个无所事事的处理程序:NullHandler(自 Python 3.1 起)。可以将此处理程序的实例添加到库使用的日志记录命名空间的顶级记录器中(*如果*您想在没有日志记录配置的情况下阻止库的日志事件输出到 sys.stderr)。如果库 *foo* 的所有日志记录都使用名称与“foo.x”、“foo.x.y”等匹配的记录器完成,那么代码

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

应该产生预期的效果。如果一个组织生产多个库,那么指定的记录器名称可以是“orgname.foo”,而不仅仅是“foo”。

备注

强烈建议您*不要在您的库中记录到根记录器*。相反,请使用一个具有唯一且易于识别名称的记录器,例如您库的顶级包或模块的__name__。记录到根记录器将使应用程序开发人员难以或无法根据自己的意愿配置您库的日志记录详细程度或处理程序。

备注

强烈建议您*不要向库的记录器添加除*NullHandler*之外的任何处理程序*。这是因为处理程序的配置是使用您库的应用程序开发人员的特权。应用程序开发人员了解他们的目标受众以及哪种处理程序最适合他们的应用程序:如果您“暗中”添加处理程序,您很可能会干扰他们进行单元测试和交付符合其要求的日志的能力。

日志记录级别

日志级别的数值在下表中给出。如果您想定义自己的级别,并且需要它们相对于预定义级别具有特定的值,那么这些级别主要才会有意义。如果您定义一个具有相同数值的级别,它会覆盖预定义的值;预定义的名称将丢失。

级别

数值

CRITICAL

50

ERROR

40

WARNING

30

INFO

20

DEBUG

10

NOTSET

0

级别也可以与记录器关联,由开发人员设置或通过加载保存的日志记录配置设置。当在记录器上调用日志记录方法时,记录器将其自身级别与与方法调用关联的级别进行比较。如果记录器的级别高于方法调用的级别,则实际上不会生成任何日志记录消息。这是控制日志输出详细程度的基本机制。

日志消息编码为 LogRecord 类的实例。当记录器决定实际记录一个事件时,会从日志消息中创建一个 LogRecord 实例。

日志消息通过使用*处理程序*进行分发,处理程序是 Handler 类的子类的实例。处理程序负责确保日志消息(以 LogRecord 的形式)最终到达对该消息的目标受众(例如终端用户、支持人员、系统管理员、开发人员)有用的特定位置(或一组位置)。处理程序被传递用于特定目标的 LogRecord 实例。每个记录器都可以关联零个、一个或多个处理程序(通过 LoggeraddHandler() 方法)。除了直接与记录器关联的任何处理程序之外,*与记录器所有祖先关联的所有处理程序*都会被调用以分发消息(除非记录器的 *propagate* 标志设置为 false 值,此时传递给祖先处理程序的操作将停止)。

就像记录器一样,处理程序也可以具有与之关联的级别。处理程序的级别以与记录器级别相同的方式充当过滤器。如果处理程序决定实际分派事件,则使用 emit() 方法将消息发送到其目的地。大多数用户定义的 Handler 子类都需要覆盖此 emit()

自定义级别

定义自己的级别是可能的,但应该不是必需的,因为现有级别是根据实践经验选择的。但是,如果您确信需要自定义级别,则在这样做时应格外小心,并且如果您正在开发库,则定义自定义级别可能*是一个非常糟糕的主意*。这是因为如果多个库作者都定义了自己的自定义级别,那么一起使用的这些多个库的日志输出可能会让使用它的开发人员难以控制和/或解释,因为给定的数值对于不同的库可能意味着不同的东西。

有用的处理器

除了基本的 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 实例不处理错误消息。它们由想要使用日志记录但希望避免“未找到记录器 *XXX* 的处理程序”消息的库开发人员使用,该消息可能会在库用户未配置日志记录时显示。有关更多信息,请参阅为库配置日志记录

3.1 版新增: NullHandler 类。

3.2 版新增: QueueHandler 类。

NullHandlerStreamHandlerFileHandler 类在核心日志记录包中定义。其他处理程序在子模块 logging.handlers 中定义。(还有一个子模块 logging.config,用于配置功能。)

日志消息通过 Formatter 类的实例进行格式化以供显示。它们使用适合与 % 运算符和字典一起使用的格式字符串进行初始化。

为了批量格式化多条消息,可以使用 BufferingFormatter 的实例。除了格式字符串(应用于批处理中的每条消息)之外,还提供了头部和尾部格式字符串。

当基于日志器级别和/或处理器级别的过滤不足时,可以将 Filter 实例添加到 LoggerHandler 实例(通过它们的 addFilter() 方法)。在决定是否进一步处理消息之前,日志器和处理器都会咨询它们所有的过滤器以获得许可。如果任何过滤器返回一个假值,则消息将不会被进一步处理。

基本的 Filter 功能允许按特定的日志器名称进行过滤。如果使用此功能,发送到指定日志器及其子日志器的消息将通过过滤器,而所有其他消息将被丢弃。

日志记录期间引发的异常

日志记录包旨在吞噬在生产环境中日志记录时发生的异常。这是为了防止在处理日志记录事件时发生的错误(例如日志记录配置错误、网络错误或其他类似错误)导致使用日志记录的应用程序过早终止。

SystemExitKeyboardInterrupt 异常永远不会被吞噬。在 Handler 子类的 emit() 方法期间发生的其他异常将传递给其 handleError() 方法。

HandlerhandleError() 的默认实现会检查模块级变量 raiseExceptions 是否已设置。如果已设置,则会将堆栈跟踪打印到 sys.stderr。如果未设置,则异常将被吞噬。

备注

raiseExceptions 的默认值为 True。这是因为在开发过程中,您通常希望收到任何发生的异常通知。建议您在生产环境中使用时将 raiseExceptions 设置为 False

使用任意对象作为消息

在前面的章节和示例中,假设记录事件时传递的消息是字符串。然而,这并非唯一可能。您可以传递任意对象作为消息,当日志系统需要将其转换为字符串表示时,将调用其 __str__() 方法。实际上,如果您愿意,可以完全避免计算字符串表示——例如,SocketHandler 通过将其序列化并通过网络发送来发出事件。

优化

消息参数的格式化会延迟到无法避免为止。然而,计算传递给日志记录方法的参数也可能很耗时,如果日志器只是丢弃您的事件,您可能希望避免这样做。要决定如何操作,您可以调用 isEnabledFor() 方法,该方法接受一个级别参数,并且如果日志器会为该级别的调用创建事件,则返回 True。您可以编写如下代码:

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

这样,如果日志器的阈值设置在 DEBUG 以上,则永远不会调用 expensive_func1expensive_func2

备注

在某些情况下,isEnabledFor() 本身可能比您期望的更耗时(例如,对于嵌套很深的日志器,其中显式级别只在日志器层次结构的高层设置)。在这种情况下(或者如果您想在紧密循环中避免调用方法),您可以将调用 isEnabledFor() 的结果缓存到局部或实例变量中,并使用该变量代替每次调用方法。这样的缓存值只有在应用程序运行时日志配置动态更改时才需要重新计算(这种情况并不常见)。

对于需要更精确控制收集哪些日志信息的特定应用程序,还可以进行其他优化。以下是您可以采取的一些措施,以避免在日志记录过程中处理您不需要的信息:

您不想收集什么

如何避免收集

有关调用来源的信息。

logging._srcfile 设置为 None。这可以避免调用 sys._getframe(),这可能有助于在 PyPy 等环境中加快代码速度(PyPy 无法加速使用 sys._getframe() 的代码)。

线程信息。

logging.logThreads 设置为 False

当前进程 ID (os.getpid())

logging.logProcesses 设置为 False

使用 multiprocessing 管理多个进程时的当前进程名称。

logging.logMultiprocessing 设置为 False

使用 asyncio 时的当前 asyncio.Task 名称。

logging.logAsyncioTasks 设置为 False

另外请注意,核心日志模块只包含基本处理器。如果您不导入 logging.handlerslogging.config,它们将不会占用任何内存。

其他资源

参见

模块 logging

logging 模块的 API 参考。

模块 logging.config

日志模块的配置 API。

模块 logging.handlers

日志模块附带的有用的处理器。

日志记录指南