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

介紹

logging 模組是標準的 Python 庫的一部分,用於追蹤軟件運行時發生的事件。您可以在代碼中添加日誌呼叫以指示發生了哪些事件。

logging 模組允許進行診斷日誌記錄,記錄與應用程序操作相關的事件,以及記錄用戶交易事件進行分析的審計日誌。它特別用於將事件記錄到文件中。

先決條件

您應該已經安裝了 Python 3,並在您的計算機或服務器上設置了編程環境。如果您尚未設置編程環境,您可以參考相應的安裝和設置指南,安裝一個本地編程環境或者適合您的操作系統(Ubuntu、CentOS、Debian 等)的服務器上的編程環境。

為什麼使用 logging 模組

logging 模組記錄程序中發生的事件,使得可以查看與軟體運行期間發生的任何事件相關的輸出。

您可能更熟悉通過在代碼中使用 print() 陳述來檢查事件是否發生。 print() 陳述提供了一種基本方法來調試代碼以解決問題。嵌入到代碼中的 print() 陳述可以追蹤執行流程和程序的當前狀態,但與使用 logging 模組相比,這種解決方案較難維護:

  • 很難區分調試輸出和正常程序輸出,因為兩者混在一起
  • 在代碼中分散使用 print() 陳述時,沒有有效的方法來禁用提供調試輸出的陳述
  • 當調試完成後,刪除所有 print() 陳述變得困難
  • 沒有包含可用於診斷的信息的日誌記錄

在您的代碼中使用 logging 模組是一個好習慣,因為這對於應用程序的開發而言更為適用,它提供了一種可持續的調試方法,適用於超出小型 Python 腳本範圍的應用程序。

因为日志可以显示您的行为和错误随着时间的推移,它们也可以让您更好地了解应用程序开发过程中正在发生的情况。

将调试消息打印到控制台

信息:要按照本教程中的示例代码进行操作,请在本地系统上打开Python交互式shell,运行python3命令。然后,您可以通过在>>>提示之后添加示例来复制、粘贴或编辑示例。

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

pizza.py
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

现在让我们运行程序:

  1. python pizza.py

我们将收到以下输出:

Output
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

pizza.py
import logging


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

logging 模块的 默认级别WARNING,这是高于 DEBUG 的级别。由于我们将在本示例中使用 logging 模块进行调试,因此需要修改配置,使 logging.DEBUG 级别能够向控制台返回信息。我们可以在 导入语句 下方添加以下行:

pizza.py
import logging

logging.basicConfig(level=logging.DEBUG)


class Pizza():
...

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

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

pizza.py
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 命令运行程序时,将会得到以下输出:

Output
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")
Output
DEBUG:module_1:Module 1 debugger DEBUG:module_2:Module 2 debugger

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

将消息记录到文件中

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

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

pizza.py
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 檔案:

  1. nano test.log

當檔案打開時,我們將收到以下內容:

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 的參數:

pizza.py
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 檔案:

  1. nano test.log

當我們檢查檔案時,我們會看到新增了幾行新內容,以及上次程式運行時保留的先前行:

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。這些屬性中的每個將由冒號分隔,如所添加的代碼所示:

pizza.py
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級別的名稱以及作為字符串傳遞給記錄器的相關消息。

test.log
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屬性,以使您的程序文件日志與您相關。

將調試和其他消息記錄到單獨的文件中,可讓您全面了解您的Python程序的情況,從而讓您有機會根據程序的歷史工作以及發生的事件和交易來進行疑難排解和修改代碼。

日誌級別表

作為開發人員,您可以通過添加嚴重性級別來將在日誌記錄中捕獲的事件歸因為一個重要程度。嚴重性級別如下表所示。

日誌級別在技術上是整數(一個常量),它們都以10的增量開始,從初始化數值為0的NOTSET開始。

您還可以定義相對於預定義級別的自己的級別。如果您定義了具有相同數值的級別,則將覆蓋與該值關聯的名稱。

以下表格顯示了各種級別名稱、它們的數值、您可以使用的調用級別的函數以及該級別的用途。

Level Numeric Value Function Used to
CRITICAL 50 logging.critical() Show a serious error, the program may be unable to continue running
ERROR 40 logging.error() Show a more serious problem
WARNING 30 logging.warning() Indicate something unexpected happened, or could happen
INFO 20 logging.info() Confirm that things are working as expected
DEBUG 10 logging.debug() Diagnose problems, show detailed information

logging 模塊將默認級別設置為WARNING,因此默認將記錄WARNINGERRORCRITICAL。在上面的示例中,我們修改了配置以包含以下代碼中的DEBUG級別:

logging.basicConfig(level=logging.DEBUG)

您可以從官方logging文檔中了解有關命令和與調試器一起工作的更多信息。

結論

除錯是任何軟體開發專案中的重要步驟。logging模組是Python標準庫的一部分,它提供了跟踪軟體運行時發生的事件的功能,並可以將這些事件輸出到單獨的日誌文件中,讓您可以跟踪代碼運行時發生的情況。這使您有機會根據了解程序隨時間運行而發生的各種事件來進行除錯。

Source:
https://www.digitalocean.com/community/tutorials/how-to-use-logging-in-python-3