Logging HOWTO

  • Author

    • Vinay Sajip <vinay_sajip at red-dove dot com>

基本日志记录教程

日志记录是一种跟踪某些软件运行时发生的事件的方法。该软件的开发人员在其代码中添加了记录调用,以指示发生了某些事件。事件由描述性消息描述,该消息可以可选地包含变量数据(即,每次事件发生时都可能不同的数据)。事件也具有开发者认为该事件的重要性。重要性也可以称为* level severity *。

何时使用日志记录

日志记录提供了一组便利Function,用于简单的日志记录用法。它们是debug()info()warning()error()critical()。要确定何时使用日志记录,请参见下表,该表针对一组常见任务中的每个状态,指出了用于日志记录的最佳工具。

您要执行的任务完成任务的最佳工具
显示控制台输出,用于命令行脚本或程序的常规使用print()
报告在程序正常运行期间发生的事件(例如,用于状态监视或故障调查)logging.info()(或logging.debug(),用于诊断目的非常详细的输出)
发出有关特定运行时事件的警告如果可以避免该问题,请在库代码中 Importingwarnings.warn(),并应修改 Client 端应用程序以消除警告

logging.warning()如果 Client 端应用程序无法解决这种情况,但仍应注意该事件
报告有关特定运行时事件的错误引发异常
在不引发异常的情况下报告对错误的抑制(例如,长时间运行的服务器进程中的错误处理程序)logging.error()logging.exception()logging.critical()适用于特定的错误和应用程序域

日志记录函数以它们用来跟踪的事件的级别或严重性来命名。下面描述了标准级别及其适用性(按照严重程度从高到低的 Sequences):

Level使用时
DEBUG详细信息,通常仅在诊断问题时才需要。
INFO确认一切正常。
WARNING表示发生了意外情况,或者表示在不久的将来出现了一些问题(例如“磁盘空间不足”)。该软件仍按预期运行。
ERROR由于存在更严重的问题,该软件无法执行某些Function。
CRITICAL严重错误,表明程序本身可能无法 continue 运行。

默认级别为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 解释器中try以下操作,而不仅仅是从上述会话 continue:

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,所以所有消息均已打印。

如果要pass命令行选项设置日志记录级别,例如:

--log=INFO

并且您在某些变量* loglevel *中具有为--log传递的参数值,可以使用:

getattr(logging, loglevel.upper())

以获得pass* level *参数传递给basicConfig()的值。您可能想对任何用户 Importing 值进行错误检查,如下例所示:

# 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 。如果您希望每个运行重新开始,而不记得先前运行的消息,则可以pass将上述示例中的调用更改为以下命令来指定 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 中的模式将其推广到多个模块。请注意,对于这种简单的使用模式,除了查看事件描述之外,pass查看日志文件,您不会知道消息在应用程序中的何处*。如果要跟踪消息的位置,则需要参考教程级别以外的文档–参见高级日志记录教程

记录变量数据

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

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

will display:

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

注意,先前示例中出现的“根”已经消失。对于可以以格式字符串显示的所有内容,您可以参考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.如果您需要对日期/时间格式的更多控制,请为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.

Next Steps

基本教程到此结束。它足以使您启动并运行日志记录。日志记录软件包提供了更多的Function,但是要充分利用它,您需要花费更多的时间阅读以下部分。如果您准备好了,那就喝点您喜欢的饮料然后 continue。

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

还在?您可以 continue 阅读以下几节,它们提供的教程要比上面的基础教程稍微更高级/更深入。之后,您可以查看Logging Cookbook

高级日志记录教程

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

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

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

  • 筛选器提供了更细粒度的Function,用于确定要输出的日志记录。

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

日志事件信息在LogRecord实例中的 Logger,处理程序,过滤器和格式化程序之间传递。

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

命名 Logger 时,一个好的习惯是在每个使用记录的模块中使用模块级 Logger,命名如下:

logger = logging.getLogger(__name__)

这意味着 Logger 名称会跟踪程序包/模块的层次结构,并且从 Logger 名称中记录事件的地方就很明显了。

