Python 的 logging 模組是開發過程中不可或缺的工具,能有效追蹤程式執行狀態、診斷問題並最佳化系統。不同於 print() 函式的靜態輸出,logging 提供動態調整日誌級別、輸出到檔案或遠端伺服器等彈性機制。透過設定 DEBUGINFOWARNINGERRORCRITICAL 等不同級別,開發者可精確控制輸出訊息的詳細程度,例如在生產環境中僅顯示 WARNING 以長官別,避免過多資訊幹擾。文章也示範如何捕捉異常並記錄堆積疊追蹤資訊,方便快速定位錯誤。自動化日誌管理是提升效率的關鍵,建議實施自動日誌清理、日誌等級分類別、格式標準化以及集中化管理。日誌輪替功能能有效管理日誌檔案大小,避免檔案過大難以處理。此外,文章也說明瞭如何同時輸出日誌到多個平台,例如控制檯和檔案,以及如何在多執行緒環境下使用日誌記錄。最後,介紹了第三方套件 Loguru 和 Structlog,提供更簡潔易用的介面和結構化日誌格式,方便開發者快速整合與應用。

Python 記錄系統(Logging)深度剖析

記錄系統的基本概念

記錄系統的重要性

記錄系統在軟體開發中扮演著不可或缺的角色。它不僅能幫助開發者追蹤程式執行過程中的狀態和錯誤,還能提供有價值的資訊來進行問題診斷和系統最佳化。透過記錄,開發者可以更容易地理解程式的執行流程,找出潛在的問題,並及時進行修正。

記錄系統與 print() 的差異

許多人在開發初期可能會使用 print() 函式來輸出除錯資訊,但這種方法有其侷限性。print() 的輸出是靜態且固定的,無法根據需要動態調整記錄級別,也無法方便地將記錄寫入檔案或遠端伺服器。此外,print() 的輸出會直接顯示在終端機上,可能會與其他輸出混淆,導致除錯過程中難以辨識。

建立簡單的「Hello World」記錄

要開始使用 Python 的記錄系統,首先需要匯入 logging 模組並進行基本組態。以下是一個簡單的範例,展示如何建立一個基本的「Hello World」記錄:

import logging

# 組態基本的記錄設定
logging.basicConfig(level=logging.DEBUG)

# 建立一個記錄器
logger = logging.getLogger(__name__)

# 發出一條 DEBUG 級別的記錄
logger.debug("Hello, World!")

內容解密:

這段程式碼展示瞭如何使用 Python 的 logging 模組來建立一個簡單的記錄系統。首先,我們匯入了 logging 模組。接著,使用 logging.basicConfig() 函式來組態基本的記錄設定,這裡設定了最低記錄級別為 DEBUG。然後,我們建立了一個名為 __name__ 的記錄器。最後,我們使用 logger.debug() 函式來發出一條 DEBUG 級別的記錄訊息「Hello, World!」。

記錄級別(Log Levels)

可用的記錄級別

Python 的 logging 模組提供了多個預設的記錄級別,從低到高依序為:DEBUGINFOWARNINGERRORCRITICAL。每個級別代表不同程度的問題嚴重性:

  • DEBUG: 提供詳細資訊,通常用於除錯。
  • INFO: 確認程式執行正常。
  • WARNING: 指示可能會發生問題的情況。
  • ERROR: 指示已經發生錯誤。
  • CRITICAL: 指示非常嚴重的錯誤。

使用記錄級別

根據需要設定不同的記錄級別可以幫助我們更精確地控制哪些訊息會被輸出。例如,在生產環境中可能只需要顯示 WARNING 或更高階別的訊息,以避免過多不必要的資訊幹擾。

import logging

# 組態基本的記錄設定
logging.basicConfig(level=logging.WARNING)

# 建立一個記錄器
logger = logging.getLogger(__name__)

# 發出不同級別的記錄
logger.debug("This is a debug message")
logger.info("This is an info message")
logger.warning("This is a warning message")
logger.error("This is an error message")
logger.critical("This is a critical message")

內容解密:

