Python 的 logging 用法全教學
剛開始學習 Python 時,我們最常用來除錯的幫手就是 print(),不管是要檢查某個變數的值、確定程式有沒有跑 if 判斷式、或是追蹤迴圈的進度,用 print()把值印出來看看,通常能解決大多數的問題。
Python 的 logging 用法全教學

2020.02.01 Bangkok, Thailand
剛開始學習 Python 時,我們最常用來除錯的幫手就是 print(),不管是要檢查某個變數的值、確定程式有沒有跑 if 判斷式、或是追蹤迴圈的進度,用 print()把值印出來看看,通常能解決大多數的問題。
然而,隨著專案規模逐漸龐大、程式碼行數從幾十行擴張到幾千行,單純依賴 print()會變得很麻煩,並且會遇到以下問題:
- 終端機被無用的資訊淹沒: 所有的輸出都混在一起,很難分辨哪行是不重要的測試訊息,哪行是系統發出的嚴重錯誤警告。
- 缺乏關鍵的上下文資訊: 當錯誤發生時,
print()預設不會告訴你這行字是幾點幾分印出的、來自哪個檔案,又或者是出自哪一個函式,所以除錯時又要花力氣再整理一次資訊。 - 上線前的維護夢魘: 當程式準備部署到正式環境時,為了避免暴露敏感資訊或拖慢效能,你必須全域搜尋並手動刪除 (或註解掉) 一堆
print();一旦又需要除錯,你還得再把它們一個個加回來。 - 難以有效儲存與管理:
print()預設只能將內容輸出到螢幕上,如果想要同時將紀錄寫入檔案留存、或是根據錯誤嚴重程度發送到不同的監控系統,處理起來會非常吃力。
因此,本文要來介紹一個 Python 裡面可以解決這個問題的模組 - logging。
分級機制
相比於陽春的 print(),logging 提供了強大的分級機制 (從 DEBUG 到 CRITICAL),不僅能自動補齊時間、檔案位置等上下文,還能透過簡單的設定,決定哪些訊息要顯示在螢幕上、哪些訊息要默默寫入日誌檔中。
logging 模組最特別的設計,就是將輸出的訊息依照「嚴重程度」分級,也就是說,針對每一條內容,你可以決定它的嚴重程度,並且在執行專案時決定要印出哪個層級以上的內容。
Python 內建了五個標準的日誌層級,嚴重程度由低到高分別為
- DEBUG (級別 10): 最詳細的資訊,通常只有在開發階段或除錯 (Debug)時才會用到。例如:印出迴圈當下的變數值、API 請求的詳細參數。
- INFO (級別 20): 用來確認程式正按照預期正常運作,記錄系統的日常狀態。例如:「伺服器已成功啟動」、「資料庫連線建立成功」、「使用者已登入」。
- WARNING (級別 30): 警告訊息。表示發生了某些非預期的狀況,或未來可能會出問題,但程式目前仍在正常執行。例如:「磁碟空間即將不足」、「使用了準備廢棄的函式」(這是 Python logging 的預設層級)
- ERROR (級別 40): 發生了錯誤,導致程式的某個特定功能無法正常執行。例如:「讀取檔案失敗」、「API 回傳 404 找不到網頁」。
- CRITICAL (級別 50): 最嚴重的錯誤,通常代表程式本身已經無法繼續執行,即將崩潰或強制關閉。例如:「記憶體耗盡 (Out of Memory)」、「硬體連線全面中斷」。
執行 logging 程式
了解了層級之後,我們來看看最基礎的用法。你可以直接載入 logging 模組,並呼叫對應層級的方法:
import logging
logging.debug("這是一條 DEBUG 訊息:用來除錯的詳細資料")
logging.info("這是一條 INFO 訊息:程式正常運作中")
logging.warning("這是一條 WARNING 訊息:請注意,某個變數值怪怪的")
logging.error("這是一條 ERROR 訊息:糟糕,讀不到設定檔")
logging.critical("這是一條 CRITICAL 訊息:系統崩潰啦!")
如果把這段程式碼貼到 Python 裡執行,終端機只會印出以下三行:
WARNING:root:這是一條 WARNING 訊息:請注意,某個變數值怪怪的
ERROR:root:這是一條 ERROR 訊息:糟糕,讀不到設定檔
CRITICAL:root:這是一條 CRITICAL 訊息:系統崩潰啦!
為什麼 DEBUG 和 INFO 不見了? 這就是 logging 比 print() 聰明的地方。正如前面所提,Python logging 預設的日誌層級是 WARNING。這代表只有嚴重程度「大於或等於 WARNING」的訊息才會被輸出。這個機制完美解決了我們前面提到的痛點:在正式上線的環境中,你只需要把層級設高,那些繁瑣的 DEBUG 訊息就會自動被隱藏,再也不用手動去刪除它們!
調整層級與格式
如果你在開發階段,就是想要看到 DEBUG 和 INFO 訊息,或是覺得預設的輸出格式太醜,少了我們最需要的「時間」和「檔案位置」,這時就可以使用 logging.basicConfig() 來做全域的基礎設定。
import logging
# 進行基礎設定
logging.basicConfig(
level=logging.DEBUG, # 將顯示層級調低到 DEBUG,這樣所有訊息都會顯示
format='%(asctime)s - %(filename)s - %(levelname)s - %(message)s', # 自訂輸出格式
datefmt='%Y-%m-%d %H:%M:%S' # 自訂時間格式
)
logging.debug("現在你看得到 DEBUG 訊息了!")
logging.error("這是一條帶有時間戳記的錯誤訊息")
裡面每一個被 %(...)s 包起來的東西,都是一個特殊的變數 (結尾的 s 代表把它轉換成字串 String),以下是它們各自代表的意思:
**%(asctime)s(發生時間,ASCII Time):** 記錄這行程式碼被執行的確切時間。它印出來的時間格式,會直接受到下方datefmt='%Y-%m-%d %H:%M:%S'的控制 (也就是年-月-日 時:分:秒)。**%(filename)s(檔案名稱,File Name):** 印出是哪一個 Python 檔案發出了這條日誌。以我們剛才的例子來說,它會印出main.py。**%(levelname)s(日誌層級名稱,Level Name):** 印出這條紀錄的嚴重程度。例如DEBUG、INFO或ERROR。**%(message)s(你輸入的訊息,Message):** 這就是你在括號裡面真正想要印出來的那句話。例如"現在你看得到 DEBUG 訊息
執行後的輸出結果會變成這樣:
2026-07-27 22:50:01 - main.py - DEBUG - 現在你看得到 DEBUG 訊息了!
2026-07-27 22:50:01 - main.py - ERROR - 這是一條帶有時間戳記的錯誤訊息
透過簡單的一行 basicConfig,我們不僅修改了觸發層級,還自動補上了精準的發生時間 (%(asctime)s)、發生檔案 (%(filename)s) 以及訊息層級 (%(levelname)s)。除錯時,這點上下文資訊可以幫我們省下大把的時間。
將日誌寫入檔案
到目前為止,我們的日誌都只印在終端機 (螢幕) 上。這在開發階段很方便,但如果你的程式是準備要長期運行的網路爬蟲、或是架設在伺服器上的後端 API,你總不可能 24 小時盯著螢幕看。如果半夜發生了 ERROR,隔天早上螢幕早就被其他資訊洗版了,要怎麼追查?
因此,將日誌儲存成實體的 .log 檔案,是實務上絕對必備的技巧。
好消息是,在 Python 裡要把日誌存成檔案非常簡單,我們只需要在剛剛學過的 basicConfig() 裡面,多加上幾個參數就可以了:
import logging
logging.basicConfig(
level=logging.INFO,
format='%(asctime)s - %(levelname)s - %(message)s',
filename='my_app.log', # 指定輸出的日誌檔案名稱
filemode='a', # 檔案寫入模式:'a' (附加) 或 'w' (覆寫)
encoding='utf-8' # 建議加上 utf-8,避免輸出中文時變成亂碼 (Python 3.9+)
)
logging.info("系統啟動,開始執行任務...")
logging.warning("API 回應速度稍微變慢,但不影響運作")
logging.error("找不到目標檔案 data.csv!")
當你執行這段程式碼時,你會發現終端機什麼都沒有印出來。這代表程式成功了。去檢查你的資料夾,你會發現多了一個名為 my_app.log 的檔案。打開它,裡面完整記錄了:
2026-07-28 11:32:57,123 - INFO - 系統啟動,開始執行任務...
2026-07-28 11:32:57,124 - WARNING - API 回應速度稍微變慢,但不影響運作
2026-07-28 11:32:57,125 - ERROR - 找不到目標檔案 data.csv!
filemode是可以調整的,比較常見的是 'a' 和 'w' 兩種模式。
**'a'(Append / 附加):** 這是預設的模式。每次執行程式時,新的日誌都會「接續」寫在檔案的最尾端。這通常是我們最想要的行為,因為你不會希望每次重啟程式就把昨天的紀錄都刪光。**'w'(Write / 覆寫):** 如果你希望每次執行程式時,都會把舊的日誌清空,從頭開始記錄,就可以把模式改成'w'。這在反覆測試同一小段程式碼時很方便。
學會了 basicConfig 之後,你可能會遇到一個新的困境:
如果我加了 filename,終端機就看不到日誌了;如果我不加 filename,又沒辦法存成檔案。難道不能兩個都要嗎?
除此之外,如果你負責的是一個大型專案,你可能會希望:
- 一般的
INFO訊息印在螢幕上就好。 - 只要發生
ERROR等級以上的錯誤,不僅要印在螢幕上,還要存進error.log檔案裡。 - 如果是第三方的套件 (例如 requests) 發出的日誌,把它過濾掉。
當你的需求變得如此靈活時,單靠一個簡單的 logging.basicConfig() 已經不夠用了。這時候,我們就要進入 logging 模組真正的強大之處——也就是它的「四大核心組件」:Logger (紀錄器)、Handler (處理器) 與 Formatter (格式器) 。
Logger、Handler 與 Formatter
前面學到的 logging.basicConfig() 雖然方便、快速,但所有的設定 (包含層級、格式、輸出位置) 都是全域 (Global) 綁死在一起的。
如同我們在設計大型物件導向 (OOP) 系統時,會傾向將不同的職責拆分到不同的類別 (Class) 以提高彈性;logging 模組的底層,其實也是基於這種優雅的解耦架構。要實現「同時輸出螢幕與檔案」,甚至「螢幕只顯示 INFO,檔案卻專門記錄 ERROR」,我們需要認識 logging 的三大核心元件:
Logger (紀錄器): 它是你的專屬秘書,也是你在程式碼中唯一需要互動的物件 (例如呼叫 logger.info())。它的工作是接收你傳來的訊息,並根據嚴重程度判斷是否要往下傳遞。
Handler (處理器): 它是物流中心。當 Logger 決定放行一條訊息後,會交給 Handler 決定「要把訊息送到哪裡」。一個 Logger 可以同時綁定多個 Handler。常見的 Handler 包括:
StreamHandler:負責將訊息發送到終端機 (螢幕)。FileHandler:負責將訊息寫入檔案。
Formatter (格式器): 它是包裝設計師。負責決定訊息最終呈現的排版格式 (包含時間、檔名等)。你可以為不同的 Handler 配置不同的 Formatter。
現在我們就可以自己組裝一個多功能的 logger
import logging
# 步驟 1:建立並命名 Logger (你的專屬秘書)
# 慣例上會使用 __name__ 當作名稱,這樣能輕易追蹤這條日誌是哪個 Python 檔案發出的
logger = logging.getLogger(__name__)
logger.setLevel(logging.DEBUG) # 設定 Logger 的總接收門檻為最低的 DEBUG
# 步驟 2:建立 Handlers (物流中心)
# 2.1 螢幕輸出處理器:我們希望螢幕畫面乾淨一點,只顯示 INFO 等級以上的訊息
console_handler = logging.StreamHandler()
console_handler.setLevel(logging.INFO)
# 2.2 檔案輸出處理器:我們希望檔案只記錄 ERROR 等級以上的嚴重錯誤
file_handler = logging.FileHandler('error.log', encoding='utf-8')
file_handler.setLevel(logging.ERROR)
# 步驟 3:建立 Formatters (包裝設計師)
# 螢幕上的格式可以簡單一點,檔案裡的格式我們希望詳細記錄時間
console_format = logging.Formatter('%(name)s - %(levelname)s - %(message)s')
file_format = logging.Formatter('%(asctime)s - [%(levelname)s] 在 %(filename)s 中發生錯誤:%(message)s')
# 將格式器綁定到對應的處理器上
console_handler.setFormatter(console_format)
file_handler.setFormatter(file_format)
# 步驟 4:把處理器全部綁定到 Logger 身上
logger.addHandler(console_handler)
logger.addHandler(file_handler)
# ================= 測試時間 =================
logger.debug("這是一條 DEBUG:Logger 收到了,但兩個 Handler 的門檻都比較高,所以這條會被丟掉。")
logger.info("這是一條 INFO:會出現在螢幕上,但不會寫入 error.log。")
logger.warning("這是一條 WARNING:螢幕上看得到,同樣不會寫入檔案。")
logger.error("這是一條 ERROR:螢幕上看得到,而且終於被寫入 error.log 裡了!")
當你執行這段程式碼時,終端機 (螢幕) 會印出:
__main__ - INFO - 這是一條 INFO:會出現在螢幕上,但不會寫入 error.log。
__main__ - WARNING - 這是一條 WARNING:螢幕上看得到,同樣不會寫入檔案。
__main__ - ERROR - 這是一條 ERROR:螢幕上看得到,而且終於被寫入 error.log 裡了!
同時,資料夾中會生成一個 error.log 檔案,裡面精準地只記錄了錯誤資訊,並且帶有完整的時間與檔案出處:
2026-07-28 13:50:42,881 - [ERROR] 在 main.py 中發生錯誤:這是一條 ERROR:螢幕上看得到,而且終於被寫入 error.log 裡了!
透過這種物件導向的組裝方式,你完全掌握了日誌的控制權。日後不管系統怎麼擴展,想要把日誌發送到 Email、寫入資料庫、或是推送到雲端監控服務,都只需要「再多掛載一個對應的 Handler」就能無痛達成,原本的程式碼一行都不用改。
在多個 Python 檔案中部屬 logger
當你的專案從單一腳本,成長為包含 main.py(主程式)、database.py(資料庫操作)、api.py(網路請求) 等多個模組的大型專案時,你馬上會面臨一個現實的問題:
難道我剛剛寫的那一大串建立 Logger、Handler、Formatter 的程式碼,要在每一個 Python 檔案裡都複製貼上一次嗎?
這麼做很沒效率,也很容易出錯,因為未來如果你想修改日誌的時間格式,還要打開幾十個檔案一個一個改。
Python 的 logging 模組在設計時,就已經預判了這個需求。logging 內部採用了一種類似「樹狀結構」的層級設計,並且具有自動「向上傳遞」的特性。
簡單來說,你只需要在專案的入口點 (通常是 main.py)統一設定好一次 Handler 和 Formatter。在其他所有的子模組 (如 database.py) 中,只要用一行程式碼呼叫專屬的 Logger,它就會自動「繼承」主程式的設定。
讓我們來看看實務上到底怎麼寫:
1. 主程式 (main.py):負責統一設定
在主程式中,我們進行全局的配置。這裡為了示範簡潔,我們使用 basicConfig 來設定根 (Root) Logger,當然你也可以用上一段學到的進階組裝法。
# main.py
import logging
import database # 載入你的另一個 Python 模組
# 在程式的進入點,統一設定好日誌的格式與輸出位置
logging.basicConfig(
level=logging.INFO,
format='%(asctime)s - [%(name)s] - %(levelname)s - %(message)s'
)
# 建立 main.py 專屬的 logger
logger = logging.getLogger(__name__)
def main():
logger.info("主程式啟動,準備連接資料庫...")
database.connect_db() # 呼叫另一個檔案的函式
logger.info("主程式執行完畢。")
if __name__ == "__main__":
main()
2. 子模組 (database.py):只負責發出日誌
在其他的模組裡,你完全不需要再設定任何格式或檔案名稱:
# database.py
import logging
# 關鍵:直接用 __name__ 建立專屬這個檔案的 logger
logger = logging.getLogger(__name__)
def connect_db():
logger.info("正在嘗試連接到 PostgreSQL...")
# 假設這裡發生了錯誤
logger.error("連接超時!請檢查資料庫伺服器狀態。")
看看執行的結果:
當你執行 python main.py 時,終端機會完美印出以下內容:
2026-07-28 14:15:30,110 - [__main__] - INFO - 主程式啟動,準備連接資料庫...
2026-07-28 14:15:30,111 - [database] - INFO - 正在嘗試連接到 PostgreSQL...
2026-07-28 14:15:30,112 - [database] - ERROR - 連接超時!請檢查資料庫伺服器狀態。
2026-07-28 14:15:30,113 - [__main__] - INFO - 主程式執行完畢。
仔細看上面的輸出結果,有沒有發現中括號 [...] 裡面的名稱不一樣?
- 在
main.py裡,__name__的值是__main__。 - 在
database.py裡,__name__的值則是模組名稱database。
當你在 database.py 呼叫 logging.getLogger(__name__) 時,它會去尋找名為 database 的 Logger。因為你沒有特別為它設定 Handler,它就會乖乖地把訊息向上層傳遞,最後交由 main.py 中設定好的全域規則來輸出。
這種「每個檔案都擁有自己名字的 Logger,但共享同一套輸出規則」的作法,能讓你在未來的除錯過程中,一眼就看出這行錯誤到底是從哪一個模組、哪一個檔案噴出來的,乾淨俐落。
用 logger.exception做錯誤追蹤
在寫 Python 程式時,為了避免程式因為突發狀況而整個崩潰死當,我們通常會使用 try...except 區塊來捕捉並處理例外狀況 (Exceptions)。
很多初學者在剛換掉 print()改用 logging 時,在 except 區塊裡這樣寫:
import logging
logger = logging.getLogger(__name__)
def divide(a, b):
return a / b
try:
result = divide(10, 0)
except Exception as e:
# 新手常見寫法:只記錄了錯誤訊息字串
logger.error(f"糟糕,發生錯誤了:{e}")
如果你執行這段程式碼,終端機確實會印出: ERROR - 糟糕,發生錯誤了:division by zero
在這麼簡單的程式裡,你當然一眼就知道是因為 10 / 0 導致的錯誤。但想像一下,如果你是在幾千行程式碼的大型專案裡,呼叫了十幾個層層嵌套的函式,最後看到這句 division by zero,你絕對會崩潰——因為你根本不知道這個錯誤是在哪一個檔案、哪一行程式碼發生的!
單純使用 logger.error(),會把 Python 內建那串最有價值的「錯誤追蹤訊息 (Traceback)」給吞掉。為了解決這個問題,logging 模組提供了一個專門在 except 區塊內使用的殺手級方法:logger.exception()。
我們把剛剛的程式碼稍微改一行:
try:
result = divide(10, 0)
except Exception as e:
# 內行寫法:使用 logger.exception
logger.exception("計算過程發生嚴重的錯誤!")
再一次執行程式碼,看看這次終端機會印出什麼:
ERROR - 計算過程發生嚴重的錯誤!
Traceback (most recent call last):
File "main.py", line 7, in <module>
result = divide(10, 0)
File "main.py", line 4, in divide
return a / b
ZeroDivisionError: division by zero
logger.exception() 不僅會以 ERROR 層級記錄你自訂的錯誤提示訊息,它還會在背景默默幫你加上 exc_info=True 的參數,自動將最完整的 Traceback 原封不動地印出來 (或是寫入你的日誌檔案裡)。注意到 exception()的等級歸類在 error(),它是專門給 except 抓錯用的工具。
這樣一來,你的程式既不會因為未處理的錯誤而中斷服務,你也能在日誌檔案中保留最詳細的案發現場線索,讓後續的除錯工作變得輕而易舉。
日誌切割
當你學會了將日誌寫入檔案後,如果你的程式是一個需要 24 小時不間斷運行的服務 (例如:網站後端、Discord 機器人或是長時間的數據收集爬蟲),你很快就會面臨一個新的問題:日誌檔無限膨脹。
想像一下,你的程式每天產生 50 MB 的日誌,一個月後這個 app.log 就會變成 1.5 GB。不但會吃光你伺服器的硬碟空間,當你想打開這個檔案來查錯誤時,普通的文字編輯器還會直接當機卡死。
為了解決這個問題,業界標準的作法是進行「日誌切割 (Log Rotation)」。在 logging.handlers 模組中,提供了兩種非常實用的進階處理器:
1. 按檔案大小切割:RotatingFileHandler
如果你希望嚴格控管日誌檔案所佔用的總硬碟空間,這是最好的選擇。你可以設定當檔案達到某個容量上限時,自動將舊檔案備份,並建立一個新的空檔案繼續寫入。
import logging
from logging.handlers import RotatingFileHandler
logger = logging.getLogger(__name__)
logger.setLevel(logging.INFO)
# 設定:當檔案達到 5MB 時自動切割,最多保留 3 份舊備份
handler = RotatingFileHandler(
'my_app.log',
maxBytes=5 * 1024 * 1024, # 5 MB (單位是 Bytes)
backupCount=3, # 保留 my_app.log.1, my_app.log.2, my_app.log.3
encoding='utf-8'
)
formatter = logging.Formatter('%(asctime)s - %(levelname)s - %(message)s')
handler.setFormatter(formatter)
logger.addHandler(handler)
它的運作機制是這樣的: 程式會一直把日誌寫入 my_app.log。當這個檔案超過 5MB 時,Python 會自動把它重新命名為 my_app.log.1,然後建立一個全新的 my_app.log 繼續寫。當再次滿 5MB 時,.1 會變成 .2,新的滿檔變成 .1。以此類推,超過 3 份的最舊備份就會被系統無情地刪除,永遠不會撐爆你的硬碟。
2. 按時間切割:TimedRotatingFileHandler
如果你的專案需要按天來查核日誌 (例如:老闆請你查「昨天晚上 10 點」的連線錯誤),那麼按時間切割會更直覺。
import logging
from logging.handlers import TimedRotatingFileHandler
logger = logging.getLogger(__name__)
logger.setLevel(logging.INFO)
# 設定:每天午夜 (midnight) 進行一次切割,保留最近 7 天的紀錄
handler = TimedRotatingFileHandler(
'daily_app.log',
when='midnight', # 切割時機:午夜
interval=1, # 每 1 個單位 (天) 切割一次
backupCount=7, # 留存 7 天
encoding='utf-8'
)
formatter = logging.Formatter('%(asctime)s - %(levelname)s - %(message)s')
handler.setFormatter(formatter)
logger.addHandler(handler)
它的運作機制是這樣的: 每天午夜一過,Python 就會把昨天的檔案加上日期後綴,例如變成 daily_app.log.2026-07-27,然後繼續把今天的日誌寫入全新的 daily_app.log 中。這對於每日資料備份與歷史追蹤來說,簡直是完美的設計。
用 dictConfig 解耦
當你的專案成長到一定規模,或者準備進入團隊協作的階段,你會發現另一個管理上的麻煩:在程式碼裡面寫一大堆建立 Logger、Handler 和 Formatter 的邏輯,實在是太佔版面了!
更致命的是,寫死在程式碼裡的設定缺乏彈性。想像一下,你的網站平常在正式環境中只記錄 INFO,某天半夜突然發生莫名的 Bug,你需要臨時把日誌層級降到 DEBUG 來收集更多線索。如果設定寫死在程式碼裡,你竟然需要修改程式、甚至重新部署整個服務,這在正式環境中是風險極高的操作。
為了達到「設定與程式碼分離」,業界最推崇的做法是使用 logging.config.dictConfig。
dictConfig 允許我們將所有複雜的日誌架構(包含哪些 Formatter、哪些 Handler、以及各個 Logger 的層級路由) 全部定義在一個字典 (Dictionary)中。
實務上,開發者通常會把這份字典寫成外掛的 JSON 或 YAML 檔案。這裡我們以直觀的 Python 字典為例,看看這能讓主程式碼變得多乾淨:
import logging
import logging.config
import yaml # 實務上常搭配 yaml 模組讀取外部設定檔
# 將所有複雜的設定獨立成一個字典 (實務上會從 logging_config.yaml 讀取)
LOGGING_CONFIG = {
'version': 1,
'disable_existing_loggers': False,
'formatters': {
'standard': {
'format': '%(asctime)s - [%(levelname)s] - %(name)s - %(message)s'
},
'simple': {
'format': '%(levelname)s - %(message)s'
},
},
'handlers': {
'console': {
'level': 'INFO',
'class': 'logging.StreamHandler',
'formatter': 'simple',
},
'file': {
'level': 'ERROR',
'class': 'logging.handlers.RotatingFileHandler',
'filename': 'app_error.log',
'maxBytes': 10485760, # 10 MB
'backupCount': 5,
'formatter': 'standard',
},
},
'loggers': {
'': { # 這是 Root Logger (根紀錄器)
'handlers': ['console', 'file'],
'level': 'DEBUG',
'propagate': True
}
}
}
# 魔法發生在這裡:一行程式碼,瞬間套用所有複雜設定!
logging.config.dictConfig(LOGGING_CONFIG)
# 接下來的程式碼完全不用管設定,直接拿來用就好
logger = logging.getLogger(__name__)
logger.info("系統啟動!現在的主程式碼變得非常乾淨。")
logger.error("這條錯誤會按照設定檔的規則,安靜地寫入 app_error.log 裡,同時印在螢幕上。")
這樣有幾個好處:
- 程式整潔: 主程式 (或啟動檔) 裡再也看不到落落長的 Handler 與 Formatter 設定,開發者可以專注在主要內容上。
- 環境切換: 你可以準備兩份檔案:
logging_dev.yaml(開發用,輸出 DEBUG) 和logging_prod.yaml(正式用,只輸出 WARNING,且自動切割檔案)。程式啟動時,只要根據環境變數讀取對應的檔案即可。
學會了 dictConfig,現在你就具備基本的logging 知識了。
結語
這篇文章的目的是希望初學者在用了一陣子的 print()大法後,也學會用更完善的 logging 系統,短期內雖然會比直接寫 print() 多花一點點時間進行基礎設定,當你的專案在半夜發生未知的 Bug,或是幾個月後需要交接、進行系統維護時,這些結構化、帶有完整上下文且分類明確的日誌,會讓你輕鬆很多。
메타데이터
- post_id
- d5508aab9671
- slug
- python-的-logging-用法全教學-d5508aab9671
- url
- https://medium.com/@acamvproducingstudio/python-%E7%9A%84-logging-%E7%94%A8%E6%B3%95%E5%85%A8%E6%95%99%E5%AD%B8-d5508aab9671
- canonical_url
- https://medium.com/@acamvproducingstudio/python-%E7%9A%84-logging-%E7%94%A8%E6%B3%95%E5%85%A8%E6%95%99%E5%AD%B8-d5508aab9671
- author_url
- https://medium.com/@acamvproducingstudio
- status
- ok
- fetched_at
- 2026-08-07 06:05:25