Skip to main content

记录HOWTO

Author:

Vinay Sajip <vinay_sajip at red-dove dot com>

基本日志教程

日志记录是跟踪某些软件运行时发生的事件的一种手段。软件开发人员将记录调用添加到他们的代码,以指示发生了某些事件。事件由描述性消息描述,该描述性消息可以可选地包含可变数据(即,对于事件的每次出现都潜在地不同的数据)。事件也具有开发者归属于事件的重要性;重要性也可以称为 levelseverity

何时使用日志记录

日志记录为简单的日志记录使用提供了一组方便的功能。这些是 debug()info()warning()error()critical()。要确定何时使用日志记录,请参阅下表,其中列出了一组常见任务中使用的最佳工具。

要执行的任务

任务的最佳工具

显示控制台输出,用于正常使用命令行脚本或程序

print()

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

logging.info() (或用于非常详细输出的 logging.debug() 用于诊断目的)

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

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

logging.warning() 如果没有什么客户端应用程序可以做的情况,但事件仍然应该注意

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

引发异常

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

logging.error()logging.exception()logging.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。打印的消息包括记录呼叫中提供的事件的级别和描述的指示,即“注意!”。现在不要担心“根”部分:稍后会解释。如果需要,实际输出可以非常灵活地格式化;格式化选项也将在后面解释。

记录到文件

一个很常见的情况是在一个文件中记录日志事件,所以让我们来看下一个。请务必在新开始的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

并且你有一个变量 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() 的调用应该是 beforedebug()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 中的模式将其推广到多个模块。请注意,对于这种简单的使用模式,通过查看日志文件中您的消息来自应用程序中的 where,除了查看事件描述,您将不知道。如果要跟踪邮件的位置,您需要参阅教程级别之外的文档 - 请参阅 高级日志教程

记录变量数据

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

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

将显示:

WARNING:root:Look before you leap!

可以看到,将可变数据合并到事件描述消息中使用了旧的字符串格式的%格式。这是为了向后兼容性:日志包包含更新的格式化选项,如 str.format()string.Template。这些较新的格式选项 are 支持,但探索它们不在本教程的范围内:有关详细信息,请参阅 在应用程序中使用特定的格式化样式

更改显示消息的格式

要更改用于显示消息的格式,您需要指定要使用的格式:

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

注意,在前面的例子中出现的“根”已经消失了。对于可以出现在格式字符串中的一整套事物,您可以参考 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。如果您需要更多地控制日期/时间的格式,请为 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/group/comp.lang.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事件日志)的电子邮件。目的地由 handler 类服务。如果您有任何内置的处理程序类没有满足的特殊要求,您可以创建自己的日志目标类。

默认情况下,没有为任何日志记录消息设置目标。您可以使用 basicConfig() 指定目标(如控制台或文件),如教程示例中所示。如果你调用函数 debug()info()warning()error()critical(),他们将检查是否没有设置目的地;如果没有设置,则在委派给根记录器进行实际的消息输出之前,它们将设置控制台的目的地(sys.stderr)和所显示消息的默认格式。

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

severity:logger name:message

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

记录流程

记录器和处理程序中的日志事件信息流如下图所示。

../../_images/logging_flow.png

记录器

Logger 对象有三重工作。首先,它们向应用程序代码公开了几种方法,以便应用程序可以在运行时记录消息。第二,记录器对象基于严重性(默认过滤工具)或过滤器对象来确定要作用于哪些日志消息。第三,logger对象将相关的日志消息传递给所有感兴趣的日志处理程序。

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

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

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

使用配置的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 的后代。

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

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

处理程序

Handler 对象负责将适当的日志消息(基于日志消息的严重性)分派给处理程序的指定目标。 Logger 对象可以使用 addHandler() 方法向自身添加零个或多个处理程序对象。作为示例场景,应用程序可能想要将所有日志消息发送到日志文件,将所有错误或更高的日志消息发送到stdout,以及所有对电子邮件地址至关重要的消息。这种情况需要三个单独的处理程序,每个处理程序负责将特定严重性的消息发送到特定位置。

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

在处理程序中有很少的方法可供应用程序开发人员关注自己。对于使用内置处理程序对象(即不创建自定义处理程序)的应用程序开发人员来说,唯一的处理程序方法是以下配置方法:

  • setLevel() 方法,正如在logger对象中一样,指定将被分派到适当目的地的最低严重性。为什么有两个 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() 相同签名的函数。要想改变它的所有格式化,例如,如果你想在格林尼治标准时间显示所有的日志记录时间,请在格式化类(time.gmtime 为GMT显示)的 converter 属性。

