Python中的日志记录(Logging)

日志记录是一种存储有关脚本信息并跟踪发生的事件的方法。在 Python 中编写任何复杂的脚本时,日志记录对于在开发过程中调试软件至关重要。没有日志记录,在代码中找到问题的根源可能会非常耗时。

完成本教程后,您将了解:

  • 为什么我们要使用日志记录模块
  • 如何使用日志记录模块
  • 如何自定义日志记录机制

通过我的新书《Python for Machine Learning》开始您的项目,其中包括分步教程和所有示例的Python源代码文件。

让我们开始吧。

Python中的日志记录(Logging)
照片来源:ilaria88。保留部分权利。

教程概述

本教程分为四个部分;它们是

  • 日志记录的好处
  • 基本日志记录
  • 日志记录的高级配置
  • 日志记录用法的示例

日志记录的好处

你可能会问:“为什么不直接使用打印?”

当您运行算法并想确认它的行为是否符合预期时,很自然地会在战略性位置添加一些 print() 语句来显示程序的狀態。打印可以帮助调试更简单的脚本,但随着代码变得越来越复杂,打印缺乏日志记录所具有的灵活性和健壮性。

通过日志记录,您可以精确地知道日志调用的来源,区分消息之间的严重性,并将信息写入文件,而打印无法做到这一点。例如,我们可以打开或关闭大型程序某个模块的消息。我们还可以调整日志消息的详细程度,而无需更改大量代码。

基本日志记录

Python 有一个内置库 logging 用于此目的。创建“记录器”以记录您想看到的日志或信息非常简单。

Python 中的日志系统在分层命名空间和不同的严重性级别下运行。Python 脚本可以在命名空间下创建记录器,每次记录消息时,脚本都必须指定其严重性。根据为命名空间设置的处理程序,记录的消息可以去到不同的地方。最常见的是将消息简单地打印在屏幕上,就像无处不在的 print() 函数一样。当我们启动程序时,我们可以注册一个新的处理程序并设置处理程序将响应的严重性级别。

有 5 个不同的日志记录级别,表示日志的严重性,按严重性递增显示

  1. DEBUG
  2. INFO
  3. WARNING
  4. ERROR
  5. CRITICAL

下面显示了一个非常简单的日志记录示例,使用默认记录器或根记录器

这些将发出不同严重级别的日志消息。虽然有五行日志记录,但如果您运行此脚本,您可能只会看到三行输出,如下所示

这是因为根记录器默认只打印严重性级别为 WARNING 或更高级别的日志消息。但是,这样使用根记录器与使用 print() 函数没有什么区别。

根记录器的设置并非一成不变。我们可以配置根记录器将输出到特定文件,更改其默认严重性级别,并格式化输出。 这是一个例子

运行此脚本将在屏幕上不产生任何输出,但会在新创建的文件 file.log 中包含以下内容

logging.basicConfig() 的调用是为了修改根记录器。在我们的示例中,我们将处理程序设置为输出到文件而不是屏幕,调整日志记录级别,使所有级别为 DEBUG 或更高级别的日志消息都能得到处理,并且还更改了日志消息输出的格式,以包含时间。

请注意,现在所有五个消息都已输出,因此根记录器记录的默认级别现在是“DEBUG”。可以 在日志记录文档中找到用于格式化输出的日志记录属性(例如 %(asctime)s)。

虽然有默认的记录器,但我们通常希望创建和使用可以单独配置的其他记录器。这是因为我们可能希望为不同的记录器设置不同的严重性级别或格式。可以使用以下方法创建新记录器

在内部,记录器按层次结构组织。使用以下方式创建的记录器

将被创建在名称为“parent”的记录器下,该记录器又在根记录器下。使用字符串中的点表示子记录器是父记录器的子代。在上面的例子中,一个名为“parent.child”的记录器被创建,同时隐式地创建了一个名为 "parent" 的记录器。