Logger 层次结构的根称为根 Logger。这就是函数debug()info()warning()error()critical()所使用的 Logger,它们仅调用根 Logger 的同名方法。函数和方法具有相同的签名。根 Logger 的名称在记录的输出中显示为“ root”。

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

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

basicConfig()为邮件设置的默认格式为:

severity:logger name:message

您可以pass使用* format *关键字参数将格式字符串传递给basicConfig()来更改此设置。有关如何构造格式字符串的所有选项,请参见Formatter Objects

Logging Flow

下图说明了 Logger 和处理程序中的日志事件信息流。

../_images/logging_flow.png

Loggers

Logger个对象具有三重职责。首先,它们向应用程序代码公开了几种方法,以便应用程序可以在运行时记录消息。其次,Logger 对象根据严重性(默认过滤工具)或过滤器对象确定要采取哪些日志消息。第三,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()返回具有指定名称的 Logger 实例的引用(如果提供),否则返回root。名称是句点分隔的层次结构。对具有相同名称的getLogger()的多次调用将返回对同一 Logger 对象的引用。层次结构列表中位于最下方的 Logger 是列表中较高位置的 Logger 的子级。例如,给定名称为foo的 Logger,名称为foo.barfoo.bar.bazfoo.bam的 Logger 都是foo的后代。

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

子 Logger 将消息传播到与其祖先 Logger 关联的处理程序。因此,不必为应用程序使用的所有 Logger 定义和配置处理程序。只需为顶级 Logger 配置处理程序并根据需要创建子 Logger 就足够了。 (不过,您可以pass将 Logger 的* propagate *属性设置为False来关闭传播.)

Handlers

Handler对象负责将适当的日志消息(基于日志消息的严重性)调度到处理程序的指定目标。 Logger对象可以使用addHandler()方法向自己添加零个或多个处理程序对象。作为示例场景,应用程序可能希望将所有日志消息发送到日志文件,将所有错误或更高级别的日志消息发送到 stdout,并将所有关键消息发送到电子邮件地址。此方案需要三个单独的处理程序,其中每个处理程序负责将特定严重性的消息发送到特定位置。

标准库包括很 multiprocessing 程序类型(请参见Useful Handlers);本教程的示例中主要使用StreamHandlerFileHandler

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

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

  • setFormatter()选择此处理程序要使用的 Formatter 对象。

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

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

Formatters

格式化程序对象配置日志消息的finally Sequences,结构和内容。与基础logging.Handler类不同,应用程序代码可以实例化格式化程序类,尽管如果您的应用程序需要特殊的行为,则您可以将格式化程序子类化。构造函数采用两个可选参数–消息格式字符串和日期格式字符串。

  • logging.Formatter. __init__(* fmt = None datefmt = None *)

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

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

最后加上毫秒

消息格式字符串使用%(<dictionary key>)s样式的字符串替换;可能的键记录在LogRecord attributes中。

以下消息格式字符串将以人类可读的格式记录时间,消息的严重性以及消息的内容,该 Sequences 为:

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

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

Configuring Logging

程序员可以pass三种方式配置日志记录:

  • 使用调用上面列出的配置方法的 Python 代码显式创建 Logger,处理程序和格式化程序。

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

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

有关最后两个选项的参考文档,请参见Configuration functions。下面的示例使用 Python 代码配置一个非常简单的 Logger,一个控制台处理程序和一个简单的格式化程序:

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 模块创建的 Logger,处理程序和格式化程序几乎与上述示例相同,唯一的不同是对象的名称:

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 代码方法具有一些优势,主要是配置和代码的分离以及非编码器轻松修改日志记录属性的能力。

Warning

fileConfig()函数采用默认参数disable_existing_loggers,出于向后兼容的原因,该参数默认为True。这可能不是您想要的,因为这将导致禁用fileConfig()调用之前存在的所有 Logger,除非在配置中明确命名了它们(或祖先)。请参考参考文档以获取更多信息,并根据需要为该参数指定False

