一文搞懂 Python logging 模块的应用

简介

logging 模块是 Python 标准库的一部分,用于跟踪软件运行时发生的事件。您可以在代码中添加日志调用以指示发生了哪些事件。

logging 模块允许进行诊断日志记录,记录与应用程序操作相关的事件,以及记录用户交易事件以进行分析的审计日志。它特别用于将事件记录到文件中。

先决条件

您应该已经安装了 Python 3,并在计算机或服务器上设置了编程环境。如果您尚未设置编程环境,可以参考本地编程环境或适合您操作系统(Ubuntu、CentOS、Debian 等)的服务器编程环境的安装和设置指南。

为什么使用 logging 模块

logging 模块记录程序内发生的事件,使得可以查看与软件运行时发生的任何事件相关的输出。

您可能更习惯于使用 print() 语句来检查事件是否发生。print() 语句确实提供了一种基本的调试代码以解决问题的方法。虽然在代码中嵌入 print() 语句可以跟踪执行流程和程序的当前状态,但与使用 logging 模块相比,这种解决方案在可维护性上存在一些问题:

  • 很难区分调试输出和正常程序输出,因为两者混在一起
  • 在代码中分散使用 print() 语句时,没有有效的方法来禁用提供调试输出的语句
  • 在调试完成后,删除所有 print() 语句变得困难
  • 没有包含可用的诊断信息的日志记录

习惯在代码中使用 logging 模块是个好主意,因为这更适合于超出小型 Python 脚本范围的应用程序,并提供了一种可持续的调试方法。

由于日志可以显示应用程序开发过程中的行为和错误,它们还可以为您提供应用程序开发过程中正在发生的情况的更全面的图像。

将调试消息打印到控制台

如果您习惯使用 print() 语句来查看程序中发生的情况,您可能习惯于看到定义类并实例化对象的程序生成类似以下内容:

class Pizza():
    def __init__(self, name, price):
        self.name = name
        self.price = price
        print("Pizza created: {} (${})".format(self.name, self.price))

    def make(self, quantity=1):
        print("Made {} {} pizza(s)".format(quantity, self.name))

    def eat(self, quantity=1):
        print("Ate {} pizza(s)".format(quantity, self.name))

pizza_01 = Pizza("artichoke", 15)
pizza_01.make()
pizza_01.eat()

pizza_02 = Pizza("margherita", 12)
pizza_02.make(2)
pizza_02.eat()

以上代码中有一个 __init__ 方法来定义 Pizza 类的 nameprice。然后有两个方法,一个叫做 make() 用于制作比萨饼,另一个叫做 eat() 用于吃比萨饼。这两个方法接受名为 quantity 的参数,该参数初始化为 1

现在让我们运行程序:

python pizza.py

我们将收到以下输出:

Pizza created: artichoke ($15)
Made 1 artichoke pizza(s)
Ate 1 pizza(s)
Pizza created: margherita ($12)
Made 2 margherita pizza(s)
Ate 1 pizza(s)

虽然 print() 语句允许我们看到代码正在工作,但我们可以使用 logging 模块来代替。

让我们删除或注释掉整个代码中的 print() 语句,并在文件顶部添加 import logging

import logging


class Pizza():
    def __init__(self, name, value):
        self.name = name
        self.value = value
...

logging 模块具有默认级别 WARNING,这是高于 DEBUG 的级别。由于在本示例中我们将使用 logging 模块进行调试,因此需要修改配置,以便级别为 logging.DEBUG 将为我们返回信息到控制台。我们可以通过在导入语句下面添加以下行来实现:

import logging

logging.basicConfig(level=logging.DEBUG)


class Pizza():
...

logging.DEBUG 级别是一个我们在上面的代码中引用的常量整数值,用于设置阈值。DEBUG 级别为 10。

现在,我们将所有的 print() 语句替换为 logging.debug() 语句。与 logging.DEBUG 是一个常量不同,logging.debug()logging 模块的一个方法。在使用此方法时,我们可以使用与 print() 传递的相同字符串,如下所示:

import logging

logging.basicConfig(level=logging.DEBUG)


class Pizza():
    def __init__(self, name, price):
        self.name = name
        self.price = price
        logging.debug("Pizza created: {} (${})".format(self.name, self.price))

    def make(self, quantity=1):
        logging.debug("Made {} {} pizza(s)".format(quantity, self.name))

    def eat(self, quantity=1):
        logging.debug("Ate {} pizza(s)".format(quantity, self.name))

pizza_01 = Pizza("artichoke", 15)
pizza_01.make()
pizza_01.eat()

pizza_02 = Pizza("margherita", 12)
pizza_02.make(2)
pizza_02.eat()

