1. 程式人生 > 實用技巧 >python之 logging 模組(上篇)

python之 logging 模組(上篇)

一、日誌關概念

日誌是一種可以追蹤某些軟體執行時所發生事件的方法。軟體開發人員可以向他們的程式碼中呼叫日誌記錄相關的方法來表明發生了某些事情。一個事件可以用一個可包含可選變數資料的訊息來描述。此外,事件也有重要性的概念,這個重要性也可以被稱為嚴重性級別(level)。
日誌的作用
通過log的分析,可以方便使用者瞭解系統或軟體、應用的執行情況;如果你的應用log足夠豐富,也可以分析以往使用者的操作行為、型別喜好、地域分佈或其他更多資訊;如果一個應用的log同時也分了多個級別,那麼可以很輕易地分析得到該應用的健康狀況,及時發現問題並快速定位、解決問題,補救損失。

簡單來講就是,我們通過記錄和分析日誌可以瞭解一個系統或軟體程式執行情況是否正常,也可以在應用程式出現故障時快速定位問題。比如,做運維的同學,在接收到報警或各種問題反饋後,進行問題排查時通常都會先去看各種日誌,大部分問題都可以在日誌中找到答案。再比如,做開發的同學,可以通過IDE控制檯上輸出的各種日誌進行程式除錯。對於運維老司機或者有經驗的開發人員,可以快速的通過日誌定位到問題的根源。可見,日誌的重要性不可小覷。日誌的作用可以簡單總結為以下3點:
程式除錯

瞭解軟體程式執行情況,是否正常

軟體程式執行故障分析與問題定位

如果應用的日誌資訊足夠詳細和豐富,還可以用來做使用者行為分析,如:分析使用者的操作行為、型別洗好、地域分佈以及其它更多的資訊,由此可以實現改進業務、提高商業利益。
日誌的等級
我們先來思考下下面的兩個問題:

作為開發人員,在開發一個應用程式時需要什麼日誌資訊?在應用程式正式上線後需要什麼日誌資訊?

作為應用運維人員,在部署開發環境時需要什麼日誌資訊?在部署生產環境時需要什麼日誌資訊?

在軟體開發階段或部署開發環境時,為了儘可能詳細的檢視應用程式的執行狀態來保證上線後的穩定性,我們可能需要把該應用程式所有的執行日誌全部記錄下來進行分析,這是非常耗費機器效能的。當應用程式正式釋出或在生產環境部署應用程式時,我們通常只需要記錄應用程式的異常資訊、錯誤資訊等,這樣既可以減小伺服器的I/O壓力,也可以避免我們在排查故障時被淹沒在日誌的海洋裡。那麼,怎樣才能在不改動應用程式程式碼的情況下實現在不同的環境記錄不同詳細程度的日誌呢?這就是日誌等級的作用了,我們通過配置檔案指定我們需要的日誌等級就可以了。
不同的應用程式所定義的日誌等級可能會有所差別,分的詳細點的會包含以下幾個等級:

DEBUG

INFO

NOTICE

WARNING

ERROR

CRITICAL

ALERT

EMERGENCY

級別	何時使用
DEBUG	詳細資訊,典型地除錯問題時會感興趣。 詳細的debug資訊。
INFO	證明事情按預期工作。 關鍵事件。
WARNING	表明發生了一些意外,或者不久的將來會發生問題(如‘磁碟滿了’)。軟體還是在正常工作。
ERROR	由於更嚴重的問題,軟體已不能執行一些功能了。 一般錯誤訊息。
CRITICAL	嚴重錯誤,表明軟體已不能繼續運行了。
NOTICE	不是錯誤,但是可能需要處理。普通但是重要的事件。
ALERT	需要立即修復,例如系統資料庫損壞。
EMERGENCY	緊急情況,系統不可用(例如系統崩潰),一般會通知所有使用者。
3、日誌欄位資訊與日誌格式
一條日誌資訊對應的是一個事件的發生,而一個事件通常需要包括以下幾個內容:

事件發生時間

事件發生位置

事件的嚴重程度–日誌級別

事件內容

上面這些都是一條日誌記錄中可能包含的欄位資訊,當然還可以包括一些其他資訊,如程序ID、程序名稱、執行緒ID、執行緒名稱等。日誌格式就是用來定義一條日誌記錄中包含那些欄位的,且日誌格式通常都是可以自定義的。
4、日誌功能的實現
幾乎所有開發語言都會內建日誌相關功能,或者會有比較優秀的第三方庫來提供日誌操作功能,比如:log4j,log4php等。它們功能強大、使用簡單。Python自身也提供了一個用於記錄日誌的標準庫模組–logging。

二、 logging 模組介紹

