日誌是一種能夠追蹤某些軟件運行時所發生事件的方法。軟件開發人員能夠向他們的代碼中調用日誌記錄相關的方法來代表發生了某些事情。一個事件能夠用一個可包含可選變量數據的消息來描述。此外,事件也有重要性的概念,這個重要性也能夠被稱爲嚴重性級別(level)。php
經過log的分析,能夠方便用戶瞭解系統或軟件、應用的運行狀況;若是你的應用log足夠豐富,也能夠分析以往用戶的操做行爲、類型喜愛、地域分佈或其餘更多信息;若是一個應用的log同時也分了多個級別,那麼能夠很輕易地分析獲得該應用的健康情況,及時發現問題並快速定位、解決問題,補救損失。html
簡單來說就是,咱們經過記錄和分析日誌能夠了解一個系統或軟件程序運行狀況是否正常,也能夠在應用程序出現故障時快速定位問題。好比,作運維的同窗,在接收到報警或各類問題反饋後,進行問題排查時一般都會先去看各類日誌,大部分問題均可以在日誌中找到答案。再好比,作開發的同窗,能夠經過IDE控制檯上輸出的各類日誌進行程序調試。對於運維老司機或者有經驗的開發人員,能夠快速的經過日誌定位到問題的根源。可見,日誌的重要性不可小覷。日誌的做用能夠簡單總結爲如下3點:python
程序調試nginx
瞭解軟件程序運行狀況,是否正常數據庫
軟件程序運行故障分析與問題定位服務器
若是應用的日誌信息足夠詳細和豐富,還能夠用來作用戶行爲分析,如:分析用戶的操做行爲、類型洗好、地域分佈以及其它更多的信息,由此能夠實現改進業務、提升商業利益。markdown
咱們先來思考下下面的兩個問題:網絡
做爲開發人員,在開發一個應用程序時須要什麼日誌信息?在應用程序正式上線後須要什麼日誌信息?運維
做爲應用運維人員,在部署開發環境時須要什麼日誌信息?在部署生產環境時須要什麼日誌信息?函數
在軟件開發階段或部署開發環境時,爲了儘量詳細的查看應用程序的運行狀態來保證上線後的穩定性,咱們可能須要把該應用程序全部的運行日誌所有記錄下來進行分析,這是很是耗費機器性能的。當應用程序正式發佈或在生產環境部署應用程序時,咱們一般只須要記錄應用程序的異常信息、錯誤信息等,這樣既能夠減少服務器的I/O壓力,也能夠避免咱們在排查故障時被淹沒在日誌的海洋裏。那麼,怎樣才能在不改動應用程序代碼的狀況下實如今不一樣的環境記錄不一樣詳細程度的日誌呢?這就是日誌等級的做用了,咱們經過配置文件指定咱們須要的日誌等級就能夠了。
不一樣的應用程序所定義的日誌等級可能會有所差異,分的詳細點的會包含如下幾個等級:
DEBUG
INFO
NOTICE
WARNING
ERROR
CRITICAL
ALERT
EMERGENCY
級別 | 什麼時候使用 |
---|---|
DEBUG | 詳細信息,典型地調試問題時會感興趣。 詳細的debug信息。 |
INFO | 證實事情按預期工做。 關鍵事件。 |
WARNING | 代表發生了一些意外,或者不久的未來會發生問題(如‘磁盤滿了’)。軟件仍是在正常工做。 |
ERROR | 因爲更嚴重的問題,軟件已不能執行一些功能了。 通常錯誤消息。 |
CRITICAL | 嚴重錯誤,代表軟件已不能繼續運行了。 |
NOTICE | 不是錯誤,可是可能須要處理。普通可是重要的事件。 |
ALERT | 須要當即修復,例如系統數據庫損壞。 |
EMERGENCY | 緊急狀況,系統不可用(例如系統崩潰),通常會通知全部用戶。 |
一條日誌信息對應的是一個事件的發生,而一個事件一般須要包括如下幾個內容:
事件發生時間
事件發生位置
事件的嚴重程度--日誌級別
事件內容
上面這些都是一條日誌記錄中可能包含的字段信息,固然還能夠包括一些其餘信息,如進程ID、進程名稱、線程ID、線程名稱等。日誌格式就是用來定義一條日誌記錄中包含那些字段的,且日誌格式一般都是能夠自定義的。
幾乎全部開發語言都會內置日誌相關功能,或者會有比較優秀的第三方庫來提供日誌操做功能,好比:log4j,log4php等。它們功能強大、使用簡單。Python自身也提供了一個用於記錄日誌的標準庫模塊--logging。
logging模塊是Python內置的標準模塊,主要用於輸出運行日誌,能夠設置輸出日誌的等級、日誌保存路徑、日誌文件回滾等;相比print,具有以下優勢:
能夠經過設置不一樣的日誌等級,在release版本中只輸出重要信息,而沒必要顯示大量的調試信息;
print將全部信息都輸出到標準輸出中,嚴重影響開發者從標準輸出中查看其它數據;logging則能夠由開發者決定將信息輸出到什麼地方,以及怎麼輸出。
logging模塊默認定義瞭如下幾個日誌等級,它容許開發人員自定義其餘日誌級別,可是這是不被推薦的,尤爲是在開發供別人使用的庫時,由於這會致使日誌級別的混亂。
日誌等級(level) | 描述 |
---|---|
DEBUG | 最詳細的日誌信息,典型應用場景是 問題診斷 |
INFO | 信息詳細程度僅次於DEBUG,一般只記錄關鍵節點信息,用於確認一切都是按照咱們預期的那樣進行工做 |
WARNING | 當某些不指望的事情發生時記錄的信息(如,磁盤可用空間較低),可是此時應用程序仍是正常運行的 |
ERROR | 因爲一個更嚴重的問題致使某些功能不能正常運行時記錄的信息 |
CRITICAL | 當發生嚴重錯誤,致使應用程序不能繼續運行時記錄的信息 |
開發應用程序或部署開發環境時,可使用DEBUG或INFO級別的日誌獲取儘量詳細的日誌信息來進行開發或部署調試;
應用上線或部署生產環境時,應該使用WARNING或ERROR或CRITICAL級別的日誌來下降機器的I/O壓力和提升獲取錯誤日誌信息的效率。日誌級別的指定一般都是在應用程序的配置文件中進行指定的。
說明:
上面列表中的日誌等級是從上到下依次升高的,即:DEBUG < INFO < WARNING < ERROR < CRITICAL,而日誌的信息量是依次減小的;
當爲某個應用程序指定一個日誌級別後,應用程序會記錄全部日誌級別大於或等於指定日誌級別的日誌信息,而不是僅僅記錄指定級別的日誌信息,nginx、php等應用程序以及這裏的python的logging模塊都是這樣的。一樣,logging模塊也能夠指定日誌記錄器的日誌級別,只有級別大於或等於該指定日誌級別的日誌記錄纔會被輸出,小於該等級的日誌記錄將會被丟棄。
logging模塊提供了兩種記錄日誌的方式:
第一種方式是使用logging提供的模塊級別的函數
第二種方式是使用Logging日誌系統的四大組件
其實,logging所提供的模塊級別的日誌記錄函數也是對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名稱:用戶輸出消息
簡單的例子
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")
輸出結果
2018-05-09 23:37:49 Wed root DEBUG msg1 2018-05-09 23:37:49 Wed root INFO msg2 2018-05-09 23:37:49 Wed root WARNING msg3 2018-05-09 23:37:49 Wed root ERROR msg4 2018-05-09 23:37:49 Wed root CRITICAL msg5
參數名稱 | 描述 |
---|---|
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異常。 |
字段/屬性名稱 | 使用格式 | 描述 |
---|---|---|
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"d:\test\test.log" #有了filename參數就不會直接輸出顯示到控制檯,而是直接寫入文件 ) logging.debug("msg1") logging.info("msg2") logging.warning("msg3") logging.error("msg4") logging.critical("msg5")
輸出結果
日誌在d:\test目錄下,日誌具體內容
2018-05-10 02:13:46 Thu AM root DEBUG D:/06python/exercise/test_package/test.py msg1 2018-05-10 02:13:46 Thu AM root INFO D:/06python/exercise/test_package/test.py msg2 2018-05-10 02:13:46 Thu AM root WARNING D:/06python/exercise/test_package/test.py msg3 2018-05-10 02:13:46 Thu AM root ERROR D:/06python/exercise/test_package/test.py msg4 2018-05-10 02:13:46 Thu AM root CRITICAL D:/06python/exercise/test_package/test.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關鍵詞參數的說明:見參考資料1。(瞭解)
上面簡單配置的方法例子中咱們瞭解到了logging.debug()、logging.info()、logging.warning()、logging.error()、logging.critical()(分別用以記錄不一樣級別的日誌信息),logging.basicConfig()(用默認日誌格式(Formatter)爲日誌系統創建一個默認的流處理器(StreamHandler),設置基礎配置(如日誌級別等)並加到root logger(根Logger)中)這幾個logging模塊級別的函數。
第二種是一個模塊級別的函數是logging.getLogger([name])(返回一個logger對象,若是沒有指定名字將返回root logger)。
在介紹logging模塊的日誌流處理流程以前,咱們先來介紹下logging模塊的四大組件:
組件名稱 | 對應類名 | 功能描述 |
---|---|---|
日誌器 | Logger | 提供了應用程序可一直使用的接口 |
處理器 | Handler | 將logger建立的日誌記錄發送到合適的目的輸出 |
過濾器 | Filter | 提供了更細粒度的控制工具來決定輸出哪條日誌記錄,丟棄哪條日誌記錄 |
格式器 | Formatter | 決定日誌記錄的最終輸出格式 |
logging模塊就是經過這些組件來完成日誌處理的,上面所使用的logging模塊級別的函數也是經過這些組件對應的類來實現的。
日誌器(logger)須要經過處理器(handler)將日誌信息輸出到目標位置,如:文件、sys.stdout、網絡等;
不一樣的處理器(handler)能夠將日誌輸出到不一樣的位置;
日誌器(logger)能夠設置多個處理器(handler)將同一條日誌記錄輸出到不一樣的位置;
每一個處理器(handler)均可以設置本身的過濾器(filter)實現日誌過濾,從而只保留感興趣的日誌;
每一個處理器(handler)均可以設置本身的格式器(formatter)實現同一條日誌以不一樣的格式輸出到不一樣的地方。
簡單點說就是:日誌器(logger)是入口,真正幹活兒的是處理器(handler),處理器(handler)還能夠經過過濾器(filter)和格式器(formatter)對要輸出的日誌內容作過濾和格式化等處理操做。
與logging四大組件相關的類:Logger, Handler, Filter, Formatter。
Logger對象有3個任務要作:
1)嚮應用程序代碼暴露幾個方法,使應用程序能夠在運行時記錄日誌消息;
2)基於日誌嚴重等級(默認的過濾設施)或filter對象來決定要對哪些日誌進行後續處理;
3)將日誌消息傳送給全部感興趣的日誌handlers。
Logger對象最經常使用的方法分爲兩類:配置方法 和 消息發送方法
最經常使用的配置方法以下:
方法 | 描述 |
---|---|
Logger.setLevel() | 設置日誌器將會處理的日誌消息的最低嚴重級別 |
Logger.addHandler() 和 Logger.removeHandler() | 爲該logger對象添加 和 移除一個handler對象 |
Logger.addFilter() 和 Logger.removeFilter() | 爲該logger對象添加 和 移除一個filter對象 |
logger對象配置完成後,可使用下面的方法來建立日誌記錄:
方法 | 描述 |
---|---|
Logger.debug(), Logger.info(), Logger.warning(), Logger.error(), Logger.critical() | 建立一個與它們的方法名對應等級的日誌記錄 |
Logger.exception() | 建立一個相似於Logger.error()的日誌消息 |
Logger.log() | 須要獲取一個明確的日誌level參數來建立一個日誌記錄 |
一個Logger對象呢?一種方式是經過Logger類的實例化方法建立一個Logger類的實例,可是咱們一般都是用第二種方式--logging.getLogger()方法。
logging.getLogger()方法有一個可選參數name,該參數表示將要返回的日誌器的名稱標識,若是不提供該參數,則其值爲'root'。若以相同的name參數值屢次調用getLogger()方法,將會返回指向同一個logger對象的引用。
屢次使用注意不能建立多個logger,不然會出現重複輸出日誌現象。
關於logger的層級結構與有效等級的說明:
logger的名稱是一個以'.'分割的層級結構,每一個'.'後面的logger都是'.'前面的logger的children,例如,有一個名稱爲 foo 的logger,其它名稱分別爲 foo.bar, foo.bar.baz 和 foo.bam都是 foo 的後代。
logger有一個"有效等級(effective level)"的概念。若是一個logger上沒有被明確設置一個level,那麼該logger就是使用它parent的level;若是它的parent也沒有明確設置level則繼續向上查找parent的parent的有效level,依次類推,直到找到個一個明確設置了level的祖先爲止。須要說明的是,root logger老是會有一個明確的level設置(默認爲 WARNING)。當決定是否去處理一個已發生的事件時,logger的有效等級將會被用來決定是否將該事件傳遞給該logger的handlers進行處理。
child loggers在完成對日誌消息的處理後,默認會將日誌消息傳遞給與它們的祖先loggers相關的handlers。所以,咱們沒必要爲一個應用程序中所使用的全部loggers定義和配置handlers,只須要爲一個頂層的logger配置handlers,而後按照須要建立child loggers就可足夠了。咱們也能夠經過將一個logger的propagate屬性設置爲False來關閉這種傳遞機制。
Handler對象的做用是(基於日誌消息的level)將消息分發到handler指定的位置(文件、網絡、郵件等)。Logger對象能夠經過addHandler()方法爲本身添加0個或者更多個handler對象。好比,一個應用程序可能想要實現如下幾個日誌需求:
1)把全部日誌都發送到一個日誌文件中;
2)把全部嚴重級別大於等於error的日誌發送到stdout(標準輸出);
3)把全部嚴重級別爲critical的日誌發送到一個email郵件地址。這種場景就須要3個不一樣的handlers,每一個handler複雜發送一個特定嚴重級別的日誌到一個特定的位置。
Handler.setLevel(lel):指定被處理的信息級別,低於lel級別的信息將被忽略
Handler.setFormatter():給這個handler選擇一個格式
Handler.addFilter(filt)、Handler.removeFilter(filt):新增或刪除一個filter對象
須要說明的是,應用程序代碼不該該直接實例化和使用Handler實例。由於Handler是一個基類,它只定義了素有handlers都應該有的接口,同時提供了一些子類能夠直接使用或覆蓋的默認行爲。下面是一些經常使用的Handler:
Handler | 描述 |
---|---|
logging.StreamHandler | 將日誌消息發送到輸出到Stream,如std.out, std.err或任何file-like對象。 |
logging.FileHandler | 將日誌消息發送到磁盤文件,默認狀況下文件大小會無限增加 |
logging.handlers.RotatingFileHandler | 將日誌消息發送到磁盤文件,並支持日誌文件按大小切割 |
logging.hanlders.TimedRotatingFileHandler | 將日誌消息發送到磁盤文件,並支持日誌文件按時間切割 |
logging.handlers.HTTPHandler | 將日誌消息以GET或POST的方式發送給一個HTTP服務器 |
logging.handlers.SMTPHandler | 將日誌消息發送給一個指定的email地址 |
logging.NullHandler | 該Handler實例會忽略error messages,一般被想使用logging的library開發者使用來避免'No handlers could be found for logger XXX'信息的出現。 |
Formater對象用於配置日誌信息的最終順序、結構和內容。與logging.Handler基類不一樣的是,應用代碼能夠直接實例化Formatter類。另外,若是你的應用程序須要一些特殊的處理行爲,也能夠實現一個Formatter的子類來完成。
Formatter類的構造方法定義以下:
logging.Formatter.__init__(fmt=None, datefmt=None, style='%')
可見,該構造方法接收3個可選參數:
fmt:指定消息格式化字符串,若是不指定該參數則默認使用message的原始值
datefmt:指定日期格式字符串,若是不指定該參數則默認使用"%Y-%m-%d %H:%M:%S"
style:Python 3.2新增的參數,可取值爲 '%', '{'和 '$',若是不指定該參數則默認使用'%'
通常直接用logging.Formatter(fmt, datefmt)
Filter能夠被Handler和Logger用來作比level更細粒度的、更復雜的過濾功能。Filter是一個過濾器基類,它只容許某個logger層級下的日誌事件經過過濾。該類定義以下:
class logging.Filter(name='')
filter(record)
好比,一個filter實例化時傳遞的name參數值爲'A.B',那麼該filter實例將只容許名稱爲相似以下規則的loggers產生的日誌記錄經過過濾:'A.B','A.B,C','A.B.C.D','A.B.D',而名稱爲'A.BB', 'B.A.B'的loggers產生的日誌則會被過濾掉。若是name的值爲空字符串,則容許全部的日誌事件經過過濾。
filter方法用於具體控制傳遞的record記錄是否能經過過濾,若是該方法返回值爲0表示不能經過過濾,返回值爲非0表示能夠經過過濾。
說明:
若是有須要,也能夠在filter(record)方法內部改變該record,好比添加、刪除或修改一些屬性。
咱們還能夠經過filter作一些統計工做,好比能夠計算下被一個特殊的logger或handler所處理的record數量等。
一、建立一個logger
二、設置下logger的日誌的等級
三、建立合適的Handler(FileHandler要有路徑)
四、設置下每一個Handler的日誌等級
五、建立下日誌的格式
六、向Handler中添加上面建立的格式
七、將上面建立的Handler添加到logger中
八、打印輸出logger.debug\logger.info\logger.warning\logger.error\logger.critical
例子
import logging #建立logger,若是參數爲空則返回root logger logger = logging.getLogger("nick") logger.setLevel(logging.DEBUG) #設置logger日誌等級 #建立handler fh = logging.FileHandler("test.log",encoding="utf-8") ch = logging.StreamHandler() #設置輸出日誌格式 formatter = logging.Formatter( fmt="%(asctime)s %(name)s %(filename)s %(message)s", datefmt="%Y/%m/%d %X" ) #注意 logging.Formatter的大小寫 #爲handler指定輸出格式,注意大小寫 fh.setFormatter(formatter) ch.setFormatter(formatter) #爲logger添加的日誌處理器 logger.addHandler(fh) logger.addHandler(ch) #輸出不一樣級別的log logger.warning("泰拳警告") logger.info("提示") logger.error("錯誤")
用Python的logging模塊記錄日誌時,可能會遇到重複記錄日誌的問題,第一條記錄寫一次,第二條記錄寫兩次,第三條記錄寫三次
緣由:沒有移除handler 解決:在日誌記錄完以後removeHandler
例子
def log(msg): #建立logger,若是參數爲空則返回root logger logger = logging.getLogger("nick") logger.setLevel(logging.DEBUG) #設置logger日誌等級 #建立handler fh = logging.FileHandler("test.log",encoding="utf-8") ch = logging.StreamHandler() #設置輸出日誌格式 formatter = logging.Formatter( fmt="%(asctime)s %(name)s %(filename)s %(message)s", datefmt="%Y/%m/%d %X" ) #爲handler指定輸出格式 fh.setFormatter(formatter) ch.setFormatter(formatter) #爲logger添加的日誌處理器 logger.addHandler(fh) logger.addHandler(ch) # 輸出不一樣級別的log logger.info(msg) log("泰拳警告") log("提示") log("錯誤")
輸出結果
2018/05/10 20:06:18 nick test.py 泰拳警告 2018/05/10 20:06:18 nick test.py 提示 2018/05/10 20:06:18 nick test.py 提示 2018/05/10 20:06:18 nick test.py 錯誤 2018/05/10 20:06:18 nick test.py 錯誤 2018/05/10 20:06:18 nick test.py 錯誤
分析:能夠看到輸出結果有重複打印
緣由:第二次調用log的時候,根據getLogger(name)裏的name獲取同一個logger,而這個logger裏已經有了第一次你添加的handler,第二次調用又添加了一個handler,因此,這個logger裏有了兩個一樣的handler,以此類推,調用幾回就會有幾個handler。
解決方案1
添加removeHandler語句
import logging def log(msg): #建立logger,若是參數爲空則返回root logger logger = logging.getLogger("nick") logger.setLevel(logging.DEBUG) #設置logger日誌等級 #建立handler fh = logging.FileHandler("test.log",encoding="utf-8") ch = logging.StreamHandler() #設置輸出日誌格式 formatter = logging.Formatter( fmt="%(asctime)s %(name)s %(filename)s %(message)s", datefmt="%Y/%m/%d %X" ) #爲handler指定輸出格式 fh.setFormatter(formatter) ch.setFormatter(formatter) #爲logger添加的日誌處理器 logger.addHandler(fh) logger.addHandler(ch) # 輸出不一樣級別的log logger.info(msg) #解決方案1,添加removeHandler語句,每次用完以後移除Handler logger.removeHandler(fh) logger.removeHandler(ch) log("泰拳警告") log("提示") log("錯誤")
解決方案2
在log方法裏作判斷,若是這個logger已有handler,則再也不添加handler。
import logging def log(msg): #建立logger,若是參數爲空則返回root logger logger = logging.getLogger("nick") logger.setLevel(logging.DEBUG) #設置logger日誌等級 #解決方案2:這裏進行判斷,若是logger.handlers列表爲空,則添加,不然,直接去寫日誌 if not logger.handlers: #建立handler fh = logging.FileHandler("test.log",encoding="utf-8") ch = logging.StreamHandler() #設置輸出日誌格式 formatter = logging.Formatter( fmt="%(asctime)s %(name)s %(filename)s %(message)s", datefmt="%Y/%m/%d %X" ) #爲handler指定輸出格式 fh.setFormatter(formatter) ch.setFormatter(formatter) #爲logger添加的日誌處理器 logger.addHandler(fh) logger.addHandler(ch) # 輸出不一樣級別的log logger.info(msg) log("泰拳警告") log("提示") log("錯誤")
logger調用方式例子
import logging def log(): #建立logger,若是參數爲空則返回root logger logger = logging.getLogger("nick") logger.setLevel(logging.DEBUG) #設置logger日誌等級 #這裏進行判斷,若是logger.handlers列表爲空,則添加,不然,直接去寫日誌 if not logger.handlers: #建立handler fh = logging.FileHandler("test.log",encoding="utf-8") ch = logging.StreamHandler() #設置輸出日誌格式 formatter = logging.Formatter( fmt="%(asctime)s %(name)s %(filename)s %(message)s", datefmt="%Y/%m/%d %X" ) #爲handler指定輸出格式 fh.setFormatter(formatter) ch.setFormatter(formatter) #爲logger添加的日誌處理器 logger.addHandler(fh) logger.addHandler(ch) return logger #直接返回logger logger = log() logger.warning("泰拳警告") logger.info("提示") logger.error("錯誤") logger.debug("查錯")
參考連接
[1]http://www.javashuo.com/article/p-wqlkttny-y.html
[2]https://blog.csdn.net/huilan_same/article/details/51858817