這段程式碼展示瞭如何設定不同級別的紀律。首先組態基本紀律設定並指定紀律等級為 WARNING。接下來建立一個紀律器並發出不同等級(DEBUG、INFO、WARNING、ERROR、CRITICAL)紀律資訊。只有 WARNING、ERROR 和 CRITICAL 三種等級資訊才會顯示。

處理異常(Exceptions)

在實際應用中,我們經常需要處理異常情況並將相關資訊進行紀律。Python 的紀律模組提供了一個方便的方法來處理異常並自動附加堆積疊追蹤資訊:

import logging

# 組態基本的紀律設定
logging.basicConfig(level=logging.ERROR)

# 建立一個紀律器
logger = logging.getLogger(__name__)

try:
    # 模擬一個除以零錯誤
    result = 1 / 0
except Exception as e:
    logger.error("Exception occurred", exc_info=True)

內容解密:

這段程式碼展示瞭如何在處理異常時使用紀律模組來記錄錯誤資訊和堆積疊追蹤。首先組態基本紀律設定並指定紀律等級為 ERROR。接下來建立一個紀錄器並模擬一個除以零錯誤。當發生異常時會捕捉到異常並使用 logger.error() 函式來發出 ERROR 級別記錄訊息並在引數中傳遞 exc_info=True 用於附加堆積疊追蹤資訊。

自動化日誌管理與最佳實踐

玄貓認為透過自動化來處理日誌管理是非常重要的一環。透過自動化可以提升效率、降低人為錯誤以及提高日誌管理的一致性與可靠性。以下是一些最佳實踐:

  1. 自動日誌清理:設定日誌檔案大小上限及自動清理舊日誌。
  2. 日誌等級分類別:根據不同環境(如開發、測試、生產)設定合適的日誌等級。
  3. 日誌格式標準化:統一日誌格式以便更容易進行分析和搜尋。
  4. 集中化日誌管理:利用集中化日誌管理系統如 ELK Stack(Elasticsearch, Logstash, Kibana)來統一收集和分析日誌。

此圖示展示了一個簡單而完整的 Python 日誌管理架構:

  graph TD;
    A[Application] --> B[Logger];
    B --> C[Handler];
    C --> D[Console];
    C --> E[File];
    C --> F[Remote Server];

此圖示解說:

  • Application 生成日誌訊息。
  • Logger 接收應用程式產生的日誌訊息。
  • Handler 處理日誌訊息並決定將其傳送至何處。
  • ConsoleFileRemote Server 分別代表將日誌訊息輸出到終端機、本地檔案和遠端伺服器。

自動化旋轉(Rotating)日誌

隨著時間推移或資料量增加,日誌檔案會變得龐大且難以管理。因此,旋轉(Rotating)功能成為必需品之一。

根據檔案大小旋轉

當檔案達到特定大小時就會建立新檔案:

import logging
from logging.handlers import RotatingFileHandler

# 建立一個旋轉檔案處理器
handler = RotatingFileHandler('app.log', maxBytes=2000, backupCount=5)
handler.setLevel(logging.DEBUG)

# 建立一個格式化器並將其新增到處理器
formatter = logging.Formatter('%(asctime)s - %(name)s - %(levelname)s - %(message)s')
handler.setFormatter(formatter)

# 建立一個根紀律器並將處理器新增到其中
logger = logging.getLogger()
logger.setLevel(logging.DEBUG)
logger.addHandler(handler)

# 傳遞一些紀律訊息給根紀律器
for i in range(100):
    logger.debug(f'This is a debug message {i}')

內容解密:

這段程式碼展示瞭如何根據檔案大小進行旋轉功能組態。首先匯入所需模組並建立一個 RotatingFileHandler。接著設定最大檔案大小(maxBytes)以及保留備份數量(backupCount)。然後組態格式化器並將其新增到處理器上。最後建立根紀律器並新增該處理器。

自動化同時多種讀取方式

現在常見於企業或大型專案中可能需要同時向多種平台輸出日誌訊息:

import logging

# 組態基本紀律設定並新增多種處理器
logging.basicConfig(level=logging.DEBUG,
                    format='%(asctime)s - %(name)s - %(levelname)s - %(message)s')