什麼是logging模組
logging模組是python自帶的標準模組
logging模組的作用
主要用於輸出執行日誌

可以控制輸出日誌的等級, 過濾一些重要資訊, 不顯示大量無關要緊的除錯資訊

日誌儲存的路徑, 可以是輸出到終端, 也可以是輸出到檔案

以及檔案輪轉等等, 日誌檔案輪轉指的是設定儲存日誌檔案個數, 當超過最大日誌檔案個數, 最早的那個日誌檔案會被刪除
logging模組的優點:
logging模組是Python內建的標準模組,主要用於輸出執行日誌,可以設定輸出日誌的等級、日誌儲存路徑、日誌檔案回滾等;相比print,具備如下優點:

可以通過設定不同的日誌等級,在release版本中只輸出重要資訊,而不必顯示大量的除錯資訊;

print將所有資訊都輸出到標準輸出中,嚴重影響開發者從標準輸出中檢視其它資料;logging則可以由開發者決定將資訊輸出到什麼地方,以及怎麼輸出。
logging模組日誌的級別
logging模組預設定義了以下幾個日誌等級,它允許開發人員自定義其他日誌級別,但是這是不被推薦的,尤其是在開發供別人使用的庫時,因為這會導致日誌級別的混亂。

日誌等級(level)	描述
DEBUG	最詳細的日誌資訊,典型應用場景是 問題診斷
INFO	資訊詳細程度僅次於DEBUG,通常只記錄關鍵節點資訊,用於確認一切都是按照我們預期的那樣進行工作
WARNING	當某些不期望的事情發生時記錄的資訊(如,磁碟可用空間較低),但是此時應用程式還是正常執行的
ERROR	由於一個更嚴重的問題導致某些功能不能正常執行時記錄的資訊
CRITICAL	當發生嚴重錯誤,導致應用程式不能繼續執行時記錄的資訊
CRITICAL = 50 #FATAL = CRITICAL # 緊急
ERROR = 40  # 錯誤
WARNING = 30 #WARN = WARNING    # 警告
INFO = 20   # 訊息
DEBUG = 10  # 除錯
NOTSET = 0  # 不設定
開發應用程式或部署開發環境時,可以使用DEBUG或INFO級別的日誌獲取儘可能詳細的日誌資訊來進行開發或部署除錯;

應用上線或部署生產環境時,應該使用WARNING或ERROR或CRITICAL級別的日誌來降低機器的I/O壓力和提高獲取錯誤日誌資訊的效率。日誌級別的指定通常都是在應用程式的配置檔案中進行指定的。
說明:

上面列表中的日誌等級是從上到下依次升高的,即:DEBUG < INFO < WARNING < ERROR < CRITICAL,而日誌的資訊量是依次減少的;

當為某個應用程式指定一個日誌級別後,應用程式會記錄所有日誌級別大於或等於指定日誌級別的日誌資訊,而不是僅僅記錄指定級別的日誌資訊,nginx、php等應用程式以及這裡的python的logging模組都是這樣的。同樣,logging模組也可以指定日誌記錄器的日誌級別,只有級別大於或等於該指定日誌級別的日誌記錄才會被輸出,小於該等級的日誌記錄將會被丟棄。

三、logging 模組的使用方式介紹
logging使用方式介紹:
logging模組提供了兩種記錄日誌的方式:

第一種方式是使用logging提供的模組級別的函式

第二種方式是使用Logging日誌系統的四大元件

其實,logging所提供的模組級別的日誌記錄函式也是對logging日誌系統相關類的封裝而已。
示例:直接匯入logging模組
import logging
​
# 先進行日誌的基本配置
logging.basicConfig(
            # filename='access.log',          # 日誌名字 (不指定預設輸出到終端)
            format='%(asctime)s - %(name)s - %(levelname)s - %(module)s: %(message)s', # 日誌格式
            datefmt='%Y-%m-%d %H:%M:%S %p',   # 時間格式
            level=30,                         # 日誌等級
            )
​
# 進行日誌輸出
logging.debug('在大樓使用電子裝置')          # 10  除錯資訊
logging.info('大樓裡面使用打火機')           # 20  正常執行資訊
logging.warning('大樓裡抽菸')               # 30 警告  可能出錯
logging.error('正在大樓裡玩火')             # 40 出錯
logging.critical('拿著手榴彈在大樓裡溜達')   # 50 出錯長時間不管會崩潰
輸出結果
2020-12-11 19:50:30 PM - root - WARNING - test: 大樓裡抽菸
2020-12-11 19:50:30 PM - root - ERROR - test: 正在大樓裡玩火
2020-12-11 19:50:30 PM - root - CRITICAL - test: 拿著手榴彈在大樓裡溜達

