當你在開發 Python 應用時,常會使用 logging
模組來記錄程式運行的資訊。不過,你可能會遇到這個令人困惑的問題:
這篇文章將完整解析這個問題的根本原因、如何重現、以及該如何正確解決。當
logging
模組一開始就已經「有 handler」時,你後續設定的basicConfig()
完全無效!
🔍 問題描述:logging.basicConfig()
無效
在 Python 中,最常見的 logging 初始化寫法如下:
import logging
logging.basicConfig(level=logging.DEBUG, format='%(levelname)s: %(message)s')
logging.debug("This is a debug message.")
但當你在某些專案、Jupyter Notebook、或大型框架中執行時,發現 debug()
、info()
等訊息完全沒出現!
這時候你可能會懷疑是不是格式錯、等級錯,其實——問題很可能是:
logging.hasHandlers() == True
⚠️ 為什麼 basicConfig
會失效?
根據官方文件,logging.basicConfig()
只會在尚未設定任何 handler 時才會生效。如果在你呼叫 basicConfig()
之前,logging 模組的 root logger 已經有 handler,則該設定會被忽略。
你可以透過以下程式碼確認:
import logging
print(logging.getLogger().hasHandlers()) # 如果是 True,表示已經有 handler 存在
在 Jupyter Notebook、某些框架(如 Flask、Django)、或第三方函式庫中,logging 可能早在你自己設定前就被初始化了!
✅ 解決方法一:強制移除所有 handler 再設定
若你能掌控主程式流程,這是最直接、有效的方式:
import logging
# 先移除 root logger 的所有 handlers
root_logger = logging.getLogger()
if root_logger.hasHandlers():
root_logger.handlers.clear()
# 然後再設定 basicConfig
logging.basicConfig(level=logging.DEBUG, format='%(asctime)s - %(levelname)s - %(message)s')
logging.debug("重新設定 logging 成功!")
📌 handlers.clear()
是從 Python 3.3 開始支援的。若使用較舊版本,請改用以下方式:
for handler in root_logger.handlers[:]:
root_logger.removeHandler(handler)
✅ 解決方法二:手動添加 handler(繞過 basicConfig)
如果你希望更細緻地控制 logging,也可以不使用 basicConfig
,自己建立 handler 並加入 logger:
import logging
logger = logging.getLogger()
logger.setLevel(logging.DEBUG)
# 建立 StreamHandler 並設定格式
console_handler = logging.StreamHandler()
formatter = logging.Formatter('%(asctime)s - %(levelname)s - %(message)s')
console_handler.setFormatter(formatter)
# 若已有 handler,先清除
logger.handlers.clear()
logger.addHandler(console_handler)
logger.debug("手動添加 handler 成功!")
這種方法特別適用於你需要同時輸出到檔案和 console、或設定多個 logger 的情境。
✅ 解決方法三:子 logger 不繼承 handler
如果你使用的是子 logger,例如 logging.getLogger('my.module')
,你可以控制它是否繼承 root handler:
logger = logging.getLogger("my.module")
logger.propagate = False # 不要繼承 root handler
# 自訂 handler
handler = logging.StreamHandler()
handler.setFormatter(logging.Formatter('%(name)s - %(levelname)s - %(message)s'))
logger.addHandler(handler)
logger.setLevel(logging.INFO)
logger.info("使用子 logger 且避免繼承 root handler")
🧪 範例重現問題
以下是一個會讓 basicConfig
無效的情境:
import logging
# 第三方模組或其他地方已加入 handler
logging.getLogger().addHandler(logging.NullHandler())
# 接下來 basicConfig 就沒有效果
logging.basicConfig(level=logging.DEBUG, format='%(levelname)s: %(message)s')
logging.debug("這行不會顯示!")
執行後,這行 debug()
不會有輸出。
🔚 結語與建議
當你發現 logging.basicConfig()
沒有效果時,不要懷疑人生——先檢查:
import logging
print(logging.getLogger().hasHandlers()) # 若是 True,代表你要手動清 handler!
✅ 建議實務作法:
- 確保
basicConfig()
是程式一開始就設定。 - 若不能保證,請先
clear()
所有 handler 再重新設定。 - 更進階的控制請用
StreamHandler
、FileHandler
自行加入 handler。 - 使用多個 logger 時,善用
propagate
控制是否傳遞至 root logger。
如果你需要一個可複用的初始化函式,可以這樣寫:
def setup_logger(level=logging.DEBUG):
logger = logging.getLogger()
if logger.hasHandlers():
logger.handlers.clear()
logging.basicConfig(level=level, format='%(asctime)s - %(levelname)s - %(message)s')
之後只要呼叫:
setup_logger()
就能確保 logging 行為一致、輸出正常。