传递给dictConfig()的字典还可以使用键disable_existing_loggers指定布尔值,如果未在字典中明确指定,则默认将其解释为True。这会导致上述 Logger 禁用行为,这可能不是您想要的-在这种情况下,请为键明确提供False的值。

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

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

这是与上述相同配置的示例,采用基于新字典的 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

如果不提供任何配置会怎样?

如果未提供日志记录配置,则可能出现需要输出日志记录事件但找不到处理程序以输出事件的情况。在这种情况下,日志记录包的行为取决于 Python 版本。

对于 Python 2.x,其行为如下:

  • 如果* logging.raiseExceptions *为False(生产模式),则事件被静默删除。

  • 如果* logging.raiseExceptions *为True(开发模式),则会显示一条消息“找不到 LoggerX.Y.Z 的处理程序”。

配置库的日志记录

在开发使用日志记录的库时,应注意记录该库如何使用日志记录-例如,所用 Logger 的名称。还需要考虑其日志记录配置。如果正在使用的应用程序未配置日志记录,并且库代码进行了日志调用,则(如上一节所述)错误消息将被打印到sys.stderr

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

日志记录程序包NullHandler(自 Python 2.7 起)中包含了什么都不做的处理程序。可以将此处理程序的一个实例添加到库使用的日志记录名称空间的顶级 Logger 中(如果,如果要防止在没有日志记录配置的情况下将错误消息输出到sys.stderr,则为*)。如果所有库* foo *的记录都是使用名称与'foo.x','foo.x.y'等匹配的 Logger 完成的,则代码:

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

应该具有预期的效果。如果组织生产许多库,则指定的 Logger 名称可以是“ orgname.foo”,而不仅仅是“ foo”。

Note

强烈建议您 NullHandler 之外,不要添加任何其他处理程序到库的 Logger中。这是因为处理程序的配置是使用您的库的应用程序开发人员的特权。应用程序开发人员知道他们的目标受众以及最适合他们的应用程序的处理程序:如果在“幕后”添加处理程序,则很可能会干扰他们执行单元测试和交付适合其要求的日志的能力。

Logging Levels

下表中给出了日志记录级别的数值。如果您想定义自己的级别,并且需要它们具有相对于 sched 义级别的特定值,那么这些是最重要的。如果用相同的数值定义一个级别,它将覆盖 sched 义的值;sched 义名称丢失。

LevelNumeric value
CRITICAL50
ERROR40
WARNING30
INFO20
DEBUG10
NOTSET0

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

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

日志消息pass使用* handler 进行分配机制, handlers 是Handler类的子类的实例。处理程序负责确保记录的消息(以LogRecord的形式)finally到达特定的位置(或一组位置),这对于该消息的目标受众(例如finally用户,支持台人员,系统)是有用的 Management 员,开发人员)。处理程序将传递给特定目的地的LogRecord个实例。每个 Logger 可以具有零个,一个或多个与其关联的处理程序(passLoggeraddHandler()方法)。除了与 Logger 直接相关的任何处理程序外,还调用与 Logger 的所有祖先相关联的所有处理程序来调度消息(除非 Logger 的 propagate 标志设置为 false,否则传递到祖先处理程序停止)。

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

Custom Levels

定义自己的级别是可能的,但不是必须的,因为现有级别是根据实践经验选择的。但是,如果您确信需要自定义级别,则在执行此操作时应格外小心,如果正在开发库,定义自定义级别可能是一个非常糟糕的主意。这是因为,如果多个库作者都定义了自己的自定义级别,那么使用这样的多个库的日志输出可能对使用开发人员来说很难控制和/或解释,因为给定的数值可能意味着不同的含义用于不同的库。

Useful Handlers

