當前位置: 妍妍網 > 辦公

萬字長文詳解python logging日誌模組

2024-03-21辦公

這篇文章熬了一周,終於寫完了。一個知識點自己理解可能只需要花半個小時,而要想把它寫出來讓別人理解,要花十倍甚至更多的時間。所以說寫技術文是真的不容易。而它的價值在於它的生命力更長久。即使三五年後給別人看依然會有收獲。對寫作者自己而言,寫的過程也是對知識的一次更通透的理解。

說到日誌,無論是寫框架程式碼還是業務程式碼,都離不開日誌的記錄,他能給我們定位問題帶來極大的幫助。

記錄日誌最簡單的方法就是在你想要記錄的地方加上一句 print , 我相信無論是新手還是老鳥都經常這麽幹。在簡單的程式碼中或者小型計畫中這麽幹一點問題都沒有。但是在一些稍大一點的計畫,有時候定位一個問題,需要檢視歷史日誌定位問題,用print就不合時宜了。

print 打印出來的日誌沒有時間,不知道日誌記錄的位置,也沒有可讀的日誌格式, 還不能把日誌輸出到指定檔。。。。除非這些你都全部自己重復造一遍輪子。

最佳的做法是使用內建的logging模組, 因為 logging 模組給開發者提供了非常豐富的功能。

比如上圖就是用標準庫logging模組記錄生成的日誌,有日誌的具體時間、日誌發生的模組、有日誌級別和日誌的具體內容等等

怎麽用呢,來看個例子

匯入logging模組,然後直接使用logging提供的日誌訊息記錄方法就可以。

日誌級別

日誌級別分為以下5個級別

日誌級別 使用場景
DEBUG debug級別用來記錄詳細的資訊,方便定位問題進行偵錯,在生產環境我們一般不開啟DEBUG
INFO 用來記錄關鍵程式碼點的資訊,以便程式碼是否按照我們預期的執行,生產環境通常會設定INFO級別
WARNING 記錄某些不預期發生的情況,如磁盤不足
ERROR 由於一個更嚴重的問題導致某些功能不能正常執行時記錄的資訊
CRITICAL 當發生嚴重錯誤,導致應用程式不能繼續執行時記錄的資訊

日誌級別重要程度逐次提高,python提供了5個對應級別的方法。預設情況下日誌的級別是WARGING, 低於WARING的日誌資訊都不會輸出。

從上面程式碼中可以看到loging.warging以後的日誌內容都打印在標準輸出流,也就是命令列視窗,但是logging.debug和info記錄的日誌不會打印出來。

修改日誌級別

如何讓debug級別的資訊也輸出?

當然是修改預設的日誌級別,在開始記錄日誌前可以使用 logging.basicConfig 方法來設定日誌級別

import logging
logging.basicConfig( level=logging.DEBUG)
logging.debug("this is debug")
logging.info("this is info")
logging.error("this is error")

設定為debug級別後,所有的日誌資訊都會輸出

DEBUG:root:this is debug
INFO:root:this is info
ERROR:root:this is error

日誌記錄到檔

前面的日誌預設會把日誌輸出到標準輸出流,就是只在命令列視窗輸出,程式重新開機後歷史日誌沒地方找,所以把日誌內容永久記錄是一個很常見的需求。同樣透過配置函式logging.basicConfig可以指定日誌輸出到什麽地方

import logging
logging.basicConfig(filename="test.log", level=logging.INFO)
logging.debug("this is debug")
logging.info("this is info")
logging.error("this is error")

這裏我指定日誌輸出到檔test.log中,日誌級別指定為了 INFO,最後檔中記錄的內容如下:

INFO:root:this is info
ERROR:root:this is error

每次重新執行時,日誌會以追加的方式在後面, 如果每次執行前要覆蓋之前的日誌,則需指定 filemode='w', 這個和 open 函式寫數據到檔用的參數是一樣的。

指定日誌格式

預設輸出的格式包含3部份,日誌級別,日誌記錄器的名字,以及日誌內容,中間用「:」連線。如果我們想改變日誌格式,例如想加入日期時間、顯示日誌器名字,我們是可以指定format參數來設定日誌的格式

import logging
logging.basicConfig(format='%(asctime)s %(levelname)s %(name)s %(message)s')
logging.error("this is error")

輸出

2021-12-15 07:44:16,547 ERROR root this is error

日誌格式化輸出提供了非常多的參數,除了時間、日誌級別、日誌訊息內容、日誌記錄器的名字外,還可以指定執行緒名,行程名等等

到這裏為止,日誌模組的基本用法就這些了,也能滿足大部份套用場景,更高級的方法接著往下看,可以幫助你更好的處理日誌

