Python 的 logging 模組提供強大的日誌管理功能,對於追蹤程式執行狀態和診斷問題至關重要。本文將逐步講解如何構建一個記錄異常的裝飾器,並示範如何利用 RotatingFileHandler 和 TimedRotatingFileHandler 進行日誌檔案轉換,避免日誌檔案過大,同時也探討了使用組態檔案集中管理日誌組態的方法。首先,我們會建立一個 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.2 和 test.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)
內容解密:
- 匯入模組:首先匯入
logging和time模組,並從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 中實作不同方式的日誌轉換。無論是根據檔案大小還是時間間隔進行轉換,都可以透過適當的設定來滿足不同的需求。希望這些範例能夠幫助你更好地管理和分析應用程式中的日誌資訊。