Python日誌系統非常豐富。新增結構化或非結構化日誌輸出到python程式碼,寫到檔案,輸出到控制檯,傳送到系統日誌,或者自定義輸出格式都很容易。
我們正在重新檢查mozharness中日誌的工作機制,希望提取程式碼能更容易,並減少摻合模式的使用。
下面是一些在python日誌中真正幫到我們的建議和技巧:
可以有多個logger
好吧,對一個給定名稱確實只會有一個logger。特殊的“根”logger沒有名稱。對相同名稱多次呼叫getLogger(name)
會返回相同的logger物件。這個特性很重要,你不用在程式碼中顯式將logger物件傳來傳去。你可以通過名稱來獲取它們。日誌模組維護了一個全域性日誌物件登錄檔。
你可以使用多個logger物件,每個只限定於其特定模組,甚至類或例項。
每個logger都有一個名稱,通常是logger所處模組的名稱。你能在Python模組中看到的通用模式大概這樣:
1 2 3 |
# in module foo.py import logging log = logging.getLogger(__name__) |
這段程式碼沒錯是因為foo.py
中,__name__
等於”foo”。所以在此模組中,log
物件只會用於這個模組。
logger可分級的
名稱空間中的logger名稱使用“.”來分層。這意味著如果你有foo.bar
和foo.baz
兩個logger,你可以操作foo
,這樣它的兩個子logger都會起作用。尤其是,你可以設定foo
的日誌等級來顯示或忽略兩個子模組的除錯訊息。
1 2 3 |
# 我們為所有的foo模組啟用全部除錯日誌輸出 import logging logging.getLogger('foo').setLevel(logging.DEBUG) |
日誌訊息類似事件,會在層次結構中流動
假設我們有個模組foo.bar:
1 2 3 4 5 |
import logging log = logging.getLogger(__name__) # __name__ is "foo.bar" here def make_widget(): log.debug("made a widget!") |
當我們呼叫make_widget()
時,程式碼生成了一個除錯日誌訊息。層次結構中的每個logger都有機會將這個訊息輸出、忽略、傳遞給父級。
日誌預設並沒有配置其等級(或設定為NOTSET
))。這意味著logger只會把訊息傳遞給父級,然後不斷重複這個步驟,一直到根logger。
所以如果foo.bar
logger沒有指定等級,訊息將繼續傳遞到foo
logger。如果foo
logger沒有指定等級,訊息將會傳遞給根logger。
這就是為什麼你通常需要在根logger上配置日誌輸出的原因;它通常會輸出全部訊息!!!這太常見了,所以有個專門的方法來配置根logger:logging.basicConfig()
。
這也允許我們根據訊息的來源使用混合等級的日誌輸出:
1 2 3 4 5 6 7 8 9 10 11 |
import logging # 為所有的foo模組啟動除錯日誌輸出 logging.getLogger("foo").setLevel(logging.DEBUG) # 配置根logger只列印INFO訊息,並輸出到控制檯 # (預設值) logging.basicConfig(level=logging.INFO) # 將會輸出除錯訊息 logging.getLogger("foo.bar").debug("ohai!") |
如果註釋掉setLevel(logging.DEBUG)
,你將不會看到任何訊息。
exc_info是最棒的
所有的內建日誌呼叫都支援exc_info
關鍵字,如果它不是false,當前異常資訊將會附加到日誌訊息中,比如:
1 2 3 4 5 6 7 8 9 |
import logging logging.basicConfig(level=logging.INFO) log = logging.getLogger(__name__) try: assert False except AssertionError: log.info("surprise! got an exception!", exc_info=True) |
log.exception()
是個特例,等價於log.error(..., exc_info=True)
。
Python 3.2引入了一個新的關鍵字stack_info
,將會輸出當前棧到當前程式碼。當你想知道如何執行到程式碼的某處時,非常方便,即使沒有異常發生也可以。
“找不到處理函式…”
你很可能遇到過這個訊息,尤其是使用第三方模組時。這個錯誤的意思是,你沒有配置任何日誌處理函式,但是某處嘗試列印日誌訊息。這個訊息沿著層次結構向上傳遞,直到在結構鏈的頂處失敗(也許我需要一個更好的比喻)。
1 2 3 |
import logging log = logging.getLogger() log.error("no log for you!") |
輸出:
1 |
No handlers could be found for logger "root" |
這裡可以做兩件事:
1.在模組中使用basicConfig()
或類似的方法配置日誌
2.庫作者應該在模組頂層新增一個NullHandler 來防止這種情況發生。看下這裡的指南和這篇部落格來了解更多資訊。
更多?
我極力推薦你讀下日誌文件和指南,它們提供了更多有用資訊(寫的也很好!)。你也可以通過定製自己的日誌處理器來做更多事情,不同的輸出格式、一次輸出到多個位置等。玩得高興!