记录howto

作者

Vinay Sajip<Vinay_Sajip at Red Dove Dot com>

基本日志教程

日志记录是跟踪某些软件运行时发生的事件的一种方法。软件开发人员将日志记录调用添加到代码中,以指示发生了某些事件。事件由描述性消息描述,描述性消息可以选择性地包含变量数据(即,对于事件的每次出现,数据可能不同)。事件也具有开发人员将其归因于事件的重要性;其重要性也可以称为 level严重程度 .

何时使用日志记录

日志记录为简单的日志记录使用提供了一组方便的函数。这些是 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', encoding='utf-8', level=logging.DEBUG)
logging.debug('This message should go to the log file')
logging.info('So should this')
logging.warning('And this, too')
logging.error('And non-ASCII stuff, too, like Øresund and Malmö')

在 3.9 版更改: 这个 编码 已添加参数。在早期的Python版本中,或者如果未指定,则使用的编码是 open() . 虽然上面的示例中没有显示,但是 错误 现在还可以传递参数,该参数决定如何处理编码错误。有关可用值和默认值,请参阅 open() .

现在,如果我们打开文件并查看我们拥有的内容,我们应该找到日志消息:

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

此示例还显示了如何设置日志级别,该级别用作跟踪的阈值。在这种情况下,因为我们将阈值设置为 DEBUG ,所有消息都已打印。

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

--log=INFO

您已经为传递了参数的值 --log 在某个变量中 日志级别 您可以使用:

getattr(logging, loglevel.upper())

得到你要传递给的值 basicConfig() 通过 level 参数。您可能希望错误地检查任何用户输入值,可能如下例所示:

# 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() 应该来了 before 任何调用 debug()info() 等等,因为它是一个一次性的简单配置工具,所以只有第一个调用实际上可以做任何事情:随后的调用实际上是没有操作的。

如果多次运行上述脚本,则连续运行的消息将附加到文件中。 example.log . 如果希望每次运行都重新开始,而不记住以前运行的消息,则可以指定 文件格式 参数,方法是将上面示例中的调用更改为::

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 . 注意,对于这个简单的使用模式,通过查看日志文件,您不会知道, 在哪里? 在应用程序中,除了查看事件描述之外,您的消息来自。如果您想跟踪消息的位置,您需要参考教程级别之外的文档——请参见 高级日志教程 .

记录变量数据

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

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

注意,在前面的例子中出现的“根”已经消失了。有关可以在格式字符串中显示的全部内容,可以参考文档 日志记录属性 但是为了简单的使用,您只需要 平名 (严重性) 消息 (事件描述,包括变量数据)并可能在事件发生时显示。这将在下一节中描述。

在消息中显示日期/时间

