Python 的 logging 模組提供強大的日誌管理功能,對於追蹤程式執行狀態和診斷問題至關重要。本文將逐步講解如何構建一個記錄異常的裝飾器,並示範如何利用 RotatingFileHandlerTimedRotatingFileHandler 進行日誌檔案轉換,避免日誌檔案過大,同時也探討了使用組態檔案集中管理日誌組態的方法。首先,我們會建立一個 exception 裝飾器,它能夠捕捉目標函式的異常,並使用指定的 logger 物件記錄異常資訊。接著,我們會介紹如何建立和組態 logger 物件,包括設定日誌級別、輸出格式以及處理器。然後,我們將探討 RotatingFileHandler,它可以根據檔案大小限制日誌檔案的增長,並自動備份舊的日誌檔案。此外,我們還會介紹 TimedRotatingFileHandler,它可以根據時間間隔(例如每天或每小時)建立新的日誌檔案。最後,我們會示範如何使用組態檔案來管理日誌設定,這可以提高程式碼的可維護性和靈活性,方便日後調整日誌策略。

如何建立一個記錄裝飾器

在本文中,玄貓將帶領大家瞭解如何建立一個記錄裝飾器,並測試其功能。這是一個非常實用的技巧,特別是在處理複雜的程式碼時。玄貓將詳細說明每一步的操作,並提供具體的程式碼範例和解說。

準備工作

首先,我們需要建立一個裝飾器來捕捉和記錄異常。讓我們從建立一個基本的異常裝飾器開始。

異常裝飾器的基本架構

# exception_decor.py

import functools
import logging

def exception(logger):
    def decorator(function):
        @functools.wraps(function)
        def wrapper(*args, **kwargs):
            try:
                return function(*args, **kwargs)
            except Exception as e:
                err = f"There was an exception in {function.__name__}: {e}"
                logger.exception(err)
                raise
        return wrapper
    return decorator

內容解密:

以上程式碼定義了一個名為 exception 的裝飾器函式,該函式接受一個 logger 作為引數。exception 函式內部定義了一個 decorator 函式,該函式接收一個目標函式作為引數,並在目標函式執行時捕捉異常並進行記錄。

def exception(logger):
    def decorator(function):
        @functools.wraps(function)
        def wrapper(*args, **kwargs):
            try:
                return function(*args, **kwargs)
            except Exception as e:
                err = f"There was an exception in {function.__name__}: {e}"
                logger.exception(err)
                raise
        return wrapper
    return decorator
  • exception(logger):這是外層函式,接受一個 logger 作為引數。
  • decorator(function):這是內層函式,接受一個目標函式作為引數。
  • wrapper(*args, **kwargs):這是實際執行目標函式的包裝函式,會捕捉目標函式執行過程中發生的異常。
  • try-except 塊:用來捕捉目標函式執行過程中的異常。
  • logger.exception(err):當捕捉到異常時,使用 logger 進行記錄。
  • raise:將捕捉到的異常再次丟擲,以便上層呼叫者能夠處理。

測試裝飾器

接下來,我們需要測試這個裝飾器是否能正確地捕捉和記錄異常。讓我們建立一個名為 main.py 的檔案來進行測試。

# main.py

from exception_decor import exception
from exception_logger import logger

@exception(logger)
def zero_divide():
    1 / 0

if __name__ == "__main__":
    zero_divide()

內容解密:

以上程式碼首先匯入了我們剛剛建立的 exception 裝飾器和 logger 物件。然後,我們使用這個裝飾器來修飾一個簡單的函式 zero_divide(),該函式會引發除以零的異常。

from exception_decor import exception
from exception_logger import logger
  • from exception_decor import exception:從 exception_decor.py 中匯入 exception 裝飾器。
  • from exception_logger import logger:從 exception_logger.py 中匯入 logger 物件。
@exception(logger)
def zero_divide():
    1 / 0
  • @exception(logger):使用 exception 裝飾器來修飾 zero_divide 函式,並傳遞 logger 物件給它。
  • def zero_divide():: 定義一個會引發除以零異常的函式。
if __name__ == "__main__":
    zero_divide()
  • if __name__ == "__main__":: 檢查這段程式碼是否被直接執行(而不是被匯入到另一個模組中)。
  • zero_divide(): 被修飾過後的零除以零例外處理程式。

執行測試

現在,讓我們在終端機中執行以下命令來測試我們的裝飾器:

python main.py

如果一切正常,你應該會在你的日誌檔案中看到以下內容:

2023-10-05 12:34:56,789 - example_logger - ERROR - There was an exception in zero_divide: division by zero