创建后,子记录器具有其父记录器的所有属性,直到重新配置。我们可以通过以下示例来演示这一点

此代码片段将只输出一行

这是由 StreamHandler 对象及其自定义格式字符串创建的。这发生在重新配置 parent 记录器之后,因为否则,根记录器的配置将占主导地位,并且不会打印 INFO 级别的消息。

日志记录的高级配置

正如我们在最后一个示例中所看到的,我们可以配置我们创建的记录器。

级别阈值

与根记录器的基本配置一样,我们也可以配置记录器的输出目标、严重性级别和格式。以下是如何将记录器的**级别阈值**设置为 INFO

现在,严重性级别为 INFO 或更高的命令将被 parent_logger 记录。但如果仅此而已,您将看不到任何来自 parent_logger.info("messages") 的内容,因为该记录器没有分配**处理程序**。事实上,根记录器也没有任何处理程序,除非您使用 logging.basicConfig() 设置一个。

日志处理程序

我们可以使用处理程序配置记录器的输出目标。处理程序负责将日志消息发送到正确的目的地。有几种类型处理程序;最常见的是 StreamHandlerFileHandler。使用 StreamHandler,记录器将输出到终端,而使用 FileHandler,记录器将输出到特定文件。

以下是使用 StreamHandler 将日志输出到终端的示例

在上面的代码中,创建了两个处理程序:一个由 logging.basicConfig() 为根记录器创建的 FileHandler,以及一个为 parent 记录器创建的 StreamHandler

请注意,即使有一个将日志发送到终端的 StreamHandler,来自 parent 记录器的日志仍然会被发送到 file.log,因为它属于根记录器,并且根记录器的处理程序对子日志消息也同样有效。我们可以看到发送到终端的日志包含 INFO 级别及以上级别的消息

但是,终端输出没有格式化,因为我们还没有使用 Formatter。然而,file.log 的日志已经设置了 Formatter,并且将如下所示

或者,我们可以在上面 parent_logger 的示例中使用 FileHandler

上面的示例演示了您还可以设置处理程序的级别。parent_fhandler 的级别会过滤掉非 WARNING 级别或更高级别的日志。但是,如果您将处理程序的级别设置为 DEBUG,那将与不设置级别相同,因为 DEBUG 日志将被记录器的级别(即 INFO)过滤掉。

在这种情况下,parent.log 的输出是

file.log 的输出与之前相同。总之,当记录器记录日志消息时,

  1. 记录器的级别将决定该消息是否足够严重以被处理。如果未设置记录器的级别,则将使用其父记录器(最终是根记录器)的级别进行此考虑。
  2. 如果日志消息将被处理,**所有**沿记录器层级(直至根记录器)添加的处理程序都将收到消息的副本。每个处理程序的级别将决定该特定处理程序是否应响应此消息。

格式化程序

为了配置记录器的格式,我们使用 Formatter。它允许我们设置日志的格式,类似于我们在根记录器的 basicConfig() 中所做的。以下是如何为我们的处理程序添加格式化程序

首先,我们创建一个格式化程序,然后将我们的处理程序设置为使用该格式化程序。如果我们愿意,我们可以创建多个不同的记录器、处理程序和格式化程序,以便我们可以根据自己的喜好进行混合搭配。

在此示例中,parent.log 将具有

并且与根记录器关联的 file.log 将具有

下面是来自 日志记录模块文档的可视化日志记录器、处理程序和格式化程序的流程图:

日志记录模块中日志记录器和处理程序的流程图

日志记录用法示例

让我们以 Nadam 算法为例

最简单的用例是使用日志记录来替换print()函数。我们可以进行如下更改:首先,在运行任何代码之前创建一个名为nadam的记录器,并为其分配一个StreamHandler

