From:Python之日志處理(logging模塊) - 云游道士 - 博客園
https://www.cnblogs.com/xielisen/p/6817807.html
本節(jié)內(nèi)容
日志相關(guān)概念
logging模塊簡介
使用logging提供的模塊級別的函數(shù)記錄日志
logging模塊日志流處理流程
使用logging四大組件記錄日志
配置logging的幾種方式
向日志輸出中添加上下文信息
參考文檔
一馏艾、日志相關(guān)概念
日志是一種可以追蹤某些軟件運(yùn)行時(shí)所發(fā)生事件的方法勺良。軟件開發(fā)人員可以向他們的代碼中調(diào)用日志記錄相關(guān)的方法來表明發(fā)生了某些事情抽莱。一個(gè)事件可以用一個(gè)可包含可選變量數(shù)據(jù)的消息來描述扩所。此外,事件也有重要性的概念棱貌,這個(gè)重要性也可以被稱為嚴(yán)重性級別(level)冻河。
1.日志的作用
通過log的分析钝计,可以方便用戶了解系統(tǒng)或軟件、應(yīng)用的運(yùn)行情況瘫絮;如果你的應(yīng)用log足夠豐富涨冀,也可以分析以往用戶的操作行為、類型喜好檀何、地域分布或其他更多信息蝇裤;如果一個(gè)應(yīng)用的log同時(shí)也分了多個(gè)級別廷支,那么可以很輕易地分析得到該應(yīng)用的健康狀況,及時(shí)發(fā)現(xiàn)問題并快速定位栓辜、解決問題恋拍,補(bǔ)救損失。
簡單來講就是藕甩,我們通過記錄和分析日志可以了解一個(gè)系統(tǒng)或軟件程序運(yùn)行情況是否正常施敢,也可以在應(yīng)用程序出現(xiàn)故障時(shí)快速定位問題。比如狭莱,做運(yùn)維的同學(xué)僵娃,在接收到報(bào)警或各種問題反饋后,進(jìn)行問題排查時(shí)通常都會(huì)先去看各種日志腋妙,大部分問題都可以在日志中找到答案默怨。再比如,做開發(fā)的同學(xué)骤素,可以通過IDE控制臺(tái)上輸出的各種日志進(jìn)行程序調(diào)試匙睹。對于運(yùn)維老司機(jī)或者有經(jīng)驗(yàn)的開發(fā)人員,可以快速的通過日志定位到問題的根源济竹『勖剩可見,日志的重要性不可小覷送浊。日志的作用可以簡單總結(jié)為以下3點(diǎn):
程序調(diào)試
了解軟件程序運(yùn)行情況梦谜,是否正常
軟件程序運(yùn)行故障分析與問題定位
如果應(yīng)用的日志信息足夠詳細(xì)和豐富,還可以用來做用戶行為分析袭景,如:分析用戶的操作行為唁桩、類型洗好、地域分布以及其它更多的信息浴讯,由此可以實(shí)現(xiàn)改進(jìn)業(yè)務(wù)朵夏、提高商業(yè)利益。
2.日志的等級
我們先來思考下下面的兩個(gè)問題:
作為開發(fā)人員榆纽,在開發(fā)一個(gè)應(yīng)用程序時(shí)需要什么日志信息仰猖?在應(yīng)用程序正式上線后需要什么日志信息?
作為應(yīng)用運(yùn)維人員奈籽,在部署開發(fā)環(huán)境時(shí)需要什么日志信息饥侵?在部署生產(chǎn)環(huán)境時(shí)需要什么日志信息?
在軟件開發(fā)階段或部署開發(fā)環(huán)境時(shí)衣屏,為了盡可能詳細(xì)的查看應(yīng)用程序的運(yùn)行狀態(tài)來保證上線后的穩(wěn)定性躏升,我們可能需要把該應(yīng)用程序所有的運(yùn)行日志全部記錄下來進(jìn)行分析,這是非常耗費(fèi)機(jī)器性能的狼忱。當(dāng)應(yīng)用程序正式發(fā)布或在生產(chǎn)環(huán)境部署應(yīng)用程序時(shí)膨疏,我們通常只需要記錄應(yīng)用程序的異常信息一睁、錯(cuò)誤信息等,這樣既可以減小服務(wù)器的I/O壓力佃却,也可以避免我們在排查故障時(shí)被淹沒在日志的海洋里者吁。那么,怎樣才能在不改動(dòng)應(yīng)用程序代碼的情況下實(shí)現(xiàn)在不同的環(huán)境記錄不同詳細(xì)程度的日志呢饲帅?這就是日志等級的作用了复凳,我們通過配置文件指定我們需要的日志等級就可以了。
不同的應(yīng)用程序所定義的日志等級可能會(huì)有所差別灶泵,分的詳細(xì)點(diǎn)的會(huì)包含以下幾個(gè)等級:
DEBUG
INFO
NOTICE
WARNING
ERROR
CRITICAL
ALERT
EMERGENCY
3.日志字段信息與日志格式
本節(jié)開始問題提到過育八,一條日志信息對應(yīng)的是一個(gè)事件的發(fā)生,而一個(gè)事件通常需要包括以下幾個(gè)內(nèi)容:
事件發(fā)生時(shí)間
事件發(fā)生位置
事件的嚴(yán)重程度--日志級別
事件內(nèi)容
上面這些都是一條日志記錄中可能包含的字段信息赦邻,當(dāng)然還可以包括一些其他信息髓棋,如進(jìn)程ID、進(jìn)程名稱深纲、線程ID仲锄、線程名稱等劲妙。日志格式就是用來定義一條日志記錄中包含那些字段的湃鹊,且日志格式通常都是可以自定義的。
說明:
輸出一條日志時(shí)镣奋,日志內(nèi)容和日志級別是需要開發(fā)人員明確指定的币呵。對于而其它字段信息,只需要是否顯示在日志中就可以了侨颈。
4.日志功能的實(shí)現(xiàn)
幾乎所有開發(fā)語言都會(huì)內(nèi)置日志相關(guān)功能余赢,或者會(huì)有比較優(yōu)秀的第三方庫來提供日志操作功能,比如:log4j哈垢,log4php等妻柒。它們功能強(qiáng)大、使用簡單耘分。Python自身也提供了一個(gè)用于記錄日志的標(biāo)準(zhǔn)庫模塊--logging举塔。
二、logging模塊簡介
logging模塊定義的函數(shù)和類為應(yīng)用程序和庫的開發(fā)實(shí)現(xiàn)了一個(gè)靈活的事件日志系統(tǒng)求泰。logging模塊是Python的一個(gè)標(biāo)準(zhǔn)庫模塊央渣,由標(biāo)準(zhǔn)庫模塊提供日志記錄API的關(guān)鍵好處是所有Python模塊都可以使用這個(gè)日志記錄功能。所以渴频,你的應(yīng)用日志可以將你自己的日志信息與來自第三方模塊的信息整合起來芽丹。
1. logging模塊的日志級別
logging模塊默認(rèn)定義了以下幾個(gè)日志等級,它允許開發(fā)人員自定義其他日志級別卜朗,但是這是不被推薦的拔第,尤其是在開發(fā)供別人使用的庫時(shí)咕村,因?yàn)檫@會(huì)導(dǎo)致日志級別的混亂。
日志等級(level)描述
DEBUG最詳細(xì)的日志信息蚊俺,典型應(yīng)用場景是 問題診斷
INFO信息詳細(xì)程度僅次于DEBUG培廓,通常只記錄關(guān)鍵節(jié)點(diǎn)信息,用于確認(rèn)一切都是按照我們預(yù)期的那樣進(jìn)行工作
WARNING當(dāng)某些不期望的事情發(fā)生時(shí)記錄的信息(如春叫,磁盤可用空間較低)肩钠,但是此時(shí)應(yīng)用程序還是正常運(yùn)行的
ERROR由于一個(gè)更嚴(yán)重的問題導(dǎo)致某些功能不能正常運(yùn)行時(shí)記錄的信息
CRITICAL當(dāng)發(fā)生嚴(yán)重錯(cuò)誤,導(dǎo)致應(yīng)用程序不能繼續(xù)運(yùn)行時(shí)記錄的信息
開發(fā)應(yīng)用程序或部署開發(fā)環(huán)境時(shí)暂殖,可以使用DEBUG或INFO級別的日志獲取盡可能詳細(xì)的日志信息來進(jìn)行開發(fā)或部署調(diào)試价匠;應(yīng)用上線或部署生產(chǎn)環(huán)境時(shí),應(yīng)該使用WARNING或ERROR或CRITICAL級別的日志來降低機(jī)器的I/O壓力和提高獲取錯(cuò)誤日志信息的效率呛每。日志級別的指定通常都是在應(yīng)用程序的配置文件中進(jìn)行指定的踩窖。
說明:
上面列表中的日志等級是從上到下依次升高的,即:DEBUG < INFO < WARNING < ERROR < CRITICAL晨横,而日志的信息量是依次減少的洋腮;
當(dāng)為某個(gè)應(yīng)用程序指定一個(gè)日志級別后,應(yīng)用程序會(huì)記錄所有日志級別大于或等于指定日志級別的日志信息手形,而不是僅僅記錄指定級別的日志信息啥供,nginx、php等應(yīng)用程序以及這里要提高的python的logging模塊都是這樣的库糠。同樣伙狐,logging模塊也可以指定日志記錄器的日志級別,只有級別大于或等于該指定日志級別的日志記錄才會(huì)被輸出瞬欧,小于該等級的日志記錄將會(huì)被丟棄贷屎。
2. logging模塊的使用方式介紹
logging模塊提供了兩種記錄日志的方式:
第一種方式是使用logging提供的模塊級別的函數(shù)
第二種方式是使用Logging日志系統(tǒng)的四大組件
其實(shí),logging所提供的模塊級別的日志記錄函數(shù)也是對logging日志系統(tǒng)相關(guān)類的封裝而已艘虎。
logging模塊定義的模塊級別的常用函數(shù)
函數(shù)說明
logging.debug(msg, *args, **kwargs)創(chuàng)建一條嚴(yán)重級別為DEBUG的日志記錄
logging.info(msg, *args, **kwargs)創(chuàng)建一條嚴(yán)重級別為INFO的日志記錄
logging.warning(msg, *args, **kwargs)創(chuàng)建一條嚴(yán)重級別為WARNING的日志記錄
logging.error(msg, *args, **kwargs)創(chuàng)建一條嚴(yán)重級別為ERROR的日志記錄
logging.critical(msg, *args, **kwargs)創(chuàng)建一條嚴(yán)重級別為CRITICAL的日志記錄
logging.log(level, *args, **kwargs)創(chuàng)建一條嚴(yán)重級別為level的日志記錄
logging.basicConfig(**kwargs)對root logger進(jìn)行一次性配置
其中l(wèi)ogging.basicConfig(**kwargs)函數(shù)用于指定“要記錄的日志級別”唉侄、“日志格式”、“日志輸出位置”野建、“日志文件的打開模式”等信息属划,其他幾個(gè)都是用于記錄各個(gè)級別日志的函數(shù)。
logging模塊的四大組件
組件說明
loggers提供應(yīng)用程序代碼直接使用的接口
handlers用于將日志記錄發(fā)送到指定的目的位置
filters提供更細(xì)粒度的日志過濾功能贬墩,用于決定哪些日志記錄將會(huì)被輸出(其它的日志記錄將會(huì)被忽略)
formatters用于控制日志信息的最終輸出格式
說明:?logging模塊提供的模塊級別的那些函數(shù)實(shí)際上也是通過這幾個(gè)組件的相關(guān)實(shí)現(xiàn)類來記錄日志的榴嗅,只是在創(chuàng)建這些類的實(shí)例時(shí)設(shè)置了一些默認(rèn)值。
三陶舞、使用logging提供的模塊級別的函數(shù)記錄日志
回顧下前面提到的幾個(gè)重要信息:
可以通過logging模塊定義的模塊級別的方法去完成簡單的日志記錄
只有級別大于或等于日志記錄器指定級別的日志記錄才會(huì)被輸出嗽测,小于該級別的日志記錄將會(huì)被丟棄。
1.最簡單的日志輸出
先來試著分別輸出一條不同日志級別的日志記錄:
import logginglogging.debug("This is a debug log.")logging.info("This is a info log.")logging.warning("This is a warning log.")logging.error("This is a error log.")logging.critical("This is a critical log.")
也可以這樣寫:
logging.log(logging.DEBUG,"This is a debug log.")logging.log(logging.INFO,"This is a info log.")logging.log(logging.WARNING,"This is a warning log.")logging.log(logging.ERROR,"This is a error log.")logging.log(logging.CRITICAL,"This is a critical log.")
輸出結(jié)果:
WARNING:root:Thisisawarninglog.ERROR:root:Thisisaerrorlog.CRITICAL:root:Thisisacriticallog.
2. 那么問題來了
問題1:為什么前面兩條日志沒有被打印出來?
這是因?yàn)閘ogging模塊提供的日志記錄函數(shù)所使用的日志器設(shè)置的日志級別是WARNING唠粥,因此只有WARNING級別的日志記錄以及大于它的ERROR和CRITICAL級別的日志記錄被輸出了疏魏,而小于它的DEBUG和INFO級別的日志記錄被丟棄了。
問題2:打印出來的日志信息中各字段表示什么意思晤愧?為什么會(huì)這樣輸出大莫?
上面輸出結(jié)果中每行日志記錄的各個(gè)字段含義分別是:
日志級別:日志器名稱:日志內(nèi)容
之所以會(huì)這樣輸出,是因?yàn)閘ogging模塊提供的日志記錄函數(shù)所使用的日志器設(shè)置的日志格式默認(rèn)是BASIC_FORMAT官份,其值為:
"%(levelname)s:%(name)s:%(message)s"
問題3:如果將日志記錄輸出到文件中只厘,而不是打印到控制臺(tái)?
因?yàn)樵趌ogging模塊提供的日志記錄函數(shù)所使用的日志器設(shè)置的處理器所指定的日志輸出位置默認(rèn)為:
sys.stderr舅巷。
問題4:我是怎么知道這些的羔味?
查看這些日志記錄函數(shù)的實(shí)現(xiàn)代碼,可以發(fā)現(xiàn):當(dāng)我們沒有提供任何配置信息的時(shí)候钠右,這些函數(shù)都會(huì)去調(diào)用logging.basicConfig(**kwargs)方法赋元,且不會(huì)向該方法傳遞任何參數(shù)。繼續(xù)查看basicConfig()方法的代碼就可以找到上面這些問題的答案了飒房。
問題5:怎么修改這些默認(rèn)設(shè)置呢搁凸?
其實(shí)很簡單,在我們調(diào)用上面這些日志記錄函數(shù)之前狠毯,手動(dòng)調(diào)用一下basicConfig()方法护糖,把我們想設(shè)置的內(nèi)容以參數(shù)的形式傳遞進(jìn)去就可以了。
3. logging.basicConfig()函數(shù)說明
該方法用于為logging日志系統(tǒng)做一些基本配置垃你,方法定義如下:
logging.basicConfig(**kwargs)
該函數(shù)可接收的關(guān)鍵字參數(shù)如下:
參數(shù)名稱描述
filename指定日志輸出目標(biāo)文件的文件名椅文,指定該設(shè)置項(xiàng)后日志信心就不會(huì)被輸出到控制臺(tái)了
filemode指定日志文件的打開模式,默認(rèn)為'a'惜颇。需要注意的是,該選項(xiàng)要在filename指定時(shí)才有效
format指定日志格式字符串少辣,即指定日志輸出時(shí)所包含的字段信息以及它們的順序凌摄。logging模塊定義的格式字段下面會(huì)列出。
datefmt指定日期/時(shí)間格式漓帅。需要注意的是锨亏,該選項(xiàng)要在format中包含時(shí)間字段%(asctime)s時(shí)才有效
level指定日志器的日志級別
stream指定日志輸出目標(biāo)stream,如sys.stdout忙干、sys.stderr以及網(wǎng)絡(luò)stream器予。需要說明的是,stream和filename不能同時(shí)提供捐迫,否則會(huì)引發(fā)?ValueError異常
stylePython 3.2中新添加的配置項(xiàng)乾翔。指定format格式字符串的風(fēng)格,可取值為'%'、'{'和'$'反浓,默認(rèn)為'%'
handlersPython 3.3中新添加的配置項(xiàng)萌丈。該選項(xiàng)如果被指定,它應(yīng)該是一個(gè)創(chuàng)建了多個(gè)Handler的可迭代對象雷则,這些handler將會(huì)被添加到root logger辆雾。需要說明的是:filename、stream和handlers這三個(gè)配置項(xiàng)只能有一個(gè)存在月劈,不能同時(shí)出現(xiàn)2個(gè)或3個(gè)度迂,否則會(huì)引發(fā)ValueError異常。
4. logging模塊定義的格式字符串字段
我們來列舉一下logging模塊中定義好的可以用于format格式字符串中字段有哪些:
字段/屬性名稱使用格式描述
asctime%(asctime)s日志事件發(fā)生的時(shí)間--人類可讀時(shí)間猜揪,如:2003-07-08 16:49:45,896
created%(created)f日志事件發(fā)生的時(shí)間--時(shí)間戳英岭,就是當(dāng)時(shí)調(diào)用time.time()函數(shù)返回的值
relativeCreated%(relativeCreated)d日志事件發(fā)生的時(shí)間相對于logging模塊加載時(shí)間的相對毫秒數(shù)(目前還不知道干嘛用的)
msecs%(msecs)d日志事件發(fā)生事件的毫秒部分
levelname%(levelname)s該日志記錄的文字形式的日志級別('DEBUG', 'INFO', 'WARNING', 'ERROR', 'CRITICAL')
levelno%(levelno)s該日志記錄的數(shù)字形式的日志級別(10, 20, 30, 40, 50)
name%(name)s所使用的日志器名稱,默認(rèn)是'root'湿右,因?yàn)槟J(rèn)使用的是 rootLogger
message%(message)s日志記錄的文本內(nèi)容诅妹,通過?msg % args計(jì)算得到的
pathname%(pathname)s調(diào)用日志記錄函數(shù)的源碼文件的全路徑
filename%(filename)spathname的文件名部分,包含文件后綴
module%(module)sfilename的名稱部分毅人,不包含后綴
lineno%(lineno)d調(diào)用日志記錄函數(shù)的源代碼所在的行號
funcName%(funcName)s調(diào)用日志記錄函數(shù)的函數(shù)名
process%(process)d進(jìn)程ID
processName%(processName)s進(jìn)程名稱吭狡,Python 3.1新增
thread%(thread)d線程ID
threadName%(thread)s線程名稱
5.經(jīng)過配置的日志輸出
先簡單配置下日志器的日志級別
logging.basicConfig(level=logging.DEBUG)logging.debug("This is a debug log.")logging.info("This is a info log.")logging.warning("This is a warning log.")logging.error("This is a error log.")logging.critical("This is a critical log.")
輸出結(jié)果:
DEBUG:root:Thisisadebuglog.INFO:root:Thisisainfolog.WARNING:root:Thisisawarninglog.ERROR:root:Thisisaerrorlog.CRITICAL:root:Thisisacriticallog.
所有等級的日志信息都被輸出了,說明配置生效了丈莺。
在配置日志器日志級別的基礎(chǔ)上划煮,在配置下日志輸出目標(biāo)文件和日志格式
LOG_FORMAT="%(asctime)s - %(levelname)s - %(message)s"logging.basicConfig(filename='my.log', level=logging.DEBUG, format=LOG_FORMAT)logging.debug("This is a debug log.")logging.info("This is a info log.")logging.warning("This is a warning log.")logging.error("This is a error log.")logging.critical("This is a critical log.")
此時(shí)會(huì)發(fā)現(xiàn)控制臺(tái)中已經(jīng)沒有輸出日志內(nèi)容了,但是在python代碼文件的相同目錄下會(huì)生成一個(gè)名為'my.log'的日志文件缔俄,該文件中的內(nèi)容為:
2017-05-0814:29:53,783-DEBUG-Thisisadebuglog.2017-05-0814:29:53,784-INFO-Thisisainfolog.2017-05-0814:29:53,784-WARNING-Thisisawarninglog.2017-05-0814:29:53,784-ERROR-Thisisaerrorlog.2017-05-0814:29:53,784-CRITICAL-Thisisacriticallog.
在上面的基礎(chǔ)上弛秋,我們再來設(shè)置下日期/時(shí)間格式
LOG_FORMAT ="%(asctime)s - %(levelname)s - %(message)s"DATE_FORMAT ="%m/%d/%Y %H:%M:%S %p"logging.basicConfig(filename='my.log', level=logging.DEBUG,format=LOG_FORMAT, datefmt=DATE_FORMAT)logging.debug("This is a debug log.")logging.info("This is a info log.")logging.warning("This is a warning log.")logging.error("This is a error log.")logging.critical("This is a critical log.")
此時(shí)會(huì)在my.log日志文件中看到如下輸出內(nèi)容:
05/08/201714:29:04PM - DEBUG - This is a debuglog.05/08/201714:29:04PM - INFO - This is a infolog.05/08/201714:29:04PM - WARNING - This is a warninglog.05/08/201714:29:04PM -ERROR- This is aerrorlog.05/08/201714:29:04PM - CRITICAL - This is a criticallog.
掌握了上面的內(nèi)容之后,已經(jīng)能夠滿足我們平時(shí)開發(fā)中需要的日志記錄功能俐载。
6. 其他說明
幾個(gè)要說明的內(nèi)容:
logging.basicConfig()函數(shù)是一個(gè)一次性的簡單配置工具使蟹略,也就是說只有在第一次調(diào)用該函數(shù)時(shí)會(huì)起作用,后續(xù)再次調(diào)用該函數(shù)時(shí)完全不會(huì)產(chǎn)生任何操作的遏佣,多次調(diào)用的設(shè)置并不是累加操作挖炬。
日志器(Logger)是有層級關(guān)系的,上面調(diào)用的logging模塊級別的函數(shù)所使用的日志器是RootLogger類的實(shí)例状婶,其名稱為'root'意敛,它是處于日志器層級關(guān)系最頂層的日志器,且該實(shí)例是以單例模式存在的膛虫。
如果要記錄的日志中包含變量數(shù)據(jù)草姻,可使用一個(gè)格式字符串作為這個(gè)事件的描述消息(logging.debug、logging.info等函數(shù)的第一個(gè)參數(shù))稍刀,然后將變量數(shù)據(jù)作為第二個(gè)參數(shù)*args的值進(jìn)行傳遞撩独,如:logging.warning('%s is %d years old.', 'Tom', 10),輸出內(nèi)容為WARNING:root:Tom is 10 years old.
logging.debug(), logging.info()等方法的定義中,除了msg和args參數(shù)外跌榔,還有一個(gè)**kwargs參數(shù)异雁。它們支持3個(gè)關(guān)鍵字參數(shù):?exc_info, stack_info, extra,下面對這幾個(gè)關(guān)鍵字參數(shù)作個(gè)說明僧须。
關(guān)于exc_info, stack_info, extra關(guān)鍵詞參數(shù)的說明:
exc_info:?其值為布爾值纲刀,如果該參數(shù)的值設(shè)置為True,則會(huì)將異常異常信息添加到日志消息中担平。如果沒有異常信息則添加None到日志信息中示绊。
stack_info:?其值也為布爾值,默認(rèn)值為False暂论。如果該參數(shù)的值設(shè)置為True面褐,棧信息將會(huì)被添加到日志信息中。
extra:?這是一個(gè)字典(dict)參數(shù)取胎,它可以用來自定義消息格式中所包含的字段展哭,但是它的key不能與logging模塊定義的字段沖突。
一個(gè)例子:
在日志消息中添加exc_info和stack_info信息闻蛀,并添加兩個(gè)自定義的字端 ip和user
LOG_FORMAT ="%(asctime)s - %(levelname)s - %(user)s[%(ip)s] - %(message)s"DATE_FORMAT ="%m/%d/%Y %H:%M:%S %p"logging.basicConfig(format=LOG_FORMAT, datefmt=DATE_FORMAT)logging.warning("Some one delete the log file.", exc_info=True, stack_info=True, extra={'user':'Tom','ip':'47.98.53.222'})
輸出結(jié)果:
05/08/2017 16:35:00 PM - WARNING - Tom[47.98.53.222] - Some onedeletethelogfile.NoneTypeStack (most recentcalllast):File"C:/Users/wader/PycharmProjects/LearnPython/day06/log.py", line45,inlogging.warning("Some one delete the log file.", exc_info=True, stack_info=True, extra={'user':'Tom','ip':'47.98.53.222'})
四匪傍、logging模塊日志流處理流程
在介紹logging模塊的高級用法之前,很有必要對logging模塊所包含的重要組件以及其工作流程做個(gè)全面觉痛、簡要的介紹役衡,這有助于我們更好的理解我們所寫的代碼(將會(huì)觸發(fā)什么樣的操作)。
1. logging日志模塊四大組件
在介紹logging模塊的日志流處理流程之前薪棒,我們先來介紹下logging模塊的四大組件:
組件名稱對應(yīng)類名功能描述
日志器Logger提供了應(yīng)用程序可一直使用的接口
處理器Handler將logger創(chuàng)建的日志記錄發(fā)送到合適的目的輸出
過濾器Filter提供了更細(xì)粒度的控制工具來決定輸出哪條日志記錄手蝎,丟棄哪條日志記錄
格式器Formatter決定日志記錄的最終輸出格式
logging模塊就是通過這些組件來完成日志處理的,上面所使用的logging模塊級別的函數(shù)也是通過這些組件對應(yīng)的類來實(shí)現(xiàn)的俐芯。
這些組件之間的關(guān)系描述:
日志器(logger)需要通過處理器(handler)將日志信息輸出到目標(biāo)位置棵介,如:文件、sys.stdout泼各、網(wǎng)絡(luò)等鞍时;
不同的處理器(handler)可以將日志輸出到不同的位置;
日志器(logger)可以設(shè)置多個(gè)處理器(handler)將同一條日志記錄輸出到不同的位置扣蜻;
每個(gè)處理器(handler)都可以設(shè)置自己的過濾器(filter)實(shí)現(xiàn)日志過濾,從而只保留感興趣的日志及塘;
每個(gè)處理器(handler)都可以設(shè)置自己的格式器(formatter)實(shí)現(xiàn)同一條日志以不同的格式輸出到不同的地方莽使。
簡單點(diǎn)說就是:日志器(logger)是入口搪柑,真正干活兒的是處理器(handler)勾笆,處理器(handler)還可以通過過濾器(filter)和格式器(formatter)對要輸出的日志內(nèi)容做過濾和格式化等處理操作。
2. logging日志模塊相關(guān)類及其常用方法介紹
下面介紹下與logging四大組件相關(guān)的類:Logger, Handler, Filter, Formatter荆残。
Logger類
Logger對象有3個(gè)任務(wù)要做:
1)向應(yīng)用程序代碼暴露幾個(gè)方法,使應(yīng)用程序可以在運(yùn)行時(shí)記錄日志消息亿笤;
2)基于日志嚴(yán)重等級(默認(rèn)的過濾設(shè)施)或filter對象來決定要對哪些日志進(jìn)行后續(xù)處理翎迁;
3)將日志消息傳送給所有感興趣的日志handlers。
Logger對象最常用的方法分為兩類:配置方法 和 消息發(fā)送方法
最常用的配置方法如下:
方法描述
Logger.setLevel()設(shè)置日志器將會(huì)處理的日志消息的最低嚴(yán)重級別
Logger.addHandler() 和 Logger.removeHandler()為該logger對象添加 和 移除一個(gè)handler對象
Logger.addFilter() 和 Logger.removeFilter()為該logger對象添加 和 移除一個(gè)filter對象
關(guān)于Logger.setLevel()方法的說明:
內(nèi)建等級中净薛,級別最低的是DEBUG汪榔,級別最高的是CRITICAL。例如setLevel(logging.INFO)肃拜,此時(shí)函數(shù)參數(shù)為INFO痴腌,那么該logger將只會(huì)處理INFO、WARNING燃领、ERROR和CRITICAL級別的日志士聪,而DEBUG級別的消息將會(huì)被忽略/丟棄。
logger對象配置完成后猛蔽,可以使用下面的方法來創(chuàng)建日志記錄:
方法描述
Logger.debug(), Logger.info(), Logger.warning(), Logger.error(), Logger.critical()創(chuàng)建一個(gè)與它們的方法名對應(yīng)等級的日志記錄
Logger.exception()創(chuàng)建一個(gè)類似于Logger.error()的日志消息
Logger.log()需要獲取一個(gè)明確的日志level參數(shù)來創(chuàng)建一個(gè)日志記錄
說明:
Logger.exception()與Logger.error()的區(qū)別在于:Logger.exception()將會(huì)輸出堆棧追蹤信息剥悟,另外通常只是在一個(gè)exception handler中調(diào)用該方法。
Logger.log()與Logger.debug()曼库、Logger.info()等方法相比区岗,雖然需要多傳一個(gè)level參數(shù),顯得不是那么方便凉泄,但是當(dāng)需要記錄自定義level的日志時(shí)還是需要該方法來完成躏尉。
那么,怎樣得到一個(gè)Logger對象呢后众?一種方式是通過Logger類的實(shí)例化方法創(chuàng)建一個(gè)Logger類的實(shí)例胀糜,但是我們通常都是用第二種方式--logging.getLogger()方法。
logging.getLogger()方法有一個(gè)可選參數(shù)name蒂誉,該參數(shù)表示將要返回的日志器的名稱標(biāo)識教藻,如果不提供該參數(shù),則其值為'root'右锨。若以相同的name參數(shù)值多次調(diào)用getLogger()方法括堤,將會(huì)返回指向同一個(gè)logger對象的引用。
關(guān)于logger的層級結(jié)構(gòu)與有效等級的說明:
logger的名稱是一個(gè)以'.'分割的層級結(jié)構(gòu)绍移,每個(gè)'.'后面的logger都是'.'前面的logger的children悄窃,例如,有一個(gè)名稱為 foo 的logger蹂窖,其它名稱分別為 foo.bar, foo.bar.baz 和 foo.bam都是 foo 的后代轧抗。
logger有一個(gè)"有效等級(effective level)"的概念。如果一個(gè)logger上沒有被明確設(shè)置一個(gè)level瞬测,那么該logger就是使用它parent的level;如果它的parent也沒有明確設(shè)置level則繼續(xù)向上查找parent的parent的有效level横媚,依次類推纠炮,直到找到個(gè)一個(gè)明確設(shè)置了level的祖先為止。需要說明的是灯蝴,root logger總是會(huì)有一個(gè)明確的level設(shè)置(默認(rèn)為 WARNING)恢口。當(dāng)決定是否去處理一個(gè)已發(fā)生的事件時(shí),logger的有效等級將會(huì)被用來決定是否將該事件傳遞給該logger的handlers進(jìn)行處理穷躁。
child loggers在完成對日志消息的處理后耕肩,默認(rèn)會(huì)將日志消息傳遞給與它們的祖先loggers相關(guān)的handlers。因此折砸,我們不必為一個(gè)應(yīng)用程序中所使用的所有l(wèi)oggers定義和配置handlers看疗,只需要為一個(gè)頂層的logger配置handlers,然后按照需要?jiǎng)?chuàng)建child loggers就可足夠了睦授。我們也可以通過將一個(gè)logger的propagate屬性設(shè)置為False來關(guān)閉這種傳遞機(jī)制两芳。
Handler類
Handler對象的作用是(基于日志消息的level)將消息分發(fā)到handler指定的位置(文件、網(wǎng)絡(luò)去枷、郵件等)怖辆。Logger對象可以通過addHandler()方法為自己添加0個(gè)或者更多個(gè)handler對象。比如删顶,一個(gè)應(yīng)用程序可能想要實(shí)現(xiàn)以下幾個(gè)日志需求:
1)把所有日志都發(fā)送到一個(gè)日志文件中竖螃;
2)把所有嚴(yán)重級別大于等于error的日志發(fā)送到stdout(標(biāo)準(zhǔn)輸出);
3)把所有嚴(yán)重級別為critical的日志發(fā)送到一個(gè)email郵件地址逗余。
這種場景就需要3個(gè)不同的handlers特咆,每個(gè)handler復(fù)雜發(fā)送一個(gè)特定嚴(yán)重級別的日志到一個(gè)特定的位置。
一個(gè)handler中只有非常少數(shù)的方法是需要應(yīng)用開發(fā)人員去關(guān)心的录粱。對于使用內(nèi)建handler對象的應(yīng)用開發(fā)人員來說腻格,似乎唯一相關(guān)的handler方法就是下面這幾個(gè)配置方法:
方法描述
Handler.setLevel()設(shè)置handler將會(huì)處理的日志消息的最低嚴(yán)重級別
Handler.setFormatter()為handler設(shè)置一個(gè)格式器對象
Handler.addFilter() 和 Handler.removeFilter()為handler添加 和 刪除一個(gè)過濾器對象
需要說明的是,應(yīng)用程序代碼不應(yīng)該直接實(shí)例化和使用Handler實(shí)例啥繁。因?yàn)镠andler是一個(gè)基類菜职,它只定義了素有handlers都應(yīng)該有的接口,同時(shí)提供了一些子類可以直接使用或覆蓋的默認(rèn)行為旗闽。下面是一些常用的Handler:
Handler描述
logging.StreamHandler將日志消息發(fā)送到輸出到Stream酬核,如std.out, std.err或任何file-like對象。
logging.FileHandler將日志消息發(fā)送到磁盤文件适室,默認(rèn)情況下文件大小會(huì)無限增長
logging.handlers.RotatingFileHandler將日志消息發(fā)送到磁盤文件嫡意,并支持日志文件按大小切割
logging.hanlders.TimedRotatingFileHandler將日志消息發(fā)送到磁盤文件,并支持日志文件按時(shí)間切割
logging.handlers.HTTPHandler將日志消息以GET或POST的方式發(fā)送給一個(gè)HTTP服務(wù)器
logging.handlers.SMTPHandler將日志消息發(fā)送給一個(gè)指定的email地址
logging.NullHandler該Handler實(shí)例會(huì)忽略error messages捣辆,通常被想使用logging的library開發(fā)者使用來避免'No handlers could be found for logger XXX'信息的出現(xiàn)鹅很。
Formater類
Formater對象用于配置日志信息的最終順序、結(jié)構(gòu)和內(nèi)容罪帖。與logging.Handler基類不同的是促煮,應(yīng)用代碼可以直接實(shí)例化Formatter類。另外整袁,如果你的應(yīng)用程序需要一些特殊的處理行為菠齿,也可以實(shí)現(xiàn)一個(gè)Formatter的子類來完成。
Formatter類的構(gòu)造方法定義如下:
logging.Formatter.__init__(fmt=None, datefmt=None, style='%')
可見坐昙,該構(gòu)造方法接收3個(gè)可選參數(shù):
fmt:指定消息格式化字符串绳匀,如果不指定該參數(shù)則默認(rèn)使用message的原始值
datefmt:指定日期格式字符串,如果不指定該參數(shù)則默認(rèn)使用"%Y-%m-%d %H:%M:%S"
style:Python 3.2新增的參數(shù)炸客,可取值為 '%', '{'和 '$'疾棵,如果不指定該參數(shù)則默認(rèn)使用'%'
Filter類
Filter可以被Handler和Logger用來做比level更細(xì)粒度的、更復(fù)雜的過濾功能痹仙。Filter是一個(gè)過濾器基類是尔,它只允許某個(gè)logger層級下的日志事件通過過濾。該類定義如下:
classlogging.Filter(name='')? ? filter(record)
比如开仰,一個(gè)filter實(shí)例化時(shí)傳遞的name參數(shù)值為'A.B'拟枚,那么該filter實(shí)例將只允許名稱為類似如下規(guī)則的loggers產(chǎn)生的日志記錄通過過濾:'A.B','A.B,C'众弓,'A.B.C.D'恩溅,'A.B.D',而名稱為'A.BB', 'B.A.B'的loggers產(chǎn)生的日志則會(huì)被過濾掉谓娃。如果name的值為空字符串脚乡,則允許所有的日志事件通過過濾。
filter方法用于具體控制傳遞的record記錄是否能通過過濾滨达,如果該方法返回值為0表示不能通過過濾奶稠,返回值為非0表示可以通過過濾。
說明:
如果有需要弦悉,也可以在filter(record)方法內(nèi)部改變該record窒典,比如添加、刪除或修改一些屬性稽莉。
我們還可以通過filter做一些統(tǒng)計(jì)工作瀑志,比如可以計(jì)算下被一個(gè)特殊的logger或handler所處理的record數(shù)量等。
3. logging日志流處理流程
下面這個(gè)圖描述了日志流的處理流程:
我們來描述下上面這個(gè)圖的日志流處理流程:
1)(在用戶代碼中進(jìn)行)日志記錄函數(shù)調(diào)用污秆,如:logger.info(...)劈猪,logger.debug(...)等;
2)判斷要記錄的日志級別是否滿足日志器設(shè)置的級別要求(要記錄的日志級別要大于或等于日志器設(shè)置的級別才算滿足要求)良拼,如果不滿足則該日志記錄會(huì)被丟棄并終止后續(xù)的操作战得,如果滿足則繼續(xù)下一步操作;
3)根據(jù)日志記錄函數(shù)調(diào)用時(shí)摻入的參數(shù)庸推,創(chuàng)建一個(gè)日志記錄(LogRecord類)對象常侦;
4)判斷日志記錄器上設(shè)置的過濾器是否拒絕這條日志記錄浇冰,如果日志記錄器上的某個(gè)過濾器拒絕,則該日志記錄會(huì)被丟棄并終止后續(xù)的操作聋亡,如果日志記錄器上設(shè)置的過濾器不拒絕這條日志記錄或者日志記錄器上沒有設(shè)置過濾器則繼續(xù)下一步操作--將日志記錄分別交給該日志器上添加的各個(gè)處理器肘习;
5)判斷要記錄的日志級別是否滿足處理器設(shè)置的級別要求(要記錄的日志級別要大于或等于該處理器設(shè)置的日志級別才算滿足要求),如果不滿足記錄將會(huì)被該處理器丟棄并終止后續(xù)的操作坡倔,如果滿足則繼續(xù)下一步操作漂佩;
6)判斷該處理器上設(shè)置的過濾器是否拒絕這條日志記錄,如果該處理器上的某個(gè)過濾器拒絕罪塔,則該日志記錄會(huì)被當(dāng)前處理器丟棄并終止后續(xù)的操作投蝉,如果當(dāng)前處理器上設(shè)置的過濾器不拒絕這條日志記錄或當(dāng)前處理器上沒有設(shè)置過濾器測繼續(xù)下一步操作;
7)如果能到這一步征堪,說明這條日志記錄經(jīng)過了層層關(guān)卡允許被輸出了瘩缆,此時(shí)當(dāng)前處理器會(huì)根據(jù)自身被設(shè)置的格式器(如果沒有設(shè)置則使用默認(rèn)格式)將這條日志記錄進(jìn)行格式化,最后將格式化后的結(jié)果輸出到指定位置(文件请契、網(wǎng)絡(luò)咳榜、類文件的Stream等);
8)如果日志器被設(shè)置了多個(gè)處理器的話爽锥,上面的第5-8步會(huì)執(zhí)行多次涌韩;
9)這里才是完整流程的最后一步:判斷該日志器輸出的日志消息是否需要傳遞給上一級logger(之前提到過,日志器是有層級關(guān)系的)的處理器氯夷,如果propagate屬性值為1則表示日志消息將會(huì)被輸出到處理器指定的位置臣樱,同時(shí)還會(huì)被傳遞給parent日志器的handlers進(jìn)行處理直到當(dāng)前日志器的propagate屬性為0停止,如果propagate值為0則表示不向parent日志器的handlers傳遞該消息腮考,到此結(jié)束雇毫。
可見,一條日志信息要想被最終輸出需要依次經(jīng)過以下幾次過濾:
日志器等級過濾踩蔚;
日志器的過濾器過濾棚放;
日志器的處理器等級過濾;
日志器的處理器的過濾器過濾馅闽;
需要說明的是:?關(guān)于上面第9個(gè)步驟飘蚯,如果propagate值為1,那么日志消息會(huì)直接傳遞交給上一級logger的handlers進(jìn)行處理福也,此時(shí)上一級logger的日志等級并不會(huì)對該日志消息進(jìn)行等級過濾局骤。
五、使用logging四大組件記錄日志
現(xiàn)在暴凑,我們對logging模塊的重要組件及整個(gè)日志流處理流程都應(yīng)該有了一個(gè)比較全面的了解峦甩,下面我們來看一個(gè)例子。
1. 需求
現(xiàn)在有以下幾個(gè)日志記錄的需求:
1)要求將所有級別的所有日志都寫入磁盤文件中
2)all.log文件中記錄所有的日志信息现喳,日志格式為:日期和時(shí)間 - 日志級別 - 日志信息
3)error.log文件中單獨(dú)記錄error及以上級別的日志信息凯傲,日志格式為:日期和時(shí)間 - 日志級別 - 文件名[:行號] - 日志信息
4)要求all.log在每天凌晨進(jìn)行日志切割
2. 分析
1)要記錄所有級別的日志犬辰,因此日志器的有效level需要設(shè)置為最低級別--DEBUG;
2)日志需要被發(fā)送到兩個(gè)不同的目的地,因此需要為日志器設(shè)置兩個(gè)handler泣洞;另外忧风,兩個(gè)目的地都是磁盤文件,因此這兩個(gè)handler都是與FileHandler相關(guān)的球凰;
3)all.log要求按照時(shí)間進(jìn)行日志切割,因此他需要用logging.handlers.TimedRotatingFileHandler; 而error.log沒有要求日志切割腿宰,因此可以使用FileHandler;
4)兩個(gè)日志文件的格式不同呕诉,因此需要對這兩個(gè)handler分別設(shè)置格式器;
3. 代碼實(shí)現(xiàn)
importloggingimport logging.handlersimport datetimelogger = logging.getLogger('mylogger')logger.setLevel(logging.DEBUG)rf_handler = logging.handlers.TimedRotatingFileHandler('all.log', when='midnight', interval=1, backupCount=7, atTime=datetime.time(0,0,0,0))rf_handler.setFormatter(logging.Formatter("%(asctime)s - %(levelname)s - %(message)s"))f_handler = logging.FileHandler('error.log')f_handler.setLevel(logging.ERROR)f_handler.setFormatter(logging.Formatter("%(asctime)s - %(levelname)s - %(filename)s[:%(lineno)d] - %(message)s"))logger.addHandler(rf_handler)logger.addHandler(f_handler)logger.debug('debug message')logger.info('info message')logger.warning('warning message')logger.error('error message')logger.critical('critical message')
all.log文件輸出
2017-05-1316:12:40,612-DEBUG-debugmessage2017-05-1316:12:40,612-INFO-infomessage2017-05-1316:12:40,612-WARNING-warningmessage2017-05-1316:12:40,612-ERROR-errormessage2017-05-1316:12:40,613-CRITICAL-criticalmessage
error.log文件輸出
2017-05-1316:12:40,612-ERROR-log.py[:81]-errormessage2017-05-1316:12:40,613-CRITICAL-log.py[:82]-criticalmessage
六吃度、配置logging的幾種方式
作為開發(fā)者甩挫,我們可以通過以下3中方式來配置logging:
1)使用Python代碼顯式的創(chuàng)建loggers, handlers和formatters并分別調(diào)用它們的配置函數(shù);
2)創(chuàng)建一個(gè)日志配置文件椿每,然后使用fileConfig()函數(shù)來讀取該文件的內(nèi)容伊者;
3)創(chuàng)建一個(gè)包含配置信息的dict,然后把它傳遞個(gè)dictConfig()函數(shù)间护;
具體說明請參考另一篇博文《python之配置日志的幾種方式》
七亦渗、向日志輸出中添加上下文信息
除了傳遞給日志記錄函數(shù)的參數(shù)外,有時(shí)候我們還想在日志輸出中包含一些額外的上下文信息汁尺。比如法精,在一個(gè)網(wǎng)絡(luò)應(yīng)用中,可能希望在日志中記錄客戶端的特定信息痴突,如:遠(yuǎn)程客戶端的IP地址和用戶名搂蜓。這里我們來介紹以下幾種實(shí)現(xiàn)方式:
通過向日志記錄函數(shù)傳遞一個(gè)extra參數(shù)引入上下文信息
使用LoggerAdapters引入上下文信息
使用Filters引入上下文信息
具體說明請參考另一篇博文《Python之向日志輸出中添加上下文信息》
關(guān)于Python logging的更多高級用法,請參考文檔<< Logging CookBook >>辽装。
八帮碰、參考文檔
https://docs.python.org/3.5/howto/logging.html