傳遞日誌物件給裝飾器

在某些情況下,你可能需要在複雜的程式碼中使用多個日誌物件。這時候,你需要讓你的裝飾器能夠接受日誌物件作為引數。

建立日誌物件

首先,我們需要建立一個新的 Python 檔案來管理我們的日誌 API。讓我們命名它為 exception_logger.py

# exception_logger.py

import logging

def create_logger():
    """
    建立一個日誌物件並傳回它。
    """
    logger = logging.getLogger("example_logger")
    logger.setLevel(logging.INFO)

    # 建立日誌檔案處理器
    fh = logging.FileHandler("test.log")

    fmt = ("%(asctime)s - %(name)s - %(levelname)s - "
           "%(message)s")
    formatter = logging.Formatter(fmt)
    fh.setFormatter(formatter)

    # 將處理器新增到日誌物件中
    logger.addHandler(fh)
    return logger

logger = create_logger()

內容解密:

以上程式碼定義了一個名為 create_logger 的函式,該函式建立並組態了一個日誌物件,並傳回它。

import logging

def create_logger():
  • import logging: 引入 Python 的內建日誌模組。
  • def create_logger():: 定義一個名為 create_logger 的函式。
logger = logging.getLogger("example_logger")
logger.setLevel(logging.INFO)
  • "example_logger":設定日誌名稱。
  • "logging.INFO":設定日誌等級為 INFO 。
fh = logging.FileHandler("test.log")
  • "logging.FileHandler":建立一個檔案處理器,將日誌寫入到 “test.log” 。
fmt = ("%(asctime)s - %(name)s - %(levelname)s - "
       "%(message)s")
formatter = logging.Formatter(fmt)
fh.setFormatter(formatter)
  • "%(asctime)s - %(name)s - %(levelname)s - %(message)s":設定日誌格式。
  • "logging.Formatter":根據指定格式建立一個格式化器並套用於處理器。
logger.addHandler(fh)
return logger
  • "addHandler":將處理器新增到日誌物件中。
  • "return logger":傳回已組態好的日誌物件。

組態裝飾器以接受日誌物件作為引數

接下來,我們需要修改我們的裝飾器以接受日誌物件作為引數。讓我們開啟並修改我們的 exception_decor.py 檔案。

# exception_decor.py

import functools

def exception(logger):
    """
    一個裝飾器,包覆傳入的函式並記錄異常(如果有發生)
    """
    def decorator(function):
        @functools.wraps(function)
        def wrapper(*args, **kwargs):
            try:
                return function(*args, **kwargs)
            except Exception as e:
                # 記錄異常資訊並再次丟擲該異常.
                err = f"There was an exception in {function.__name__}: {e}"
                logger.exception(err)
                raise
        return wrapper

    return decorator

測試組態了日誌物件的裝飾器

最後,讓我們建立一個新的 main.py 檔案來測試這些更改。

# main.py

from exception_decor import exception
from exception_logger import logger

@exception(logger)
def zero_divide():
    1 / 0

if __name__ == "__main__":
    zero_divide()

日誌檔案轉換:以 Python 為例

在現代軟體開發中,日誌管理是一項至關重要的技能。日誌不僅幫助我們追蹤應用程式的執行狀態,還能在發生問題時提供寶貴的診斷資訊。這裡,玄貓將詳細介紹如何使用 Python 的 logging 模組來實作日誌檔案的轉換功能,這樣可以確保日誌檔案不會因為過大而影響系統效能。

使用 RotatingFileHandler 進行日誌轉換

RotatingFileHandler 是 Python logging 模組中的一個強大工具,它允許我們根據日誌檔案的大小來自動轉換日誌。這樣可以防止單一日誌檔案變得過大,從而影響系統效能。

基本概念

當日誌檔案達到設定的最大大小時,RotatingFileHandler 會將其轉換為新的檔案,並保留指定數量的舊日誌檔案。例如,如果我們設定 backupCount 為 3,那麼當前日誌檔案會被轉換為 test.log.1,而 test.log.2test.log.3 會分別被移動或刪除。

實作範例

下面是一個簡單的範例程式碼,展示如何使用 RotatingFileHandler

# simple_rotating.py

import logging
import time
from logging.handlers import RotatingFileHandler

def create_rotating_log(path):
    """
    建立一個旋轉日誌
    """
    logger = logging.getLogger("Rotating Log")
    logger.setLevel(logging.INFO)

    handler = RotatingFileHandler(path, maxBytes=20, backupCount=5)
    logger.addHandler(handler)

    for i in range(10):
        logger.info("This is test log line %s", i)
        time.sleep(1.5)

