如何在 Python 3 中使用日志记录

介绍

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

日志模块允许记录与应用程序操作相关的事件的诊断日志,以及记录用户交易事件的审计日志,用于分析。

前提条件

如果您没有设置编程环境,您可以参考本地编程环境的安装和安装指南(https://www.digitalocean.com/community/tutorial_series/how-to-install-and-set-up-a-local-programming-environment-for-python-3)或适用于您的操作系统(Ubuntu, CentOS, Debian 等)的编程环境(https://www.digitalocean.com/community/tutorial_collections/how-to-install-python-3-and-set-up-a-programming-environment)。

为什么要使用日志模块

日志模块记录了程序内部发生的事件,使它能够看到与软件运行时间中发生的任何事件相关的输出。

您可能更熟悉通过在整个代码中使用print()陈述来检查事件是否正在发生。print()陈述 does 提供了一个基本的方法来调试您的代码以解决问题。

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

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

由于日志可以向您展示时间的行为和错误,它们还可以为您提供在应用程序开发过程中正在发生的事情更好的整体图像。

打印 Debug 消息到控制台

<$>[info] Info: 要跟进本教程中的示例代码,请在本地系统上运行python3命令,打开Python互动壳。

如果你习惯了使用print()语句来查看程序中发生了什么,你可能习惯了看到一个定义类(https://andsky.com/tech/tutorials/how-to-construct-classes-and-define-objects-in-python-3)的程序,并实例化产生类似于此的对象:

 1[label pizza.py]
 2class Pizza():
 3    def __init__(self, name, price):
 4        self.name = name
 5        self.price = price
 6        print("Pizza created: {} (${})".format(self.name, self.price))
 7
 8    def make(self, quantity=1):
 9        print("Made {} {} pizza(s)".format(quantity, self.name))
10
11    def eat(self, quantity=1):
12        print("Ate {} pizza(s)".format(quantity, self.name))
13
14pizza_01 = Pizza("artichoke", 15)
15pizza_01.make()
16pizza_01.eat()
17
18pizza_02 = Pizza("margherita", 12)
19pizza_02.make(2)
20pizza_02.eat()

上面的代码有一个__init__方法来定义一个Pizza类的对象的名称价格,然后它有两种方法,一种叫做make()制作披萨,另一种叫做eat()吃披萨。

现在,让我们运行这个程序:

1python pizza.py

我们将获得以下输出:

1[secondary_label Output]
2Pizza created: artichoke ($15)
3Made 1 artichoke pizza(s)
4Ate 1 pizza(s)
5Pizza created: margherita ($12)
6Made 2 margherita pizza(s)
7Ate 1 pizza(s)

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

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

1[label pizza.py]
2import logging
3
4class Pizza():
5    def __init__(self, name, value):
6        self.name = name
7        self.value = value
8...

由于我们将在本示例中使用logging模块进行调试,我们需要修改配置,以便logging.DEBUG级将信息返回控制台为我们。

1[label pizza.py]
2import logging
3
4logging.basicConfig(level=logging.DEBUG)
5
6class Pizza():
7...

这个logging.DEBUG级别是指我们在上面的代码中引用一个恒定的整数值来设置一个门槛。

现在,我们将所有print()陈述代替logging.debug()陈述,而不是logging.DEBUG,这是一个常数,而logging.debug()logging模块的一种方法。当我们使用这种方法时,我们可以使用相同的字符串(https://www.digitalocean.com/community/tutorial_series/working-with-strings-in-python-3)传给print(),如下所示:

 1[label pizza.py]
 2import logging
 3
 4logging.basicConfig(level=logging.DEBUG)
 5
 6class Pizza():
 7    def __init__(self, name, price):
 8        self.name = name
 9        self.price = price
10        logging.debug("Pizza created: {} (${})".format(self.name, self.price))
11
12    def make(self, quantity=1):
13        logging.debug("Made {} {} pizza(s)".format(quantity, self.name))
14
15    def eat(self, quantity=1):
16        logging.debug("Ate {} pizza(s)".format(quantity, self.name))
17
18pizza_01 = Pizza("artichoke", 15)
19pizza_01.make()
20pizza_01.eat()
21
22pizza_02 = Pizza("margherita", 12)
23pizza_02.make(2)
24pizza_02.eat()

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

1[secondary_label Output]
2DEBUG:root:Pizza created: artichoke ($15)
3DEBUG:root:Made 1 artichoke pizza(s)
4DEBUG:root:Ate 1 pizza(s)
5DEBUG:root:Pizza created: margherita ($12)
6DEBUG:root:Made 2 margherita pizza(s)
7DEBUG:root:Ate 1 pizza(s)

日志消息具有严度级别DEBUG以及嵌入的词root,指您的Python模块的级别。

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

1logger1 = logging.getLogger("module_1")
2logger2 = logging.getLogger("module_2")
3
4logger1.debug("Module 1 debugger")
5logger2.debug("Module 2 debugger")
1[secondary_label Output]
2DEBUG:module_1:Module 1 debugger
3DEBUG:module_2:Module 2 debugger

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

记录消息到一个文件

日志模块的主要目的是将消息记录到一个文件,而不是到一个控制台. 保持一个消息文件为您提供了随着时间的推移,您可以查询和量化数据,以便您可以看到需要对您的代码进行哪些更改。

要开始登录到一个文件,我们可以修改logging.basicConfig()方法以包含一个filename参数。

 1[label pizza.py]
 2import logging
 3
 4logging.basicConfig(filename="test.log", level=logging.DEBUG)
 5
 6class Pizza():
 7    def __init__(self, name, price):
 8        self.name = name
 9        self.price = price
10        logging.debug("Pizza created: {} (${})".format(self.name, self.price))
11
12    def make(self, quantity=1):
13        logging.debug("Made {} {} pizza(s)".format(quantity, self.name))
14
15    def eat(self, quantity=1):
16        logging.debug("Ate {} pizza(s)".format(quantity, self.name))
17
18pizza_01 = Pizza("artichoke", 15)
19pizza_01.make()
20pizza_01.eat()
21
22pizza_02 = Pizza("margherita", 12)
23pizza_02.make(2)
24pizza_02.eat()

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

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

1nano test.log

当文件打开时,我们会收到以下信息:

1[label test.log]
2DEBUG:root:Pizza created: artichoke ($15)
3DEBUG:root:Made 1 artichoke pizza(s)
4DEBUG:root:Ate 1 pizza(s)
5DEBUG:root:Pizza created: margherita ($12)
6DEBUG:root:Made 2 margherita pizza(s)
7DEBUG:root:Ate 1 pizza(s)

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

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

我们将保持大部分代码相同,但在两个披萨实例中修改参数,即pizza_01pizza_02:

 1[label pizza.py]
 2import logging
 3
 4logging.basicConfig(filename="test.log", level=logging.DEBUG)
 5
 6class Pizza():
 7    def __init__(self, name, price):
 8        self.name = name
 9        self.price = price
10        logging.debug("Pizza created: {} (${})".format(self.name, self.price))
11
12    def make(self, quantity=1):
13        logging.debug("Made {} {} pizza(s)".format(quantity, self.name))
14
15    def eat(self, quantity=1):
16        logging.debug("Ate {} pizza(s)".format(quantity, self.name))
17
18# Modify the parameters of the pizza_01 object
19pizza_01 = Pizza("Sicilian", 18)
20pizza_01.make(5)
21pizza_01.eat(4)
22
23# Modify the parameters of the pizza_02 object
24pizza_02 = Pizza("quattro formaggi", 16)
25pizza_02.make(2)
26pizza_02.eat(2)

有了这些更改,让我们用python pizza.py命令再次运行该程序。

一旦程序运行,我们可以用 nano 重新打开我们的 test.log 文件:

1nano test.log

当我们审查文件时,我们会看到一些新的行被添加了,并保留了该程序上一次运行之前的行:

 1[label test.log]
 2DEBUG:root:Pizza created: artichoke ($15)
 3DEBUG:root:Made 1 artichoke pizza(s)
 4DEBUG:root:Ate 1 pizza(s)
 5DEBUG:root:Pizza created: margherita ($12)
 6DEBUG:root:Made 2 margherita pizza(s)
 7DEBUG:root:Ate 1 pizza(s)
 8DEBUG:root:Pizza created: Sicilian ($18)
 9DEBUG:root:Made 5 Sicilian pizza(s)
10DEBUG:root:Ate 4 pizza(s)
11DEBUG:root:Pizza created: quattro formaggi ($16)
12DEBUG:root:Made 2 quattro formaggi pizza(s)
13DEBUG:root:Ate 2 pizza(s)

虽然这些信息特别有用,但我们可以通过添加额外的 LogRecord 属性来使日志更具信息性。

我们可以将该属性添加到一个称为格式的参数中,将其引用到表中显示的字符串%(asctime)s。 此外,为了保持DEBUG级别名称,我们需要包括字符串%(levelname)s,并保持我们要求日志打印的字符串信息,我们将包括%(message)s

 1[label pizza.py]
 2import logging
 3
 4logging.basicConfig(
 5    filename="test.log",
 6    level=logging.DEBUG,
 7    format="%(asctime)s:%(levelname)s:%(message)s"
 8    )
 9
10class Pizza():
11    def __init__(self, name, price):
12        self.name = name
13        self.price = price
14        logging.debug("Pizza created: {} (${})".format(self.name, self.price))
15
16    def make(self, quantity=1):
17        logging.debug("Made {} {} pizza(s)".format(quantity, self.name))
18
19    def eat(self, quantity=1):
20        logging.debug("Ate {} pizza(s)".format(quantity, self.name))
21
22pizza_01 = Pizza("Sicilian", 18)
23pizza_01.make(5)
24pizza_01.eat(4)
25
26pizza_02 = Pizza("quattro formaggi", 16)
27pizza_02.make(2)
28pizza_02.eat(2)

当我们使用python pizza.py命令运行上面的代码时,我们将收到新行添加到我们的test.log文件中,其中包括可人读的时间戳,以及DEBUG的级别名称和相关消息,这些消息作为字符串传入日志器。

 1[label test.log]
 2DEBUG:root:Pizza created: Sicilian ($18)
 3DEBUG:root:Made 5 Sicilian pizza(s)
 4DEBUG:root:Ate 4 pizza(s)
 5DEBUG:root:Pizza created: quattro formaggi ($16)
 6DEBUG:root:Made 2 quattro formaggi pizza(s)
 7DEBUG:root:Ate 2 pizza(s)
 82021-08-19 23:31:34,484:DEBUG:Pizza created: Sicilian ($18)
 92021-08-19 23:31:34,484:DEBUG:Made 5 Sicilian pizza(s)
102021-08-19 23:31:34,484:DEBUG:Ate 4 pizza(s)
112021-08-19 23:31:34,484:DEBUG:Pizza created: quattro formaggi ($16)
122021-08-19 23:31:34,484:DEBUG:Made 2 quattro formaggi pizza(s)
132021-08-19 23:31:34,484:DEBUG:Ate 2 pizza(s)

根据您的需求,您可能希望在代码中使用额外的 LogRecord 属性来使程序文件的日志与您相关。

记录调试和其他消息到单独的文件中为您提供了对您的Python程序的整体理解,并为您提供了解决问题和修改代码的机会,以便通过程序中的历史工作以及发生的事件和交易来通知您。

木材层级表

作为开发人员,您可以通过添加严重度级别来将重要性级别分配给记录器中捕获的事件。

记录级别在技术上是整数(常数),它们都是10的增量,从NOTSET开始,它以0的数值初始化了记录器。

如果您定义具有相同数值的级别,则将重写与该值相关的名称。

下表显示了不同级别的名称、它们的数值、您可以使用哪个函数来调用该级别以及该级别用于什么。

LevelNumeric ValueFunctionUsed to
CRITICAL50logging.critical()Show a serious error, the program may be unable to continue running
ERROR40logging.error()Show a more serious problem
WARNING30logging.warning()Indicate something unexpected happened, or could happen
INFO20logging.info()Confirm that things are working as expected
DEBUG10logging.debug()Diagnose problems, show detailed information

日志模块将默认级别设置为警告,因此警告,错误关键都将默认地登录。

1logging.basicConfig(level=logging.DEBUG)

您可以从 官方的日志文档`中阅读更多关于命令和与调试器的工作。

结论

日志模块是标准Python库的一部分,提供在软件运行时发生的事件的跟踪,并可以将这些事件输出到一个单独的日志文件中,以便您可以跟踪代码运行时发生的事情。

Published At
Categories with 技术
comments powered by Disqus