日志记录 HOWTO — Python 文档
日志 HOWTO
- 作者
- 维奈沙吉普
基本日志教程
日志记录是一种跟踪某些软件运行时发生的事件的方法。 该软件的开发人员将日志调用添加到他们的代码中,以指示发生了某些事件。 事件由描述性消息描述,该消息可以选择性地包含可变数据(即 事件的每次发生可能不同的数据)。 事件也具有开发者认为事件的重要性; 重要性也可以称为 级别 或 严重性 。
何时使用日志记录
Logging 为简单的日志使用提供了一组方便的函数。 它们是 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() 使用的默认值。 虽然上面的例子中没有显示,但现在也可以传递一个 errors 参数,它决定了如何处理编码错误。 有关可用值和默认值,请参阅 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
并且您在某个变量 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)
输出将与以前相同,但不再附加日志文件,因此先前运行的消息将丢失。
从多个模块记录
如果您的程序由多个模块组成,以下是如何组织登录的示例:
# 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!
如您所见,将变量数据合并到事件描述消息中使用了旧的 %-s 字符串格式类型。 这是为了向后兼容:日志包早于更新的格式选项,例如 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属性的文档,但是为了简单的使用,只需要levelname(严重性),[ X201X]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 组(可在 [ X242X]https://groups.google.com/forum/#!forum/comp.lang.python),您应该很快就会得到帮助。
还在? 您可以继续阅读接下来的几节,其中提供了比上述基本教程稍微高级/深入的教程。 之后,您可以查看Logging Cookbook。
高级日志教程
日志库采用模块化方法并提供几类组件:记录器、处理程序、过滤器和格式化程序。
- 记录器公开应用程序代码直接使用的接口。
- 处理程序将日志记录(由记录器创建)发送到适当的目的地。
- 过滤器提供了一种更细粒度的设施,用于确定要输出哪些日志记录。
- 格式化程序指定最终输出中日志记录的布局。
日志事件信息在 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() 来更改此设置。 有关如何构造格式字符串的所有选项,请参阅 Formatter Objects。
记录器
Logger 对象有三重工作。 首先,它们向应用程序代码公开了几种方法,以便应用程序可以在运行时记录消息。 其次,记录器对象根据严重性(默认过滤工具)或过滤器对象确定要对哪些日志消息进行操作。 第三,记录器对象将相关的日志消息传递给所有感兴趣的日志处理程序。
记录器对象上使用最广泛的方法分为两类:配置和消息发送。
这些是最常见的配置方法:
- Logger.setLevel() 指定记录器将处理的最低严重性日志消息,其中 debug 是最低的内置严重性级别,critical 是最高的内置严重性。 例如,如果严重级别为 INFO,则记录器将仅处理 INFO、WARNING、ERROR 和 CRITICAL 消息,而将忽略 DEBUG 消息。
- Logger.addHandler() 和 Logger.removeHandler() 从记录器对象中添加和删除处理程序对象。 Handlers 中更详细地介绍了处理程序。
- Logger.addFilter() 和 Logger.removeFilter() 从记录器对象中添加和删除过滤器对象。 在 过滤器对象 中更详细地介绍了过滤器。
您不需要总是在您创建的每个记录器上调用这些方法。 请参阅本节的最后两段。
配置记录器对象后,以下方法会创建日志消息:
- Logger.debug()、Logger.info()、Logger.warning()、Logger.error()和[ X105X]Logger.critical() 都创建了带有消息和级别的日志记录,该级别对应于它们各自的方法名称。 该消息实际上是一个格式字符串,其中可能包含
%s
、%d
、%f
等标准字符串替换语法。 它们的其余参数是与消息中的替换字段对应的对象列表。 对于**kwargs
,日志方法只关心一个关键字exc_info
,并用它来决定是否记录异常信息。 - Logger.exception() 创建类似于 Logger.error() 的日志消息。 不同之处在于 Logger.exception() 连同它一起转储堆栈跟踪。 仅从异常处理程序调用此方法。
- Logger.log() 将日志级别作为显式参数。 与使用上面列出的日志级别便利方法相比,这对于记录消息要详细一些,但这是在自定义日志级别进行记录的方法。
getLogger() 如果提供,则返回对具有指定名称的记录器实例的引用,否则返回 root
。 名称是以句点分隔的层次结构。 多次调用具有相同名称的 getLogger() 将返回对同一记录器对象的引用。 在层次列表中更靠后的记录器是列表中更高的记录器的孩子。 例如,给定一个名称为 foo
的记录器,名称为 foo.bar
、foo.bar.baz
和 foo.bam
的记录器都是 [ X139X]。
记录器有有效等级的概念。 如果未在记录器上明确设置级别,则使用其父级的级别作为其有效级别。 如果父级没有明确的级别集,则检查 its 父级,依此类推 - 搜索所有祖先,直到找到明确设置的级别。 根记录器始终具有明确的级别集(默认为 WARNING
)。 在决定是否处理一个事件时,通过logger的有效级别来决定该事件是否被传递给了logger的handler。
子记录器将消息传播到与其祖先记录器关联的处理程序。 因此,没有必要为应用程序使用的所有记录器定义和配置处理程序。 为顶级记录器配置处理程序并根据需要创建子记录器就足够了。 (但是,您可以通过将记录器的 propagate 属性设置为 False
来关闭传播。)
处理程序
Handler 对象负责将适当的日志消息(基于日志消息的严重性)分派到处理程序的指定目的地。 Logger 对象可以使用 addHandler() 方法向自身添加零个或多个处理程序对象。 作为示例场景,应用程序可能希望将所有日志消息发送到日志文件,将所有错误或更高级别的日志消息发送到标准输出,并将所有关键消息发送到电子邮件地址。 此场景需要三个单独的处理程序,其中每个处理程序负责将特定严重性的消息发送到特定位置。
标准库包括相当多的处理程序类型(参见 有用的处理程序 ); 本教程在其示例中主要使用 StreamHandler 和 FileHandler。
应用程序开发人员关注的处理程序中的方法很少。 与使用内置处理程序对象(即,不创建自定义处理程序)的应用程序开发人员相关的唯一处理程序方法是以下配置方法:
- setLevel() 方法,就像在记录器对象中一样,指定将被分派到适当目的地的最低严重性。 为什么有两种
setLevel()
方法? 记录器中设置的级别决定了它将传递给其处理程序的消息的严重性。 每个处理程序中设置的级别决定了处理程序将发送哪些消息。 - setFormatter() 选择一个 Formatter 对象供此处理程序使用。
- addFilter() 和 removeFilter() 分别在处理程序上配置和解除配置过滤器对象。
应用程序代码不应直接实例化和使用 Handler 的实例。 相反,Handler 类是一个基类,它定义了所有处理程序应该具有的接口,并建立了一些子类可以使用(或覆盖)的默认行为。
格式化程序
格式化程序对象配置日志消息的最终顺序、结构和内容。 与基本的 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()[ 相同签名的函数X230X]。 要为所有格式化程序更改它,例如,如果您希望所有记录时间都以 GMT 显示,请在 Formatter 类中设置 converter
属性(GMT 显示为 time.gmtime
)。
配置日志
程序员可以通过三种方式配置日志记录:
- 使用调用上面列出的配置方法的 Python 代码显式创建记录器、处理程序和格式化程序。
- 创建日志配置文件并使用 fileConfig() 函数读取它。
- 创建配置信息字典并将其传递给 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
值的密钥。
请注意,配置文件中引用的类名需要相对于日志记录模块,或者可以使用正常导入机制解析的绝对值。 因此,您可以使用 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.raiseExceptions 是
False
(生产模式),则事件将被静默删除。 - 如果 logging.raiseExceptions 是
True
(开发模式),则会打印一次消息“找不到记录器 XYZ 的处理程序”。
在 Python 3.2 及更高版本中,行为如下:
- 该事件使用存储在
logging.lastResort
中的“最后的处理程序”输出。 此内部处理程序不与任何记录器关联,其作用类似于 StreamHandler,它将事件描述消息写入sys.stderr
的当前值(因此尊重可能有效的任何重定向)。 没有对消息进行格式化 - 只打印裸露的事件描述消息。 处理程序的级别设置为WARNING
,因此将输出此级别和更高严重性的所有事件。
要获得 3.2 之前的行为,可以将 logging.lastResort
设置为 None
。
为库配置日志记录
在开发使用日志记录的库时,您应该注意记录库如何使用日志记录 - 例如,使用的记录器的名称。 还需要考虑其日志记录配置。 如果使用的应用程序不使用日志记录,并且库代码进行日志记录调用,那么(如上一节所述)严重性为 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 类的实例。 当记录器决定实际记录事件时,会根据记录消息创建 LogRecord 实例。
日志消息通过使用 处理程序 进行调度机制,这些处理程序是 处理程序 类的子类的实例。 处理程序负责确保记录的消息(以 LogRecord 的形式)在特定位置(或一组位置)结束,这对于该消息的目标受众(例如最终用户、支持人员、系统管理员、开发人员)。 处理程序通过 LogRecord 用于特定目的地的实例。 每个记录器可以有零个、一个或多个与之关联的处理程序(通过 Logger 的 addHandler() 方法)。 除了与记录器直接关联的任何处理程序外, 与记录器 的所有祖先关联的所有处理程序都被调用以分派消息(除非记录器的 propagate 标志设置为false 值,此时停止传递给祖先处理程序)。
就像记录器一样,处理程序可以具有与之关联的级别。 处理程序的级别以与记录器级别相同的方式充当过滤器。 如果处理程序决定实际调度事件,则使用 emit() 方法将消息发送到其目的地。 Handler 的大多数用户定义子类将需要覆盖此 emit()。
自定义级别
定义您自己的级别是可能的,但不是必需的,因为现有级别是根据实践经验选择的。 但是,如果您确信需要自定义级别,则在执行此操作时应格外小心,如果您正在开发库 ,则定义自定义级别可能是 一个非常糟糕的主意。 这是因为如果多个库作者都定义了他们自己的自定义级别,那么来自这些一起使用的多个库的日志输出有可能让使用开发人员难以控制和/或解释,因为给定的数值可能意味着不同的东西对于不同的库。
有用的处理程序
除了基本的 Handler 类之外,还提供了许多有用的子类:
- StreamHandler 实例将消息发送到流(类文件对象)。
- FileHandler 实例将消息发送到磁盘文件。
- BaseRotatingHandler 是在某个点轮换日志文件的处理程序的基类。 它并不意味着直接实例化。 相反,使用 RotatingFileHandler 或 TimedRotatingFileHandler。
- RotatingFileHandler 实例将消息发送到磁盘文件,支持最大日志文件大小和日志文件轮换。
- TimedRotatingFileHandler 实例将消息发送到磁盘文件,以特定的时间间隔轮换日志文件。
- SocketHandler 实例向 TCP/IP 套接字发送消息。 从 3.4 开始,还支持 Unix 域套接字。
- DatagramHandler 实例向 UDP 套接字发送消息。 从 3.4 开始,还支持 Unix 域套接字。
- SMTPHandler 实例将消息发送到指定的电子邮件地址。
- SysLogHandler 实例将消息发送到 Unix syslog 守护进程,可能在远程机器上。
- NTEventLogHandler 实例将消息发送到 Windows NT/2000/XP 事件日志。
- MemoryHandler 实例将消息发送到内存中的缓冲区,只要满足特定条件就会刷新该缓冲区。
- HTTPHandler 实例使用
GET
或POST
语义向 HTTP 服务器发送消息。 - WatchedFileHandler 实例监视他们正在登录的文件。 如果文件发生更改,则会关闭并使用文件名重新打开。 这个处理程序只在类 Unix 系统上有用; Windows 不支持所使用的底层机制。
- QueueHandler 实例将消息发送到队列,例如在 queue 或 multiprocessing 模块中实现的那些。
- NullHandler 实例对错误消息不做任何处理。 它们由想要使用日志记录的库开发人员使用,但希望避免在库用户未配置日志记录时显示的“找不到记录器 XXX 的处理程序”消息。 有关详细信息,请参阅 为库配置日志记录 。
3.1 新功能: NullHandler 类。
3.2 新功能:QueueHandler 类。
NullHandler、StreamHandler 和 FileHandler 类在核心日志记录包中定义。 其他处理程序在子模块 logging.handlers 中定义。 (还有另一个子模块 logging.config,用于配置功能。)
记录的消息被格式化以通过 Formatter 类的实例进行展示。 它们使用适合与 % o 运算符和字典一起使用的格式字符串进行初始化。
要批量格式化多个消息,可以使用 BufferingFormatter
的实例。 除了格式字符串(应用于批处理中的每条消息)之外,还提供了标头和尾标格式字符串。
当基于记录器级别和/或处理程序级别的过滤不够时,可以将 Filter 的实例添加到 Logger 和 Handler 实例(通过它们的 ]addFilter() 方法)。 在决定进一步处理消息之前,记录器和处理程序都要咨询他们所有的过滤器以获得许可。 如果任何过滤器返回 false 值,则不会进一步处理该消息。
基本的 Filter 功能允许按特定记录器名称进行过滤。 如果使用此功能,则发送到命名记录器及其子记录器的消息将被允许通过过滤器,而所有其他记录器将被丢弃。
记录期间引发的异常
logging 包旨在吞下在生产中登录时发生的异常。 这是为了在处理日志事件时发生的错误——例如日志配置错误、网络或其他类似错误——不会导致使用日志的应用程序过早终止。
SystemExit 和 KeyboardInterrupt 异常永远不会被吞下。 在 Handler 子类的 emit() 方法期间发生的其他异常被传递给它的 handleError() 方法。
Handler 中 handleError() 的默认实现检查是否设置了模块级变量 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_func1()
和 expensive_func2()
。
笔记
在某些情况下, isEnabledFor() 本身可能比您想要的更昂贵(例如 对于深度嵌套的记录器,其中显式级别仅在记录器层次结构中设置较高)。 在这种情况下(或者如果您想避免在紧密循环中调用方法),您可以将调用 isEnabledFor() 的结果缓存在本地或实例变量中,并使用它而不是调用方法每次。 只有在应用程序运行时日志配置动态更改时,才需要重新计算这样的缓存值(这并不常见)。
对于需要更精确控制收集的日志信息的特定应用程序,还可以进行其他优化。 以下是您可以做的事情列表,以避免在不需要的日志记录期间进行处理:
你不想收集的东西 | 如何避免收集它 |
---|---|
有关从何处拨打电话的信息。 | 将 logging._srcfile 设置为 None 。 这避免了调用 sys._getframe(),这可能有助于在 PyPy 等环境中加速代码(无法加速使用 sys._getframe() 的代码)。
|
线程信息。 | 将 logging.logThreads 设置为 False 。
|
当前进程 ID (os.getpid()) | 将 logging.logProcesses 设置为 False 。
|
使用 multiprocessing 管理多个进程时的当前进程名称。
|
将 logging.logMultiprocessing 设置为 False 。
|
另请注意,核心日志记录模块仅包含基本处理程序。 如果不导入 logging.handlers 和 logging.config,它们不会占用任何内存。