日志记录 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() 方法,具体取决于特定错误和应用程序域

记录器方法以它们用于跟踪的事件的级别或严重性命名。标准级别及其适用性如下所述(按严重性递增顺序)

级别

使用时间

调试

详细的信息,通常只有在诊断问题时才感兴趣。

信息

确认一切按预期工作。

警告

表示发生了意外情况,或表明不久的将来会出现问题(例如,“磁盘空间不足”)。软件仍在按预期工作。

错误

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

严重

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

默认级别为 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!

如你所见,将变量数据合并到事件描述消息中使用了旧的 %-style 字符串格式化方法。这是为了向后兼容: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 或 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() 支持的格式相同。

下一步

这结束了基本教程。它应该足以让你开始使用 logging。logging 包提供了更多功能,但要充分利用它,你需要花更多时间阅读以下部分。如果你已经准备好了,那就准备好你喜欢的饮料,继续吧。

如果您的日志需求很简单,那么使用上面的示例将日志记录到您自己的脚本中,如果您遇到问题或不理解某些内容,请在 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() 来更改此格式。有关格式字符串构造方式的所有选项,请参阅 Formatter Objects

日志流

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

Logger flow Create LogRecord Logging call in user code, e.g. logger.info(...) Stop Does a filter attached to logger reject the record? Pass 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 LogRecord? 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 LogRecord passed to handler

记录器

Logger 对象有三个任务。首先,它们向应用程序代码公开几个方法,以便应用程序可以在运行时记录消息。其次,记录器对象根据严重程度(默认过滤机制)或过滤器对象来确定要处理哪些日志消息。第三,记录器对象将相关的日志消息传递给所有感兴趣的日志处理程序。

记录器对象上使用最广泛的方法分为两类:配置和消息发送。

这些是最常见的配置方法

  • Logger.setLevel() 指定记录器将处理的最低严重程度的日志消息,其中调试是最低的内置严重程度级别,而严重错误是最高的内置严重程度级别。例如,如果严重程度级别为 INFO,则记录器将仅处理 INFO、WARNING、ERROR 和 CRITICAL 消息,并将忽略 DEBUG 消息。

  • Logger.addHandler()Logger.removeHandler() 在记录器对象中添加和删除处理程序对象。处理程序将在 Handlers 中更详细地介绍。

  • Logger.addFilter()Logger.removeFilter() 在记录器对象中添加和删除过滤器对象。过滤器将在 Filter Objects 中更详细地介绍。

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

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

  • 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() 方法向自身添加零个或多个处理程序对象。例如,应用程序可能希望将所有日志消息发送到日志文件,将所有错误或更高严重程度的日志消息发送到标准输出,并将所有严重程度为 critical 的消息发送到电子邮件地址。此场景需要三个独立的处理程序,每个处理程序负责将特定严重程度的消息发送到特定位置。

标准库包含相当多的处理程序类型(参见 有用的处理程序);教程主要使用 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 代码中构建字典,通过套接字以腌制形式接收它,或者使用对您的应用程序有意义的任何方法。

以下是以 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(开发模式),则会打印一次消息“未找到用于记录器 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以外的任何处理程序。这是因为处理程序的配置是使用库的应用程序开发人员的权利。应用程序开发人员了解他们的目标受众以及哪些处理程序最适合他们的应用程序:如果您在“幕后”添加处理程序,您很可能会干扰他们执行单元测试和交付符合其要求的日志的能力。

日志记录级别

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

级别

数值

严重

50

错误

40

警告

30

信息

20

调试

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 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 版本中添加: The NullHandler 类。

在 3.2 版本中添加: The QueueHandler 类。

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

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

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

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

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

记录期间引发的异常

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

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

handleError()Handler 中的默认实现会检查模块级变量 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

日志记录模块的 API 参考。

模块 logging.config

日志记录模块的配置 API。

模块 logging.handlers

日志记录模块中包含的有用处理程序。

日志记录食谱