記錄器(logger)

前面介紹的日誌記錄,其實都是透過一個叫做日誌記錄器(Logger)的例項物件建立的,每個記錄器都有一個名稱,直接使用logging來記錄日誌時,系統會預設建立 名為 root 的記錄器,這個記錄器是根記錄器。記錄器支持層級結構,子記錄器通常不需要單獨設定日誌級別以及Handler(後面會介紹),如果子記錄器沒有單獨設定,則它的行為會委托給父級。

記錄器名稱可以是任意名稱,不過最佳實踐是直接用模組的名稱當作記錄器的名字。命名如下

logger = logging.getLogger(__name__)

預設情況下,記錄器采用層級結構,上句點作為分隔符排列在名稱空間的階層中。階層列表中位於下方的記錄器是列表中較高位置的記錄器的子級。例如,有個名叫 foo 的記錄器,而名字是 foo.bar,foo.bar.baz,和 foo.bam 的記錄器都是 foo 的子級。

├─foo
│ │ main.py
│ │ __init__.py
│ │
│ ├─bam
│ │ │ __init__.py
│ │ │
│ │
│ ├─bar
│ │ │ __init__.py
│ │ │
│ │ ├─baz
│ │ │ │ __init__.py
│ │ │ │

main.py

import foo
from foo import bar
from foo import bam
from foo.bar import baz
if __name__ == '__main__':
pass

foo.py

import logging
logging.basicConfig()
logger = logging.getLogger(__name__)
logger.setLevel(logging.INFO)
logger.info("this is foo")

這裏我只設定foo這個記錄器的級別為INFO

bar.py

import logging
logger = logging.getLogger(__name__)
logger.info("this is bar")

其它子模組都是像bar.py一樣類似的程式碼,都沒有設定日誌級別,最後的輸出結果是

INFO:foo:this is foo
INFO:foo.bar:this is bar
INFO:foo.bam:this is bam
INFO:foo.bar.baz:this is baz

這是因為 foo.bar 這個記錄器沒有設定日誌級別,就會向上找到已經設定了日日誌級別的祖先,這裏剛好找到父記錄器foo的級別為INFO,如果foo也沒設定的 ,就會找到根記錄器root,root預設的級別為WARGING。

處理器(Handler)

記錄器負責日誌的記錄,但是日誌最終記錄在哪裏記錄器並不關心,而是交給了另一個家夥--處理器(Handler)去處理。

例如一個Flask計畫,你可能會將INFO級別的日誌記錄到檔,將ERROR級別的日誌記錄到標準輸出,將某些關鍵日誌(例如有訂單或者嚴重錯誤)發送到某個信件地址通知老板。這時候你的記錄器添加多個不同的處理器來處理不同的訊息日誌,以此根據訊息的重要性發送的特定的位置。

Python內建了很多實用的處理器,常用的有:

1、StreamHandler 標準流處理器,將訊息發送到標準輸出流、錯誤流
2、FileHandler 檔處理器,將訊息發送到檔
3、RotatingFileHandler 檔處理器,檔達到指定大小後,啟用新檔儲存日誌
4、TimedRotatingFileHandler 檔處理器,日誌以特定的時間間隔輪換日誌檔

處理器操作

Handler 提供了4個方法給開發者使用,細心的你可以發現了,logger可以設定level,Handler也可以設定Level。透過setLevel可以將記錄器記錄的不同級別的訊息發送到不同的地方去。

import logging
from logging import StreamHandler
from logging import FileHandler
logger = logging.getLogger(__name__)
# 設定為DEBUG級別
logger.setLevel(logging.DEBUG)
# 標準流處理器,設定的級別為WARAING
stream_handler = StreamHandler()
stream_handler.setLevel(logging.WARNING)
logger.addHandler(stream_handler)
# 檔處理器,設定的級別為INFO
file_handler = FileHandler(filename="test.log")
file_handler.setLevel(logging.INFO)
logger.addHandler(file_handler)
logger.debug("this is debug")
logger.info("this is info")
logger.error("this is error")
logger.warning("this is warning")



執行後,在命令列視窗輸出的日誌內容是:

this is error
this is warning

輸出在檔的日誌內容是:

this is info
this is error
this is warning

盡管我們將logger的級別設定為了DEBUG,但是debug記錄的訊息並沒有輸出,因為我給兩個Handler設定的級別都比DEBUG要高,所以這條訊息被過濾掉了。

格式器(formatter)