此时,当我们使用 python pizza.py 命令运行程序时,我们将收到以下输出:

DEBUG:root:Pizza created: artichoke ($15)
DEBUG:root:Made 1 artichoke pizza(s)
DEBUG:root:Ate 1 pizza(s)
DEBUG:root:Pizza created: margherita ($12)
DEBUG:root:Made 2 margherita pizza(s)
DEBUG:root:Ate 1 pizza(s)

日志消息具有严重级别 DEBUG 以及嵌入其中的单词 root,它指的是您的 Python 模块级别。logging 模块可以与具有不同名称的日志器层次结构一起使用,以便您可以为每个模块使用不同的日志器。

例如,您可以将日志器设置为具有不同名称和不同输出的不同日志器:

logger1 = logging.getLogger("module_1")
logger2 = logging.getLogger("module_2")

logger1.debug("Module 1 debugger")
logger2.debug("Module 2 debugger")
DEBUG:module_1:Module 1 debugger
DEBUG:module_2:Module 2 debugger

现在我们已经了解了如何使用 logging 模块将消息打印到控制台,让我们继续使用 logging 模块将消息打印到文件中。

将消息记录到文件

logging 模块的主要目的是将消息记录到文件而不是控制台。保留消息文件可以为您提供随时间的数据,以便您可以查阅和量化,以便了解需要对代码进行哪些更改。

要开始记录到文件,我们可以修改 logging.basicConfig() 方法以包括一个 filename 参数。在这种情况下,让我们将文件名称为 test.log

import logging

logging.basicConfig(filename="test.log", level=logging.DEBUG)


class Pizza():
    def __init__(self, name, price):
        self.name = name
        self.price = price
        logging.debug("Pizza created: {} (${})".format(self.name, self.price))

    def make(self, quantity=1):
        logging.debug("Made {} {} pizza(s)".format(quantity, self.name))

    def eat(self, quantity=1):
        logging.debug("Ate {} pizza(s)".format(quantity, self.name))

pizza_01 = Pizza("artichoke", 15)
pizza_01.make()
pizza_01.eat()

pizza_02 = Pizza("margherita", 12)
pizza_02.make(2)
pizza_02.eat()

以上代码与前一节中的代码相同,只是现在我们添加了日志打印的文件名。一旦我们使用 python pizza.py 命令运行代码,我们应该在目录中有一个名为 test.log 的新文件。

让我们用 nano(或您选择的文本编辑器)打开 test.log 文件:

nano test.log

当文件打开时,我们将收到以下内容:

DEBUG:root:Pizza created: artichoke ($15)
DEBUG:root:Made 1 artichoke pizza(s)
DEBUG:root:Ate 1 pizza(s)
DEBUG:root:Pizza created: margherita ($12)
DEBUG:root:Made 2 margherita pizza(s)
DEBUG:root:Ate 1 pizza(s)

这与我们在前一节中遇到的控制台输出类似,只是现在它在 test.log 文件中。

让我们用 CTRL + x 关闭文件,然后返回到 pizza.py 文件,以便我们可以修改代码。

我们将保持大部分代码不变,但修改两个比萨实例 pizza_01pizza_02 中的参数:

import logging

logging.basicConfig(filename="test.log", level=logging.DEBUG)


class Pizza():
    def __init__(self, name, price):
        self.name = name
        self.price = price
        logging.debug("Pizza created: {} (${})".format(self.name, self.price))

    def make(self, quantity=1):
        logging.debug("Made {} {} pizza(s)".format(quantity, self.name))

    def eat(self, quantity=1):
        logging.debug("Ate {} pizza(s)".format(quantity, self.name))

# 修改 pizza_01 对象的参数
pizza_01 = Pizza("Sicilian", 18)
pizza_01.make(5)
pizza_01.eat(4)

# 修改 pizza_02 对象的参数
pizza_02 = Pizza("quattro formaggi", 16)
pizza_02.make(2)
pizza_02.eat(2)

通过这些更改,让我们再次使用 python pizza.py 命令运行程序。

一旦程序运行完毕,我们可以再次使用 nano 打开我们的 test.log 文件:

nano test.log

当我们查看文件时,我们会发现添加了几行新内容,并且保留了上次运行程序时的先前行:

DEBUG:root:Pizza created: artichoke ($15)
DEBUG:root:Made 1 artichoke pizza(s)
DEBUG:root:Ate 1 pizza(s)
DEBUG:root:Pizza created: margherita ($12)
DEBUG:root:Made 2 margherita pizza(s)
DEBUG:root:Ate 1 pizza(s)
DEBUG:root:Pizza created: Sicilian ($18)
DEBUG:root:Made 5 Sicilian pizza(s)
DEBUG:root:Ate 4 pizza(s)
DEBUG:root:Pizza created: quattro formaggi ($16)
DEBUG:root:Made 2 quattro formaggi pizza(s)
DEBUG:root:Ate 2 pizza(s)