if __name__ == "__main__":
    log_file = "test.log"
    create_rotating_log(log_file)

內容解密:

  • 匯入模組:首先匯入 loggingtime 模組,並從 logging.handlers 中匯入 RotatingFileHandler
  • 建立日誌器:使用 getLogger 建立一個名為 “Rotating Log” 的日誌器。
  • 設定日誌等級:將日誌等級設定為 INFO
  • 建立處理器:建立一個 RotatingFileHandler 例項,指定日誌檔案路徑、最大大小 (maxBytes) 和備份數量 (backupCount)。
  • 新增處理器:將處理器新增到日誌器中。
  • 寫入日誌:使用一個迴圈寫入 10 條測試日誌訊息,每條訊息間隔 1.5 秒。

使用 TimedRotatingFileHandler 進行時間轉換

除了根據檔案大小進行轉換外,我們還可以根據時間間隔來轉換日誌。這在某些情況下非常有用,例如每天生成一個新的日誌檔案。

基本概念

TimedRotatingFileHandler 允許我們根據時間間隔來自動轉換日誌。我們可以設定轉換的時間單位(秒、分鐘、小時、天)和間隔長度。

實作範例

下面是一個範例程式碼,展示如何使用 TimedRotatingFileHandler

# simple_timed_rotation.py

import logging
import time
from logging.handlers import TimedRotatingFileHandler

def create_timed_rotating_log(path):
    logger = logging.getLogger("Rotating Log")
    logger.setLevel(logging.INFO)

    handler = TimedRotatingFileHandler(path, when="m", interval=1, backupCount=5)
    logger.addHandler(handler)

    for i in range(6):
        logger.info("This is a test!")
        time.sleep(75)

if __name__ == "__main__":
    log_file = "timed_test.log"
    create_timed_rotating_log(log_file)

內容解密:

  • 匯入模組:與之前相同,匯入必要的模組。
  • 建立處理器:建立一個 TimedRotatingFileHandler 例項,指定時間單位 (when) 和間隔長度 (interval)。這裡設定為每分鐘轉換一次。
  • 寫入日誌:使用一個迴圈寫入 6 條測試日誌訊息,每條訊息間隔 75 秒。

組態檔案中的日誌轉換

除了在程式碼中直接設定日誌轉換之外,我們還可以使用組態檔案來管理日誌設定。這樣可以使我們的程式碼更加模組化和易於維護。

基本概念

我們可以使用 Python 的 dictConfig 來定義日誌組態。這樣可以將日誌設定與程式碼分離,使得組態更加靈活。

實作範例

下面是一個組態檔案範例:

# settings.py

LOGGING_CONFIG = {
    "version": 1,
    "loggers": {
        "__main__": {
            "handlers": ["rotatorFileHandler", "consoleHandler"],
            "level": "INFO",
        },
    },
    "handlers": {
        "rotatorFileHandler": {
            "class": "logging.handlers.RotatingFileHandler",
            "formatter": "file_formatter",
            "filename": "rotating.log",
            "maxBytes": 20,
            "backupCount": 5,
        },
        "consoleHandler": {
            "class": "logging.StreamHandler",
            "formatter": "stream_formatter",
        },
    },
    "formatters": {
        "file_formatter": {
            "format": "%(asctime)s - %(message)s",
        },
        "stream_formatter": {
            "format": "%(asctime)s - %(message)s",
            "datefmt": "%a %d %b %Y",
        },
    },
}

然後在主程式中載入這個組態:

# main.py

import logging.config
import time
from settings import LOGGING_CONFIG

if __name__ == "__main__":
    logging.config.dictConfig(LOGGING_CONFIG)
    logger = logging.getLogger(__name__)

    for i in range(10):
        logger.info(f"This is a test log line {i}")
        time.sleep(1.5)

組態檔案解析:

  • 版本號:組態字典中的版本號必須為 1。
  • 日誌器(loggers):定義了多個日誌器及其對應的處理器和等級。
  • 處理器(handlers):定義了兩個處理器:一個是旋轉檔案處理器(RotatingFileHandler),另一個是控制檯處理器(StreamHandler)。
  • 格式化器(formatters):定義了兩種格式化方式:檔案格式化和控制檯格式化。

這些範例展示瞭如何在 Python 中實作不同方式的日誌轉換。無論是根據檔案大小還是時間間隔進行轉換,都可以透過適當的設定來滿足不同的需求。希望這些範例能夠幫助你更好地管理和分析應用程式中的日誌資訊。