要显示事件的日期和时间,您可以将“%(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 . 如果需要对日期/时间的格式进行更多控制,请提供 日期fmt 参数 basicConfig ,如本例所示:

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.

的格式 日期fmt 参数与支持的参数相同 time.strftime() .

下一步

基本教程到此结束。它应该足以让您启动并运行日志记录。日志软件包提供了更多的功能,但是要充分利用这些功能,您需要在阅读以下部分时投入更多的时间。如果你准备好了,拿一些你最类似于的饮料继续。

如果您的日志记录需求很简单,那么使用上面的示例将日志记录合并到您自己的脚本中,如果您遇到问题或不理解某些内容,请在comp.lang.python usenet组上发布一个问题(可从https://groups.google.com/forum/获得)。论坛/comp.lang.python),您应该在很长时间内获得帮助。

还在这里吗?您可以继续阅读接下来的几节,其中提供了比上面的基础教程稍微高级/深入的教程。之后,你可以看看 日志秘诀 .

高级日志教程

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

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

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

  • 过滤器提供了一个更细粒度的工具,用于确定要输出哪些日志记录。

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

日志事件信息在 LogRecord 实例。

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

命名记录器时要使用的一个好习惯是在每个使用日志的模块中使用模块级记录器,其名称如下:

logger = logging.getLogger(__name__)

这意味着日志程序名称跟踪包/模块的层次结构,并且从日志程序名称中记录事件的位置可以直观地看到。

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

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

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

默认格式由设置 basicConfig() 因为消息是:

severity:logger name:message

您可以通过将格式字符串传递给 basicConfig()格式 关键字参数。有关如何构造格式字符串的所有选项,请参见 格式化程序对象 .

测井流量

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

../_images/logging_flow.png

伐木工人

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 默认情况下)。在决定是否处理事件时,记录器的有效级别用于确定事件是否传递给记录器的处理程序。

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

处理程序

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

标准库包含相当多的处理程序类型(请参见 有用的处理器 );教程主要使用 StreamHandlerFileHandler 在它的例子中。

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

  • 这个 setLevel() 方法和logger对象中的方法一样,指定将调度到适当目标的最低严重性。为什么有两个 setLevel() 方法?记录器中设置的级别确定将传递给其处理程序的消息的严重性。每个处理程序中的级别集决定处理程序将发送哪些消息。

  • setFormatter() 为该处理程序选择要使用的格式化程序对象。

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

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

使用格式器

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

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

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

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

with the milliseconds tacked on at the end. The style is one of %, '{' or '$'. If one of these is not specified, then '%' will be used.

如果 style 为“%”,消息格式字符串使用 %(<dictionary key>)s 样式化字符串替换;可能的键记录在 日志记录属性 . 如果样式为“”,则假定消息格式字符串与 str.format() (使用关键字参数),而如果样式为“$”,则消息格式字符串应符合 string.Template.substitute() .

在 3.2 版更改: 增加了 style 参数。

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

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

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

配置日志记录

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

  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
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代码构造字典,在套接字上以酸洗的形式接收它,或者使用对您的应用程序有意义的任何方法。

下面是一个与上述配置相同的示例,采用基于字典的新方法的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的处理程序”。

在Python3.2及更高版本中,行为如下:

  • 使用存储在 logging.lastResort . 此内部处理程序不与任何记录器关联,其作用类似于 StreamHandler 它将事件描述消息写入的当前值 sys.stderr (因此,关于任何可能有效的重定向)。没有对消息进行格式化-只打印裸露的事件描述消息。处理程序的级别设置为 WARNING ,因此,将输出具有此严重性和更严重性的所有事件。

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

为库配置日志记录

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

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

日志包中包含“不做任何事情”处理程序: 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 class。当记录器决定实际记录事件时, LogRecord 实例是根据日志消息创建的。

日志消息通过使用 handlers ,这是 Handler class。处理程序负责确保记录的消息(以 LogRecord )最后出现在一个特定的位置(或一组位置),这对消息的目标受众(如最终用户、支持人员、系统管理员、开发人员)很有用。已传递处理程序 LogRecord 用于特定目的地的实例。每个记录器可以有零个、一个或多个与其关联的处理程序(通过 addHandler() 方法 Logger )除了与记录器直接关联的任何处理程序之外, 与记录器的所有祖先关联的所有处理程序 调用以发送消息(除非 传播 记录器的标志设置为假值,此时停止传递给祖先处理程序)。

与记录器一样,处理程序也可以具有与其关联的级别。处理程序的级别充当过滤器的方式与记录器的级别相同。如果处理程序决定实际分派事件,则 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 实例使用以下任一方法向HTTP服务器发送消息 GETPOST 语义学。

  13. WatchedFileHandler 实例监视它们正在登录的文件。如果文件发生更改,将关闭该文件并使用文件名重新打开。此处理程序仅在类Unix系统上有用;Windows不支持所使用的底层机制。

  14. QueueHandler 实例将消息发送到队列,例如 queuemultiprocessing 模块。

  15. NullHandler 实例不处理错误消息。它们由希望使用日志记录的库开发人员使用,但希望避免出现“找不到日志记录器XXX的处理程序”消息,如果库用户未配置日志记录,则可以显示该消息。见 为库配置日志记录 更多信息。

3.1 新版功能: 这个 NullHandler class。

3.2 新版功能: 这个 QueueHandler class。

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

记录的消息经过 Formatter class。它们是用适合与%运算符和字典一起使用的格式字符串初始化的。

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

当基于记录器级别和/或处理程序级别的筛选不够时,的实例 Filter 可添加到两者 LoggerHandler 实例(通过它们 addFilter() 方法)。在决定进一步处理消息之前,日志记录器和处理程序都会查询其所有筛选器以获得权限。如果任何过滤器返回错误值,则不会进一步处理消息。

基本 Filter 功能允许按特定记录器名称进行筛选。如果使用此功能,则允许通过筛选器发送到命名记录器及其子级的消息,并删除所有其他消息。

日志记录期间引发的异常

日志记录包的设计目的是吞咽在生产日志记录时发生的异常。这样,在处理日志事件时发生的错误(例如日志配置错误、网络或其他类似错误)不会导致使用日志的应用程序过早终止。

SystemExitKeyboardInterrupt 例外永远不会被吞没。其他例外情况 emit() A方法 Handler 子类传递给它的 handleError() 方法。

默认实现 handleError() 在里面 Handler 检查模块级变量, raiseExceptions ,是设定的。如果设置,则会将跟踪打印到 sys.stderr . 如果未设置,则忽略例外。

注解

默认值为 raiseExceptionsTrue . 这是因为在开发过程中,您通常希望收到发生任何异常的通知。有人建议你去 raiseExceptionsFalse 用于生产用途。

将任意对象用作消息

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

优化

消息参数的格式将被延迟,直到无法避免为止。但是,计算传递给日志记录方法的参数也很昂贵,如果日志记录程序只是丢弃了您的事件,您可能希望避免这样做。要决定要做什么,可以调用 isEnabledFor() 方法,该方法接受一个级别参数,如果日志记录器将为该级别的调用创建事件,则返回true。您可以这样编写代码:

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

如果记录器的阈值设置高于 DEBUG 呼唤 expensive_func1()expensive_func2() 从来没有制造过。

注解

在某些情况下, isEnabledFor() 本身可能比您想要的更昂贵(例如,对于深度嵌套的记录器,显式级别仅在记录器层次结构中设置得更高)。在这种情况下(或者如果希望避免在紧密循环中调用方法),可以缓存对 isEnabledFor() 在一个局部变量或实例变量中,并使用该变量而不是每次调用该方法。只有当日志配置在应用程序运行时发生动态更改时(这并非所有常见情况),才需要重新计算此类缓存值。

还可以为特定应用程序进行其他优化,这些应用程序需要对收集的日志信息进行更精确的控制。下面列出了在日志记录过程中可以避免处理而不需要做的事情:

你不想收集的东西

如何避免收集

有关从何处拨调用的信息。

集合 logging._srcfileNone .这样可以避免呼叫 sys._getframe() ,这可能有助于加快Pypy等环境中的代码速度(Pypy无法加快使用 sys._getframe()

线程信息。

集合 logging.logThreadsFalse .

当前进程ID (os.getpid()

集合 logging.logProcessesFalse .

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

集合 logging.logMultiprocessingFalse .

还要注意,核心日志模块只包括基本的处理程序。如果不导入 logging.handlerslogging.config 它们不会占用任何内存。

参见

模块 logging

日志模块的API引用。

模块 logging.config

日志模块的配置API。

模块 logging.handlers

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

A logging cookbook