# 通過日誌等級過濾掉了"debug"以及"info"的日誌資訊 (大於以及等於你設定的那個等級才會輸出)
#注意: 開啟檔案會發生亂碼問題,它的內部執行原理可以理解為就是使用with open()開啟檔案且預設沒有指定字元編碼, 預設使用的是作業系統的GBK字元編碼寫入硬碟, 這個時候我們用文字編輯器開啟"access.log"檔案我們要使用GBK編碼的方式來讀。
logging模組定義的模組級別的常用函式
函式	說明
logging.debug(msg, *args, **kwargs)	建立一條嚴重級別為DEBUG的日誌記錄
logging.info(msg, *args, **kwargs)	建立一條嚴重級別為INFO的日誌記錄
logging.warning(msg, *args, **kwargs)	建立一條嚴重級別為WARNING的日誌記錄
logging.error(msg, *args, **kwargs)	建立一條嚴重級別為ERROR的日誌記錄
logging.critical(msg, *args, **kwargs)	建立一條嚴重級別為CRITICAL的日誌記錄
logging.log(level, *args, **kwargs)	建立一條嚴重級別為level的日誌記錄
logging.basicConfig(**kwargs)	對root logger進行一次性配置
其中logging.basicConfig(**kwargs)函式用於指定“要記錄的日誌級別”、“日誌格式”、“日誌輸出位置”、“日誌檔案的開啟模式”等資訊,其他幾個都是用於記錄各個級別日誌的函式。
第一種使用方式:簡單配置
import logging
logging.debug("debug_msg")
logging.info("info_msg")
logging.warning("warning_msg")
logging.error("error_msg")
logging.critical("critical_msg")
​
'''輸出結果
WARNING:root:warning_msg
ERROR:root:error_msg
CRITICAL:root:critical_msg
預設情況下Python的logging模組將日誌列印到了標準輸出中,且只顯示了大於等於WARNING級別的日誌,這說明預設的日誌級別設定為WARNING(日誌級別等級CRITICAL > ERROR > WARNING > INFO > DEBUG)
預設輸出格式為
  預設的日誌格式為日誌級別:Logger名稱:使用者輸出訊息
預設日誌級別為warning,預設列印到終端
import logging
​
logging.debug('除錯debug')
logging.info('訊息info')
logging.warning('警告warn')
logging.error('錯誤error')
logging.critical('嚴重critical')
​

WARNING:root:警告warn
ERROR:root:錯誤error
CRITICAL:root:嚴重critical
這裡可以用 logging.basicConfig()函式調整日誌級別、輸出格式等
簡單示例:
import logging
logging.basicConfig(level=logging.DEBUG,
                    format="%(asctime)s %(name)s %(levelname)s %(message)s",
                    datefmt = '%Y-%m-%d  %H:%M:%S %a'    #注意月份和天數不要搞亂了,這裡的格式化符與time模組相同
                    )
logging.debug("msg1")
logging.info("msg2")
logging.warning("msg3")
logging.error("msg4")
logging.critical("msg5")
​
'''輸出內容
2020-12-13  23:24:21 Sun root DEBUG msg1
2020-12-13  23:24:21 Sun root INFO msg2
2020-12-13  23:24:21 Sun root WARNING msg3
2020-12-13  23:24:21 Sun root ERROR msg4
2020-12-13  23:24:21 Sun root CRITICAL msg5
logging.basicConfig()函式包含引數說明
引數名稱	描述
filename	指定日誌輸出目標檔案的檔名(可以寫檔名也可以寫檔案的完整的絕對路徑,寫檔名日誌放執行檔案目錄下,寫完整路徑按照完整路徑生成日誌檔案),指定該設定項後日志信心就不會被輸出到控制檯了
filemode	指定日誌檔案的開啟模式,預設為’a’。需要注意的是,該選項要在filename指定時才有效
format	指定日誌格式字串,即指定日誌輸出時所包含的欄位資訊以及它們的順序。logging模組定義的格式欄位下面會列出。
datefmt	指定日期/時間格式。需要注意的是,該選項要在format中包含時間欄位%(asctime)s時才有效
level	指定日誌器的日誌級別
stream	指定日誌輸出目標stream,如sys.stdout、sys.stderr以及網路stream。需要說明的是,stream和filename不能同時提供,否則會引發 ValueError異常
style	Python 3.2中新新增的配置項。指定format格式字串的風格,可取值為’%’、'{‘和’$’,預設為’%’
handlers	Python 3.3中新新增的配置項。該選項如果被指定,它應該是一個建立了多個Handler的可迭代物件,這些handler將會被新增到root logger。需要說明的是:filename、stream和handlers這三個配置項只能有一個存在,不能同時出現2個或3個,否則會引發ValueError異常。
logging模組中定義好的可以用於format格式字串說明
欄位/屬性名稱	使用格式	描述
asctime	%(asctime)s	將日誌的時間構造成可讀的形式,預設情況下是‘2016-02-08 12:00:00,123’精確到毫秒
name	%(name)s	所使用的日誌器名稱,預設是’root’,因為預設使用的是 rootLogger
filename	%(filename)s	呼叫日誌輸出函式的模組的檔名; pathname的檔名部分,包含檔案字尾
funcName	%(funcName)s	由哪個function發出的log, 呼叫日誌輸出函式的函式名
levelname	%(levelname)s	日誌的最終等級(被filter修改後的)
message	%(message)s	日誌資訊, 日誌記錄的文字內容
lineno	%(lineno)d	當前日誌的行號, 呼叫日誌輸出函式的語句所在的程式碼行
levelno	%(levelno)s	該日誌記錄的數字形式的日誌級別(10, 20, 30, 40, 50)
pathname	%(pathname)s	完整路徑 ,呼叫日誌輸出函式的模組的完整路徑名,可能沒有
process	%(process)s	當前程序, 程序ID。可能沒有
processName	%(processName)s	程序名稱,Python 3.1新增
thread	%(thread)s	當前執行緒, 執行緒ID。可能沒有
threadName	%(thread)s	執行緒名稱
module	%(module)s	呼叫日誌輸出函式的模組名, filename的名稱部分,不包含字尾即不包含檔案字尾的檔名
created	%(created)f	當前時間,用UNIX標準的表示時間的浮點數表示; 日誌事件發生的時間–時間戳,就是當時呼叫time.time()函式返回的值
relativeCreated	%(relativeCreated)d	輸出日誌資訊時的,自Logger建立以 來的毫秒數; 日誌事件發生的時間相對於logging模組載入時間的相對毫秒數
msecs	%(msecs)d	日誌事件發生事件的毫秒部分。logging.basicConfig()中用了引數datefmt,將會去掉asctime中產生的毫秒部分,可以用這個加上
升級版日誌示例:
import logging
LOG_FORMAT = "%(asctime)s %(name)s %(levelname)s %(pathname)s %(message)s "#配置輸出日誌格式
DATE_FORMAT = '%Y-%m-%d  %H:%M:%S %a ' #配置輸出時間的格式,注意月份和天數不要搞亂了
logging.basicConfig(level=logging.DEBUG,
                    format=LOG_FORMAT,
                    datefmt = DATE_FORMAT ,
                    filename=r"F:\python_16\day 11\test.log" #有了filename引數就不會直接輸出顯示到控制檯,而是直接寫入檔案
                    )