我们必须分配一个处理程序,因为我们从未配置过根记录器,而这将是创建的唯一处理程序。然后,在nadam()函数中,我们重新创建了一个名为nadam的记录器,但由于它已经被设置好了,所以级别和处理程序都得以保留。在nadam()的每个外部for循环结束时,我们将print()函数替换为logger.info(),这样日志消息就会被日志记录系统处理。

如果我们对Nadam算法的更深层机制感兴趣,我们可以添加更多的日志。以下是完整的代码:

我们准备了两个级别的记录器,nadamnadam.iter,并将它们设置为不同的级别。在nadam()的内部循环中,我们使用子记录器打印一些内部变量。运行此脚本时,它将打印以下内容:

设置不同的记录器不仅允许我们设置不同的级别或处理程序,还可以通过查看消息中记录器的名称来区分日志消息的来源。

事实上,一个方便的技巧是创建一个日志装饰器,并将该装饰器应用于某些函数。我们可以跟踪该函数被调用的每一次。例如,我们在下面创建了一个装饰器,并将其应用于objective()derivative()函数。

然后我们将在日志中看到以下内容:

其中,我们可以通过nadam.function记录器打印的消息看到对这两个函数每次调用的参数和返回值。

想开始学习机器学习 Python 吗?

立即参加我为期7天的免费电子邮件速成课程(附示例代码)。

点击注册,同时获得该课程的免费PDF电子书版本。

随着日志消息越来越多,终端屏幕会变得非常繁忙。使日志更容易查看问题的一种方法是使用colorama模块用颜色高亮日志。您需要先安装该模块。

以下是如何将colorama模块与logging模块一起使用来更改日志颜色和文本亮度的示例:

从终端中,您将看到以下内容:

其中colorama模块中的ForeBackStyle控制着打印文本的前景、背景和亮度样式。这利用了ANSI转义字符,并且只在支持ANSI的终端上有效。因此,这不适用于将日志记录到文本文件。

事实上,我们可以从Formatter类派生出

并使用它而不是logging.Formatter。以下是我们如何进一步修改Nadam示例以添加颜色:

如果在支持的终端上运行它,我们将看到以下输出:

请注意,彩色输出可以帮助我们更轻松地发现任何异常行为。日志记录有助于调试,还可以让我们通过更改几行代码来轻松控制我们想要看到的详细程度。

进一步阅读

如果您想深入了解,本节提供了更多关于该主题的资源。

API

文章

总结

在本教程中,您学习了如何在脚本中实现日志记录技术。

具体来说,你学到了:

  • 基本和高级日志记录技术
  • 如何将日志记录应用于脚本及其好处

掌握机器学习 Python!

Python For Machine Learning

更自信地用 Python 编写代码

...从学习实用的 Python 技巧开始

在我的新电子书中探索如何实现
用于机器学习的 Python

它提供自学教程数百个可运行的代码,为您提供包括以下技能:
调试性能分析鸭子类型装饰器部署等等...

向您展示高级 Python 工具箱,用于
您的项目


查看内容

4 条关于Python 日志记录的回复

  1. a2htray 2022 年 5 月 30 日下午 6:14 #

    对 bee 开发者来说的好文章

    • James Carmichael 2022 年 5 月 31 日上午 9:49 #

      感谢您的反馈!

  2. George 2022 年 6 月 30 日下午 7:26 #

    Daniel 和 James,很棒的文章!一定会用起来!

    不过有一个问题:在您的文章中我从没见过 f-string 的用法。有什么原因吗?我觉得 f-string 比其他替代方法可读性要强得多!

    诚挚的问候
    George

    • James Carmichael 2022 年 7 月 1 日下午 12:02 #

      George,你好……谢谢你的反馈!这只是一个偏好的问题。我同意如果使用得当,它们会非常有益。

留下回复

Machine Learning Mastery 是 Guiding Tech Media 的一部分,Guiding Tech Media 是一家领先的数字媒体出版商,专注于帮助人们了解技术。访问我们的公司网站以了解更多关于我们的使命和团队的信息。