配置日志记录

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

  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.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')

这里是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
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 的值。

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

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

这里是一个与上面相同的配置示例,在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]

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

如果未提供配置会发生什么

如果没有提供日志配置,则可能存在需要输出日志事件的情况,但是没有找到处理程序来输出事件。在这些情况下,日志包的行为取决于Python版本。

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

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

  • 如果 logging.raiseExceptionsTrue (开发模式),则会显示一条消息“没有处理程序可用于日志记录器X.Y.Z”。

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

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

为了获得3.2之前的行为,logging.lastResort 可以被设置为 None

配置库的日志记录

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

如果由于某种原因,don’t 希望在没有任何日志配置的情况下打印这些消息,则可以为库的顶级记录器附加一个无用处理程序。这避免了打印消息,因为将始终为库的事件找到处理程序:它只是不产生任何输出。如果库用户配置日志记录以供应用程序使用,那么大概配置将添加一些处理程序,如果级别被适当配置,则库代码中进行的日志调用将正常地发送输出到这些处理程序。

日志包中包含一个do-nothing处理程序:NullHandler (从Python 3.1开始)。此处理程序的实例可以添加到库所使用的日志命名空间的顶层日志记录器(if,您希望在没有日志记录配置的情况下阻止库的记录事件输出到 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 实例。

记录消息通过使用 handlers 经受调度机制,handlersHandler 类的子类的实例。处理程序负责确保记录的消息(以 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 实例对错误消息不执行任何操作。它们由想要使用日志记录的库开发人员使用,但希望避免“没有处理程序可以找到logger XXX”消息,如果库用户尚未配置日志记录,则可以显示该消息。有关详细信息,请参阅 配置库的日志记录

3.1 新版功能: NullHandler 类。

3.2 新版功能: QueueHandler 类。

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

记录的消息被格式化以通过 Formatter 类的实例来呈现。它们使用适合与%操作符和字典一起使用的格式字符串进行初始化。

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

当基于记录器级别和/或处理程序级别进行过滤是不够的时,Filter 的实例可以被添加到 LoggerHandler 实例(通过它们的 addFilter() 方法)。在决定进一步处理消息之前,记录器和处理程序请咨询他们的过滤器以获得许可。如果任何过滤器返回false值,则不会进一步处理该消息。

基本的 Filter 功能允许根据特定的记录器名称进行过滤。如果使用此功能,则允许通过过滤器发送到命名的记录器及其子节点的邮件,并且所有其他邮件被删除。

日志记录期间引发的异常

日志记录包旨在吞并登录生产期间发生的异常。这样,处理日志事件(例如日志配置错误,网络或其他类似错误)时出现的错误不会导致使用日志记录的应用程序过早终止。

SystemExitKeyboardInterrupt 异常不会被吞噬。在 Handler 亚类的 emit() 方法期间发生的其他异常被传递到其 handleError() 方法。

Handler 中的 handleError() 的默认实现检查以查看是否设置了模块级变量 raiseExceptions。如果设置,回溯将打印到 sys.stderr。如果未设置,则异常被吞服。

注解

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

使用任意对象作为消息

在前面的部分和示例中,已经假设在记录事件时传递的消息是字符串。然而,这不是唯一的可能性。您可以将任意对象作为消息传递,并且当日志记录系统需要将其转换为字符串表示时,将调用其 __str__() 方法。事实上,如果你想要,你可以避免计算一个字符串表示完全 - 例如,SocketHandler 发出一个事件通过pickling和发送它在线上。

优化

消息参数的格式被延迟,直到不能避免。然而,计算传递给日志记录方法的参数也很昂贵,如果记录器只是抛弃你的事件,你可能想避免这样做。要决定做什么,您可以调用 isEnabledFor() 方法,该方法需要一个级别参数,并且如果该事件将由该级别的调用的Logger创建,则返回true。你可以这样编写代码:

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.handlerslogging.config,它们将不占用任何内存。

参见

模块 logging

日志模块的API参考。

模块 logging.config

日志模块的配置API。

模块 logging.handlers

有用的处理程序包括在日志模块中。

日志食谱