日志记录 HOWTO

作者:

Vinay Sajip <vinay_sajip at red-dove dot com>

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

基本日志记录教程

日志记录是一种跟踪某些软件运行时发生的事件的方法。软件的开发者会在他们的代码中添加日志调用来指示某些事件已经发生。一个事件由一个描述性消息来描述,该消息可以选择包含变量数据(即,对于事件的每次出现,数据可能不同)。事件也有一个重要性,开发者将其归于该事件;重要性也可以称为级别严重性

何时使用日志记录

您可以通过 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。如果您希望每次运行都重新开始,而不记住早期运行的消息,则可以指定 filemode 参数,方法是将上述示例中的调用更改为

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

下一步

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

如果您的日志记录需求很简单,那么请使用上面的示例将日志记录集成到您自己的脚本中,如果您遇到问题或不理解某些内容,请在 comp.lang.python Usenet 组(可在 https://groups.google.com/g/comp.lang.python 中找到)上发布问题,您应该很快就会得到帮助。

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

高级日志记录教程

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

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

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

  • 过滤器提供更精细的工具来确定要输出哪些日志记录。

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

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

日志记录是通过调用 Logger 类(以下称为记录器)的实例上的方法来执行的。每个实例都有一个名称,它们在概念上使用点(句点)作为分隔符排列在命名空间层次结构中。例如,名为 “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.setLevel() 指定记录器将处理的最低严重性日志消息,其中 debug 是最低的内置严重性级别,而 critical 是最高的内置严重性级别。例如,如果严重性级别为 INFO,则记录器将仅处理 INFO、WARNING、ERROR 和 CRITICAL 消息,而忽略 DEBUG 消息。

  • Logger.addHandler()Logger.removeHandler() 从记录器对象添加和删除处理程序对象。处理程序在 处理程序 中进行了更详细的介绍。

  • Logger.addFilter()Logger.removeFilter() 从记录器对象添加和删除过滤器对象。过滤器在 过滤器对象 中进行了更详细的介绍。

您不需要始终在您创建的每个记录器上调用这些方法。请参阅本节的最后两段。

配置记录器对象后,以下方法会创建日志消息

  • 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 类是一个基类,它定义了所有处理程序都应具有的接口,并建立了一些子类可以使用的(或覆盖的)默认行为。

格式化器

Formatter 对象配置日志消息的最终顺序、结构和内容。与基础 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

请注意,配置文件中引用的类名需要是相对于 logging 模块的相对值,或者是可以使用普通导入机制解析的绝对值。因此,您可以使用 WatchedFileHandler(相对于 logging 模块)或 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 版本中更改: 对于 3.2 之前的 Python 版本,行为如下:

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

  • 如果 raiseExceptionsTrue(开发模式),则会打印一次消息“找不到记录器 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 实例。每个记录器可以有零个、一个或多个与之关联的处理器(通过 addHandler() 方法,该方法属于 Logger)。除了直接与记录器关联的任何处理器之外,所有与记录器的所有祖先关联的处理器都会被调用来分派消息(除非记录器的 propagate 标志设置为假值,此时传递给祖先处理器的过程会停止)。

与记录器一样,处理器也可以有与之关联的级别。处理器的级别充当过滤器,与记录器的级别作用相同。如果处理器决定实际分派一个事件,则会使用 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 系统日志守护程序,可能在远程机器上。

  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() 方法)。在决定进一步处理消息之前,记录器和处理器都会查询它们的所有过滤器以获取许可。如果任何过滤器返回 false 值,则该消息将不会被进一步处理。

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

记录过程中引发的异常

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

SystemExitKeyboardInterrupt 异常永远不会被吞下。在 emit() 方法中发生的其他异常,将传递给 Handler 子类的 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 这样的环境中加速您的代码(无法加速使用 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

日志模块的 API 参考。

模块 logging.config

日志模块的配置 API。

模块 logging.handlers

日志模块中包含的有用处理器。

日志记录手册