logging.debug("msg1")
logging.info("msg2")
logging.warning("msg3")
logging.error("msg4")
logging.critical("msg5")
​
'''輸出結果
​
2020-12-13  23:35:22 Sun  root DEBUG F:/python_16/day 11/stupid kid.py msg1 
2020-12-13  23:35:22 Sun  root INFO F:/python_16/day 11/stupid kid.py msg2 
2020-12-13  23:35:22 Sun  root WARNING F:/python_16/day 11/stupid kid.py msg3 
2020-12-13  23:35:22 Sun  root ERROR F:/python_16/day 11/stupid kid.py msg4 
2020-12-13  23:35:22 Sun  root CRITICAL F:/python_16/day 11/stupid kid.py msg5 
​
說明:

logging.basicConfig()函式是一個一次性的簡單配置工具使,也就是說只有在第一次呼叫該函式時會起作用,後續再次呼叫該函式時完全不會產生任何操作的,多次呼叫的設定並不是累加操作。

日誌器(Logger)是有層級關係的,上面呼叫的logging模組級別的函式所使用的日誌器是RootLogger類的例項,其名稱為’root’,它是處於日誌器層級關係最頂層的日誌器,且該例項是以單例模式存在的。

如果要記錄的日誌中包含變數資料,可使用一個格式字串作為這個事件的描述訊息(logging.debug、logging.info等函式的第一個引數),然後將變數資料作為第二個引數*args的值進行傳遞,

如:

logging.warning('%s is %d years old.', 'Tom', 10),
​
'''輸出內容
WARNING:root:Tom is 10 years old.
'''
logging.debug(), logging.info()等方法的定義中,除了msg和args引數外,還有一個**kwargs引數。它們支援3個關鍵字引數: exc_info, stack_info, extra,下面對這幾個關鍵字引數作個說明。關於exc_info, stack_info, extra關鍵詞引數的說明:見參考資料(瞭解)戳我