虽然这些信息肯定是有用的,但我们可以通过添加额外的 LogRecord 属性使日志更具信息性。主要是,我们想要添加一个人类可读的时间戳,告诉我们 LogRecord 创建的时间。

我们可以将该属性添加到一个名为 format 的参数中,引用方式如表中所示,使用字符串 %(asctime)s。此外,为保留 DEBUG 级别名称,我们需要包括字符串 %(levelname)s,并为保留我们要求记录器打印的字符串消息,我们将包括 %(message)s。每个属性将由一个 冒号 分隔,如下所示:

import logging

logging.basicConfig(
    filename="test.log",
    level=logging.DEBUG,
    format="%(asctime)s:%(levelname)s:%(message)s"
    )


class Pizza():
    def __init__(self, name, price):
        self.name = name
        self.price = price
        logging.debug("Pizza created: {} (${})".format(self.name, self.price))

    def make(self, quantity=1):
        logging.debug("Made {} {} pizza(s)".format(quantity, self.name))

    def eat(self, quantity=1):
        logging.debug("Ate {} pizza(s)".format(quantity, self.name))

pizza_01 = Pizza("Sicilian", 18)
pizza_01.make(5)
pizza_01.eat(4)

pizza_02 = Pizza("quattro formaggi", 16)
pizza_02.make(2)
pizza_02.eat(2)

当我们使用添加了属性的代码运行 python pizza.py 命令时,我们将在我们的 test.log 文件中添加新行,其中包括人类可读的时间戳,以及 DEBUG 级别名称和作为字符串传递到记录器的相关消息。

DEBUG:root:Pizza created: Sicilian ($18)
DEBUG:root:Made 5 Sicilian pizza(s)
DEBUG:root:Ate 4 pizza(s)
DEBUG:root:Pizza created: quattro formaggi ($16)
DEBUG:root:Made 2 quattro formaggi pizza(s)
DEBUG:root:Ate 2 pizza(s)
2021-08-19 23:31:34,484:DEBUG:Pizza created: Sicilian ($18)
2021-08-19 23:31:34,484:DEBUG:Made 5 Sicilian pizza(s)
2021-08-19 23:31:34,484:DEBUG:Ate 4 pizza(s)
2021-08-19 23:31:34,484:DEBUG:Pizza created: quattro formaggi ($16)
2021-08-19 23:31:34,484:DEBUG:Made 2 quattro formaggi pizza(s)
2021-08-19 23:31:34,484:DEBUG:Ate 2 pizza(s)

根据您的需求,您可能希望在代码中使用其他 LogRecord 属性,以使您的程序文件的日志对您相关。

将调试和其他消息记录到单独的文件中,可以为您提供对时间的全面了解,使您有机会以历史工作和发生的事件和交易为基础来调试和修改代码。## 日志级别表

作为开发人员,你可以为记录在日志器中的事件赋予重要性级别,通过添加一个严重级别。下表显示了严重级别。

日志级别在技术上是整数(一个常量),它们都是以 10 的增量开始的,从 NOTSET 开始,它将日志器初始化为数值 0。

你也可以相对于预定义级别定义自己的级别。如果你定义了一个与相同数值的级别,你将覆盖与该数值相关联的名称。

下表显示了各级别名称、它们的数值、可以用来调用该级别的函数以及该级别的用途。

级别 数值 函数 用途
CRITICAL 50 logging.critical() 显示严重错误,程序可能无法继续运行
ERROR 40 logging.error() 显示更严重的问题
WARNING 30 logging.warning() 指示发生了意外事件,或可能发生
INFO 20 logging.info() 确认事情正在按预期工作
DEBUG 10 logging.debug() 诊断问题,显示详细信息

logging 模块将默认级别设置为 WARNING,因此默认情况下将记录 WARNINGERRORCRITICAL。在上面的示例中,我们修改了配置以包括 DEBUG 级别,使用以下代码:

logging.basicConfig(level=logging.DEBUG)

你可以从官方 logging 文档中了解更多关于命令和与调试器一起工作的信息。

结论

调试是任何软件开发项目的重要步骤。logging 模块是标准 Python 库的一部分,提供了对软件运行时发生的事件进行跟踪,并可以将这些事件输出到单独的日志文件中,以便你在代码运行时跟踪发生的事件。这为你提供了根据了解程序运行时发生的各种事件来调试代码的机会。

你可能感兴趣的:(Python,python,开发语言)