格式器在文章的前面部份其實已經有所介紹,不過那是透過logging.basicConfig來指定的,其實格式器還可以以物件的形式來設定在Handler上。格式器可以指定日誌的輸出格式,要不要展示時間,時間格式什麽,要不要展示日誌的級別,要不要展示記錄器的名字等等,都可以透過一個格式器對訊息進行格式化輸出。

import logging
from logging import StreamHandler
logger = logging.getLogger(__name__)
# 標準流處理器
stream_handler = StreamHandler()
stream_handler.setLevel(logging.WARNING)
# 建立一個格式器
formatter = logging.Formatter('%(asctime)s - %(name)s - %(levelname)s - %(message)s')
# 作用在handler上
stream_handler.setFormatter(formatter)
# 添加處理器
logger.addHandler(stream_handler)
logger.info("this is info")
logger.error("this is error")
logger.warning("this is warning")


註意,格式器只能作用在處理器上,透過處理器的 setFromatter 方法設定格式器。而且一個Handler只能設定一個格式器。是一對一的關系。而 logger 與 handler 是一對多的關系,一個logger可以添加多個handler。handler 和 logger 都可以設定日誌的等級。

logging.basicConfig

回到最開始的地方,logging.basicConfig() 方法為我們幹了啥?現在你大概能猜出來了。來看python源碼中是怎麽說的

Do basic configuration for the logging system.

This function does nothing if the root logger already has handlers configured. It is a convenience method intended for use by simple scripts to do one-shot configuration of the logging package.

The default behaviour is to create a StreamHandler which writes to sys.stderr, set a formatter using the BASIC_FORMAT format string, and add the handler to the root logger.

A number of optional keyword arguments may be specified, which can alter the default behaviour.

1、建立一個root記錄器
2、設定root的日誌級別為warning
3、為root記錄器添加StreamHandler處理器
4、為處理器設定一個簡單格式器

logging.basicConfig()
logging.warning("hello")

這兩行程式碼其實就等價於:

import sys
import logging
from logging import StreamHandler
from logging import Formatter

logger = logging.getLogger("root")
logger.setLevel(logging.WARNING)
handler = StreamHandler(sys.stderr)
logger.addHandler(handler)
formatter = Formatter(" %(levelname)s:%(name)s:%(message)s")
handler.setFormatter(formatter)
logger.warning("hello")

logging.basicConfig 方法做的事情是相當於給日誌系統做一個最基本的配置,方便開發者快速接入使用。它必須在開始記錄日誌前呼叫。不過如果 root 記錄器已經指定有其它處理器,這時候你再呼叫basciConfig,則該方式將失效,它什麽都不做。

日誌配置

日誌的配置除了前面介紹的將配置直接寫在程式碼中,還可以將配置資訊單獨放在配置檔中,實作配置與代分碼離。

日誌配置檔 logging.conf

[loggers]
keys=root
[handlers]
keys=consoleHandler
[formatters]
keys=simpleFormatter
[logger_root]
level=DEBUG
handlers=consoleHandler

[handler_consoleHandler]
class=StreamHandler
level=DEBUG
formatter=simpleFormatter
args=(sys.stdout,)
[formatter_simpleFormatter]
format=%(asctime)s - %(name)s - %(levelname)s - %(message)s



載入配置檔

import logging
import logging.config
# 載入配置
logging.config.fileConfig('logging.conf')
# 建立 logger
logger = logging.getLogger()
# 套用程式碼
logger.debug("debug message")
logger.info("info message")
logger.warning("warning message")
logger.error("error message")

輸出

2021-12-23 00:02:07,019 - root - DEBUG - debug message
2021-12-23 00:02:07,019 - root - INFO - info message
2021-12-23 00:02:07,019 - root - WARNING - warning message
2021-12-23 00:02:07,019 - root - ERROR - error message

到這裏算是對logging的一次比較完整的介紹,當然,還有很多細節並沒有涉及到,因此我給了幾個連結供參考。

參考連結:

https://docs.python.org/3/library/logging.html#

https://docs.python.org/3/howto/logging.html#logging-advanced-tutorial

https://awaywithideas.com/python-logging-a-practical-guide/

https://rmcomplexity.com/article/2020/12/01/introduction-to-python-logging.html


作者: 誌軍100

來源 :Python之禪

Crossin的新書【 碼上行動:用ChatGPT學會Python編程 】已經上市了。 本書以ChatGPT為輔助,系統全面地講解了如何掌握Python編程,適合Python零基礎入門的讀者學習。

購買後可加入讀者交流群,Crossin為你開啟陪讀模式,解答你在閱讀本書時的一切疑問。

Crossin的其他書籍:

添加微信 crossin123 ,加入編程教室共同學習 ~

感謝 轉發 點贊 的各位~