除了基本的Handler类,还提供了许多有用的子类:

  • StreamHandler个实例将消息发送到流(类似文件的对象)。

  • FileHandler个实例将消息发送到磁盘文件。

  • BaseRotatingHandler是在特定点旋转日志文件的处理程序的 Base Class。它并不意味着要直接实例化。而是使用RotatingFileHandlerTimedRotatingFileHandler

  • RotatingFileHandler个实例将消息发送到磁盘文件,并支持最大日志文件大小和日志文件轮换。

  • TimedRotatingFileHandler个实例将消息发送到磁盘文件,并按一定的时间间隔旋转日志文件。

  • SocketHandler个实例将消息发送到 TCP/IP 套接字。

  • DatagramHandler个实例将消息发送到 UDP 套接字。

  • SMTPHandler个实例将邮件发送到指定的电子邮件地址。

  • SysLogHandler个实例会将消息发送到 Unix syslog 守护程序,该守护程序可能在远程计算机上。

  • NTEventLogHandler个实例将消息发送到 Windows NT/2000/XP 事件日志。

  • MemoryHandler实例将消息发送到内存中的缓冲区,只要满足特定条件,该缓冲区就会被刷新。

  • HTTPHandler实例使用GETPOST语义将消息发送到 HTTP 服务器。

  • WatchedFileHandler个实例监视它们正在登录的文件。如果文件更改,则使用文件名将其关闭并重新打开。该处理程序仅在类似 Unix 的系统上有用。 Windows 不支持所使用的基础机制。

  • NullHandler个实例不处理错误消息。希望使用日志记录但希望避免出现“找不到用于 LoggerXXX 的处理程序”消息的库开发人员会使用它们,如果库用户未配置日志记录,则会显示该消息。有关更多信息,请参见配置库的日志记录

2.7 版的新Function:NullHandler类。

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

记录的消息经过格式化,可以passFormatter类的实例进行显示。使用适合%运算符和字典的格式字符串初始化它们。

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

当基于 Logger 级别和/或处理程序级别的过滤还不够时,可以将Filter实例添加到LoggerHandler实例中(pass它们的addFilter()方法)。在决定进一步处理消息之前,Logger 和处理程序都请查阅其所有过滤器的权限。如果任何过滤器返回错误值,则不会进一步处理该消息。

基本的FilterFunction允许按特定的 Logger 名称进行过滤。如果使用此Function,则允许pass过滤器发送到命名 Logger 及其子级的消息,所有其他消息都将被丢弃。

记录期间引发异常

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

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

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

Note

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

使用任意对象作为消息

在前面的部分和示例中,假设记录事件时传递的消息是字符串。但是,这不是唯一的可能性。您可以传递任意对象作为消息,当日志系统需要将其转换为字符串表示形式时,将调用其str()方法。实际上,如果需要,您可以避免完全计算字符串表示形式-例如,SocketHandlerpass腌制并pass电线发送事件来发出事件。

Optimization

消息参数的格式将推迟到无法避免为止。但是,计算传递给日志记录方法的参数也可能很昂贵,并且如果 Logger 只会丢弃事件,则可能要避免这样做。要决定要做什么,您可以调用isEnabledFor()方法,该方法带有一个级别参数,并且如果事件是由 Logger 为该级别的调用创建的,则返回 true。您可以编写如下代码:

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

因此,如果 Logger 的阈值设置为DEBUG以上,则永远不会调用expensive_func1()expensive_func2()

Note

在某些情况下,isEnabledFor()本身可能会比您想要的价格昂贵(例如,对于深度嵌套的 Logger,其中仅在 Logger 层次结构中设置了显式级别)。在这种情况下(或如果您希望避免在紧循环中调用方法),可以将对isEnabledFor()的调用结果存储在本地或实例变量中,并使用该结果代替每次调用该方法。仅在应用程序运行时日志记录配置动态更改时才需要重新计算这样的缓存值(这并不常见)。

还可以针对需要对收集的日志信息进行更精确控制的特定应用程序进行其他优化。以下列出了您可以避免在日志 Logging 不需要的处理操作:

你不想收集的东西如何避免收集
有关从何处拨打电话的信息。logging._srcfile设置为None。这样避免了调用sys._getframe(),这可能有助于在 PyPy 之类的环境中加速您的代码(无法加速使用sys._getframe()的代码)。
Threading information.logging.logThreads设置为0
Process information.logging.logProcesses设置为0

另请注意,核心日志记录模块仅包括基本处理程序。如果您不导入logging.handlerslogging.config,则它们不会占用任何内存。

See also

伐木食谱