使用Python自定義創(chuàng)建的Log日志模塊
日志的作用
作為一名開發(fā)人員,日志是我們排查問題的好幫手,在程序中設(shè)計(jì)一份好的日志,能夠讓我們快速定位到問題的原因。比如我們的產(chǎn)品在用戶手中出了問題,我們只需要查看該用戶日志,然后就能發(fā)現(xiàn)問題原因。毫無疑問。這會(huì)極大的節(jié)省我們排查問題的時(shí)間,提升了我們工作的效率。
日志分類
根據(jù)Python中l(wèi)ogging提供的日志函數(shù),它們分別是: debug()、 info()、 warning()、 error() 和 critical(),logging根據(jù)錯(cuò)誤對程序運(yùn)行的影響情況,可以大致分為四類(影響遞增):
- debug:所有詳細(xì)信息,用于調(diào)試。
- info:一些關(guān)鍵跳轉(zhuǎn),證明軟件正常運(yùn)行的日志。
- warning:表明發(fā)生了一些意外,軟件無法處理,但是依然能正常運(yùn)行。
- error:由于一些嚴(yán)重問題,軟件不能正常執(zhí)行一些功能,但是依然能運(yùn)行。
- critical/fatal:非常嚴(yán)重的錯(cuò)誤,軟件已經(jīng)不能繼續(xù)運(yùn)行了。
每個(gè)級別對應(yīng)的數(shù)字值為
CRITICAL:50,ERROR:40,WARNING:30,INFO:20,DEBUG:10,NOTSET:0。
Python 中日志的默認(rèn)等級是 WARNING,DEBUG 和 INFO 級別的日志將不會(huì)得到顯示,在 logging 中更改設(shè)置。
日志輸出
日志輸出的方式主要有兩種,一種使用 logging 在控制臺(tái)打印日志,另一種是將日志打印到文件中,方便日后的觀察
輸出到控制臺(tái)
使用 logging 在控制臺(tái)打印日志
import logging logging.debug('運(yùn)行正常') logging.warning('博主顏值要爆表了') logging.info('博主張的真帥,可惜你看不到') logging.error('程序運(yùn)行遇到錯(cuò)誤') logging.critical('嚴(yán)重錯(cuò)誤,無法運(yùn)行')
根據(jù)控制臺(tái)的打印情況,可以看出,日志設(shè)置在 WARNING 級別(那些數(shù)字低于這個(gè)級別的將不會(huì)展示)
那怎么去改變?nèi)罩镜募墑e呢?
很簡單,一句代碼就可以實(shí)現(xiàn)
import logging logging.basicConfig(level=logging.DEBUG) # 此處的級別要大寫 logging.debug('運(yùn)行正常') logging.warning('博主顏值要爆表了') logging.info('博主張的真帥,可惜你看不到') logging.error('程序運(yùn)行遇到錯(cuò)誤') logging.critical('嚴(yán)重錯(cuò)誤,無法運(yùn)行')
當(dāng)設(shè)置為最小的級別時(shí),那所有的日志信息就都展示出來了
將日志信息打印到文件中
將信息打印到文件中同樣需要調(diào)用 basicConfig ,現(xiàn)在我們修改上面的代碼,查看一下運(yùn)行情況。
import logging logging.basicConfig(level=logging.DEBUG, filename='Zhang.log', filemode='a') # 此處的級別要大寫 logging.debug('運(yùn)行正常') logging.warning('博主顏值要爆表了') logging.info('博主張的真帥,可惜你看不到') logging.error('程序運(yùn)行遇到錯(cuò)誤') logging.critical('嚴(yán)重錯(cuò)誤,無法運(yùn)行')
在 basicConfig 中添加配置信息 : filename(文件名稱)、filemode(寫入文件方式),OK ,查看一下文件中的內(nèi)容:
在運(yùn)行文件的同級目錄生成了 Zhang.log 日志文件 現(xiàn)在這個(gè)方法雖然將日志打印到文件中了,但是在控制臺(tái)中卻沒有了輸出信息,那怎么才能既在控制臺(tái)中顯示,又能寫入到日志中呢
強(qiáng)大的 logging
logging所提供的模塊級別的日志記錄函數(shù)是對logging日志系統(tǒng)相關(guān)類的封裝
logging 模塊提供了兩種記錄日志的方式:
使用logging提供的模塊級別的函數(shù)
使用Logging日志系統(tǒng)的四大組件
這里提到的級別函數(shù)就是上面所用的 DEBGE、ERROR 等級別,而四大組件則是指 loggers、handlers、filters 和 formatters 這幾個(gè)組件,下圖簡單明了的闡述了它們各自的作用:
日志器(logger)是入口,真正工作的是處理器(handler),處理器(handler)還可以通過過濾器(filter)和格式器(formatter)對要輸出的日志內(nèi)容做過濾和格式化等處理操作。
四大組件
下面介紹下與logging四大組件相關(guān)的類:Logger, Handler, Filter, Formatter。
Logger類
Logger 對象有3個(gè)工作要做:
1)向應(yīng)用程序代碼暴露幾個(gè)方法,使應(yīng)用程序可以在運(yùn)行時(shí)記錄日志消息;
2)基于日志嚴(yán)重等級(默認(rèn)的過濾設(shè)施)或filter對象來決定要對哪些日志進(jìn)行后續(xù)處理;
3)將日志消息傳送給所有感興趣的日志handlers。
Logger對象最常用的方法分為兩類:配置方法 和 消息發(fā)送方法 最常用的配置方法如下:
關(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)建日志記錄:
那么,怎樣得到一個(gè)Logger對象呢?一種方式是通過Logger類的實(shí)例化方法創(chuàng)建一個(gè)Logger類的實(shí)例,但是我們通常都是用第二種方式–logging.getLogger()方法。
logging.getLogger()方法有一個(gè)可選參數(shù)name,該參數(shù)表示將要返回的日志器的名稱標(biāo)識(shí),如果不提供該參數(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è)配置方法:
需要說明的是,應(yīng)用程序代碼不應(yīng)該直接實(shí)例化和使用Handler實(shí)例。因?yàn)镠andler是一個(gè)基類,它只定義了素有handlers都應(yīng)該有的接口,同時(shí)提供了一些子類可以直接使用或覆蓋的默認(rèn)行為。下面是一些常用的Handler:
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層級下的日志事件通過過濾。該類定義如下:
class logging.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ù)量等。
實(shí)戰(zhàn)演練
上面文縐縐的說了(復(fù)制/粘貼)那么多,現(xiàn)在應(yīng)該動(dòng)手實(shí)踐了。
現(xiàn)在我需要既將日志輸出到控制臺(tái)、又能將日志保存到文件,我應(yīng)該怎么辦?
利用剛才所學(xué)的知識(shí),我們可以構(gòu)思一下:
看起來好像也不難,挺簡單的樣子,但是實(shí)際如此嗎?
在實(shí)際的工作或應(yīng)用中,我們或許還需要指定文件存放路徑、用隨機(jī)數(shù)作為日志文件名、顯示具體的信息輸出代碼行數(shù)、日志信息輸出日期和日志寫入方式等內(nèi)容。再構(gòu)思一下:
具體代碼如下:
import os import logging import uuid from logging import Handler, FileHandler, StreamHandler class PathFileHandler(FileHandler): def __init__(self, path, filename, mode='a', encoding=None, delay=False): filename = os.fspath(filename) if not os.path.exists(path): os.mkdir(path) self.baseFilename = os.path.join(path, filename) self.mode = mode self.encoding = encoding self.delay = delay if delay: Handler.__init__(self) self.stream = None else: StreamHandler.__init__(self, self._open()) class Loggers(object): # 日志級別關(guān)系映射 level_relations = { 'debug': logging.DEBUG, 'info': logging.INFO, 'warning': logging.WARNING, 'error': logging.ERROR, 'critical': logging.CRITICAL } def __init__(self, filename='{uid}.log'.format(uid=uuid.uuid4()), level='info', log_dir='log', fmt='%(asctime)s - %(filename)s[line:%(lineno)d] - %(levelname)s: %(message)s'): self.logger = logging.getLogger(filename) abspath = os.path.dirname(os.path.abspath(__file__)) self.directory = os.path.join(abspath, log_dir) format_str = logging.Formatter(fmt) # 設(shè)置日志格式 self.logger.setLevel(self.level_relations.get(level)) # 設(shè)置日志級別 stream_handler = logging.StreamHandler() # 往屏幕上輸出 stream_handler.setFormatter(format_str) file_handler = PathFileHandler(path=self.directory, filename=filename, mode='a') file_handler.setFormatter(format_str) self.logger.addHandler(stream_handler) self.logger.addHandler(file_handler) if __name__ == "__main__": txt = "關(guān)注公眾號【進(jìn)擊的 Coder】,回復(fù)『日志代碼』可以領(lǐng)取文章中完整的代碼以及流程圖" log = Loggers(level='debug') log.logger.info(4) log.logger.info(5) log.logger.info(txt)
本人在使用代碼后,將日志模塊放入到程序中打包完成,但運(yùn)行后,在exe的同級目錄并沒有產(chǎn)生 log 文件夾,而自己在測的時(shí)候是可以的,另外,代碼中的 os.fspath 方法是在 Python 3.6 之后才有的 最后,根據(jù)本人程序的需要,我對原作者的代碼進(jìn)行了修改
import os import logging import time from logging import Handler, FileHandler, StreamHandler class PathFileHandler(FileHandler): def __init__(self, path, filename, mode='a', encoding=None, delay=False): if not os.path.exists(path): os.mkdir(path) self.baseFilename = os.path.join(path, filename) self.mode = mode self.encoding = encoding self.delay = delay if delay: Handler.__init__(self) self.stream = None else: StreamHandler.__init__(self, self._open()) class Loggers(object): # 日志級別關(guān)系映射 level_relations = { 'debug': logging.DEBUG, 'info': logging.INFO, 'warning': logging.WARNING, 'error': logging.ERROR, 'critical': logging.CRITICAL } def __init__(self, filename='{date}.log'.format(date = time.strftime("%Y-%m-%d_%H%M%S", time.localtime())), level='info', log_dir='log', fmt='%(asctime)s - %(filename)s[line:%(lineno)d] - %(levelname)s: %(message)s'): self.logger = logging.getLogger(filename) self.directory = os.path.join(os.getcwd(), log_dir) format_str = logging.Formatter(fmt) # 設(shè)置日志格式 self.logger.setLevel(self.level_relations.get(level)) # 設(shè)置日志級別 stream_handler = logging.StreamHandler() # 往屏幕上輸出 stream_handler.setFormatter(format_str) file_handler = PathFileHandler(path=self.directory, filename=filename, mode='a') file_handler.setFormatter(format_str) self.logger.addHandler(stream_handler) self.logger.addHandler(file_handler) if __name__ == "__main__": txt = "將信息打印到日志文件中......" log = Loggers(level='debug') log.logger.info(4) log.logger.info(5) log.logger.info(txt)
文件保存后運(yùn)行,運(yùn)行結(jié)果如下圖所示:
目錄內(nèi)生成指定的文件和文件夾,文件打開后可以看到里面的內(nèi)容:
這樣,以后在需要日志的時(shí)候,直接導(dǎo)入該日志模塊。而且,可以根據(jù)自己的需要對模塊進(jìn)行修改。最關(guān)鍵的是,原作者對該模塊設(shè)計(jì)時(shí)的思路。 如果該文章有什么問題,或者侵權(quán),請告知,我會(huì)進(jìn)行修改。
到此這篇關(guān)于使用Python自定義創(chuàng)建的Log日志模塊的文章就介紹到這了,更多相關(guān)Python自定義Log內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
pandas.DataFrame刪除/選取含有特定數(shù)值的行或列實(shí)例
今天小編就為大家分享一篇pandas.DataFrame刪除/選取含有特定數(shù)值的行或列實(shí)例,具有很好的參考價(jià)值,希望對大家有所幫助。一起跟隨小編過來看看吧2018-11-11Python實(shí)現(xiàn)如何根據(jù)文件后綴進(jìn)行分類
本文主要為大家詳細(xì)介紹了如何通過python實(shí)現(xiàn)根據(jù)文件后綴實(shí)現(xiàn)分類,文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以關(guān)注一下2021-12-12Django?url.py?path?name同一app下路由別名定義
這篇文章主要為大家介紹了Django?url.py?path?name同一app下路由別名定義詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2022-07-07python實(shí)現(xiàn)猜數(shù)字游戲(無重復(fù)數(shù)字)示例分享
這篇文章主要介紹了python實(shí)現(xiàn)猜數(shù)字游戲(無重復(fù)數(shù)字)示例,需要的朋友可以參考下2014-03-03Python實(shí)現(xiàn)采集網(wǎng)站ip代理并檢測是否可用
這篇文章主要介紹了如何利用Python爬蟲實(shí)現(xiàn)采集網(wǎng)站ip代理,并檢測IP代理是否可用。文中的示例代碼講解詳細(xì),感興趣的可以試一試2022-01-01用基于python的appium爬取b站直播消費(fèi)記錄
因工作需要,需要爬取相關(guān)數(shù)據(jù),之前是爬取網(wǎng)頁數(shù)據(jù),可以用python的requests和Selenium進(jìn)行爬取。但b站的直播消費(fèi)數(shù)據(jù)網(wǎng)頁版不能顯示,只能在手機(jī)上看到,所以就有了這篇文章。需要的朋友可以參考下2021-04-04