console_handler = logging.StreamHandler()
file_handler = logging.FileHandler('app.log')

console_handler.setLevel(logging.DEBUG)
file_handler.setLevel(logging.ERROR)

formatter = logging.Formatter('%(asctime)s - %(name)s - %(levelname)s - %(message)s')
console_handler.setFormatter(formatter)
file_handler.setFormatter(formatter)

logger = logging.getLogger()
logger.addHandler(console_handler)
logger.addHandler(file_handler)

logger.debug('This is a debug message')
logger.error('This is an error message')

內容解密:

此段程式碼展示瞭如何同時向控制檯和檔案輸出記錄資訊。首先組態基本紀錄設定並建立多種處理器:控制檯處理器和檔案處理器。然後分別設定處理器等級並組態格式化器將其新增到處理器上最後建立根紀錄器並將所有處理器新增到其中。

平行運作 (Concurrency) 與非同步 (Asynchronous) 日誌

對於需要高效能及時性要求高於 CPU 效能開發場景下時,可以考慮使用平行運作及非同步方式進行最佳化:

import logging.config
import threading

def worker(num):
    logger = logging.getLogger(f'thread-{num}')
    logger.debug(f'Thread {num} is starting')
    logger.info(f'Thread {num} is doing work')
    logger.warning(f'Thread {num} is encountering a problem')
    logger.error(f'Thread {num} encountered an error')
    logger.critical(f'Thread {num} encountered a critical issue')

if __name__ == '__main__':
    # 組態基礎紀律設定並新增多種處理機制: 檔案/控制檯/遠端伺服端...
    logging.config.dictConfig({
        'version': 1,
        'formatters': {
            'standard': {
                'format': '%(asctime)s - %(name)s - %(levelname)s - %(message)s'
            }
        },
        'handlers': {
            'console': {
                'class': 'logging.StreamHandler',
                'level': 'DEBUG',
                'formatter': 'standard'
            },
            'file': {
                'class': 'logging.FileHandler',
                'filename': 'app.log',
                'level': 'ERROR',
                'formatter': 'standard'
            }
        },
        'loggers': {
            '__main__': {
                'handlers': ['console', 'file'],
                'level': 'DEBUG',
                'propagate': True
            }
        }
    })

    threads = []
    for i in range(5):
        t = threading.Thread(target=worker, args=(i,))
        t.start()
        threads.append(t)

    for t in threads:
        t.join()

內容解密:

此段程式碼展示瞭如何在平行執行情況下使用紀律模組進行除錯及詢錯工具。首先組態基礎紀錄設定並建立多種處理機制:控制檯/檔案/遠端伺服器端… 然後使用執行緒函式 target=worker 新增各種執行緒 (threading.Thread) 開始執行任務最後等待所有執行緒完成後結束。

第三方套件簡介:Loguru 和 Structlog

除了內建功能之外,還可以選擇第三方套件來提升功能效率:

Loguru:

簡易安裝及快速上手:

from loguru import logger

class MyClass:
    def my_method(self):
        logger.debug("This is a debug message")
        logger.info("This is an info message")
        logger.warning("This is a warning message")
        logger.error("This is an error message")
        logger.critical("This is a critical message")

my_obj = MyClass()
my_obj.my_method()

內容解密:

Loguru 是一個輕量級且功能豐富的記錄函式庫, 主要功能包括簡易安裝與快速上手以及豐富記錄功能如彩色控制檯輸出、檔案迴轉記錄等。

Structlog:

Structlog 提供強大結構化日誌格式:

import structlog
import logging

structlog.configure(
    processors=[
        structlog.processors.JSONRenderer(),
    ],
    context_class=dict,
    logger_factory=structlog.stdlib.LoggerFactory(),
    wrapper_class=structlog.stdlib.BoundLogger,
    cache_logger_on_first_use=True,
)

def main():
    log = structlog.get_logger()

    log.info('event=process_started', user_id=123456, process_id='abcdefg')

if __name__ == '__main__':
     main()

內容解密:

Structlog 提供結構化記錄支援 JSON 輸出, 本例演示瞭如何透過結構化方式記錄事件資訊並且包含額外上下文資訊如 user_id 或 process_id.