Python之日志處理(logging模塊)


轉載自:https://www.cnblogs.com/yyds/p/6901864.html

 

本節內容


  1. 日志相關概念
  2. logging模塊簡介
  3. 使用logging提供的模塊級別的函數記錄日志
  4. logging模塊日志流處理流程
  5. 使用logging四大組件記錄日志
  6. 配置logging的幾種方式
  7. 向日志輸出中添加上下文信息
  8. 參考文檔

一、日志相關概念


日志是一種可以追蹤某些軟件運行時所發生事件的方法。軟件開發人員可以向他們的代碼中調用日志記錄相關的方法來表明發生了某些事情。一個事件可以用一個可包含可選變量數據的消息來描述。此外,事件也有重要性的概念,這個重要性也可以被稱為嚴重性級別(level)。

1.日志的作用

通過log的分析,可以方便用戶了解系統或軟件、應用的運行情況;如果你的應用log足夠豐富,也可以分析以往用戶的操作行為、類型喜好、地域分布或其他更多信息;如果一個應用的log同時也分了多個級別,那么可以很輕易地分析得到該應用的健康狀況,及時發現問題並快速定位、解決問題,補救損失。
簡單來講就是,我們通過記錄和分析日志可以了解一個系統或軟件程序運行情況是否正常,也可以在應用程序出現故障時快速定位問題。比如,做運維的同學,在接收到報警或各種問題反饋后,進行問題排查時通常都會先去看各種日志,大部分問題都可以在日志中找到答案。再比如,做開發的同學,可以通過IDE控制台上輸出的各種日志進行程序調試。對於運維老司機或者有經驗的開發人員,可以快速的通過日志定位到問題的根源。可見,日志的重要性不可小覷。日志的作用可以簡單總結為以下3點:

  • 程序調試
  • 了解軟件程序運行情況,是否正常
  • 軟件程序運行故障分析與問題定位

如果應用的日志信息足夠詳細和豐富,還可以用來做用戶行為分析,如:分析用戶的操作行為、類型洗好、地域分布以及其它更多的信息,由此可以實現改進業務、提高商業利益。

2.日志的等級

我們先來思考下下面的兩個問題:

  • 作為開發人員,在開發一個應用程序時需要什么日志信息?在應用程序正式上線后需要什么日志信息?
  • 作為應用運維人員,在部署開發環境時需要什么日志信息?在部署生產環境時需要什么日志信息?

在軟件開發階段或部署開發環境時,為了盡可能詳細的查看應用程序的運行狀態來保證上線后的穩定性,我們可能需要把該應用程序所有的運行日志全部記錄下來進行分析,這是非常耗費機器性能的。當應用程序正式發布或在生產環境部署應用程序時,我們通常只需要記錄應用程序的異常信息、錯誤信息等,這樣既可以減小服務器的I/O壓力,也可以避免我們在排查故障時被淹沒在日志的海洋里。那么,怎樣才能在不改動應用程序代碼的情況下實現在不同的環境記錄不同詳細程度的日志呢?這就是日志等級的作用了,我們通過配置文件指定我們需要的日志等級就可以了。

不同的應用程序所定義的日志等級可能會有所差別,分的詳細點的會包含以下幾個等級:

  • DEBUG
  • INFO
  • NOTICE
  • WARNING
  • ERROR
  • CRITICAL
  • ALERT
  • EMERGENCY

3.日志字段信息與日志格式

本節開始問題提到過,一條日志信息對應的是一個事件的發生,而一個事件通常需要包括以下幾個內容:

  • 事件發生時間
  • 事件發生位置
  • 事件的嚴重程度--日志級別
  • 事件內容

上面這些都是一條日志記錄中可能包含的字段信息,當然還可以包括一些其他信息,如進程ID、進程名稱、線程ID、線程名稱等。日志格式就是用來定義一條日志記錄中包含那些字段的,且日志格式通常都是可以自定義的。

說明:

輸出一條日志時,日志內容和日志級別是需要開發人員明確指定的。對於而其它字段信息,只需要是否顯示在日志中就可以了。

4.日志功能的實現

幾乎所有開發語言都會內置日志相關功能,或者會有比較優秀的第三方庫來提供日志操作功能,比如:log4j,log4php等。它們功能強大、使用簡單。Python自身也提供了一個用於記錄日志的標准庫模塊--logging。

二、logging模塊簡介


logging模塊定義的函數和類為應用程序和庫的開發實現了一個靈活的事件日志系統。logging模塊是Python的一個標准庫模塊,由標准庫模塊提供日志記錄API的關鍵好處是所有Python模塊都可以使用這個日志記錄功能。所以,你的應用日志可以將你自己的日志信息與來自第三方模塊的信息整合起來。

1. logging模塊的日志級別

logging模塊默認定義了以下幾個日志等級,它允許開發人員自定義其他日志級別,但是這是不被推薦的,尤其是在開發供別人使用的庫時,因為這會導致日志級別的混亂。

開發應用程序或部署開發環境時,可以使用DEBUG或INFO級別的日志獲取盡可能詳細的日志信息來進行開發或部署調試;應用上線或部署生產環境時,應該使用WARNING或ERROR或CRITICAL級別的日志來降低機器的I/O壓力和提高獲取錯誤日志信息的效率。日志級別的指定通常都是在應用程序的配置文件中進行指定的。

說明:

  • 上面列表中的日志等級是從上到下依次升高的,即:DEBUG < INFO < WARNING < ERROR < CRITICAL,而日志的信息量是依次減少的;
  • 當為某個應用程序指定一個日志級別后,應用程序會記錄所有日志級別大於或等於指定日志級別的日志信息,而不是僅僅記錄指定級別的日志信息,nginx、php等應用程序以及這里要提高的python的logging模塊都是這樣的。同樣,logging模塊也可以指定日志記錄器的日志級別,只有級別大於或等於該指定日志級別的日志記錄才會被輸出,小於該等級的日志記錄將會被丟棄。

2. logging模塊的使用方式介紹

logging模塊提供了兩種記錄日志的方式:

  • 第一種方式是使用logging提供的模塊級別的函數
  • 第二種方式是使用Logging日志系統的四大組件

其實,logging所提供的模塊級別的日志記錄函數也是對logging日志系統相關類的封裝而已。

logging模塊定義的模塊級別的常用函數

其中logging.basicConfig(**kwargs)函數用於指定“要記錄的日志級別”、“日志格式”、“日志輸出位置”、“日志文件的打開模式”等信息,其他幾個都是用於記錄各個級別日志的函數。

logging模塊的四大組件

說明: logging模塊提供的模塊級別的那些函數實際上也是通過這幾個組件的相關實現類來記錄日志的,只是在創建這些類的實例時設置了一些默認值。

三、使用logging提供的模塊級別的函數記錄日志


回顧下前面提到的幾個重要信息:

  • 可以通過logging模塊定義的模塊級別的方法去完成簡單的日志記錄
  • 只有級別大於或等於日志記錄器指定級別的日志記錄才會被輸出,小於該級別的日志記錄將會被丟棄。

1.最簡單的日志輸出

先來試着分別輸出一條不同日志級別的日志記錄:

也可以這樣寫:

輸出結果:

2. 那么問題來了

問題1:為什么前面兩條日志沒有被打印出來?

這是因為logging模塊提供的日志記錄函數所使用的日志器設置的日志級別是WARNING,因此只有WARNING級別的日志記錄以及大於它的ERRORCRITICAL級別的日志記錄被輸出了,而小於它的DEBUGINFO級別的日志記錄被丟棄了。

問題2:打印出來的日志信息中各字段表示什么意思?為什么會這樣輸出?

上面輸出結果中每行日志記錄的各個字段含義分別是:

日志級別:日志器名稱:日志內容

之所以會這樣輸出,是因為logging模塊提供的日志記錄函數所使用的日志器設置的日志格式默認是BASIC_FORMAT,其值為:

"%(levelname)s:%(name)s:%(message)s"
問題3:如果將日志記錄輸出到文件中,而不是打印到控制台?

因為在logging模塊提供的日志記錄函數所使用的日志器設置的處理器所指定的日志輸出位置默認為:
sys.stderr

問題4:我是怎么知道這些的?

查看這些日志記錄函數的實現代碼,可以發現:當我們沒有提供任何配置信息的時候,這些函數都會去調用logging.basicConfig(**kwargs)方法,且不會向該方法傳遞任何參數。繼續查看basicConfig()方法的代碼就可以找到上面這些問題的答案了。

問題5:怎么修改這些默認設置呢?

其實很簡單,在我們調用上面這些日志記錄函數之前,手動調用一下basicConfig()方法,把我們想設置的內容以參數的形式傳遞進去就可以了。

3. logging.basicConfig()函數說明

該方法用於為logging日志系統做一些基本配置,方法定義如下:

logging.basicConfig(**kwargs)

該函數可接收的關鍵字參數如下:

4. logging模塊定義的格式字符串字段

我們來列舉一下logging模塊中定義好的可以用於format格式字符串中字段有哪些:

 

 

5.經過配置的日志輸出

先簡單配置下日志器的日志級別

 

輸出結果:

所有等級的日志信息都被輸出了,說明配置生效了。

在配置日志器日志級別的基礎上,在配置下日志輸出目標文件和日志格式

此時會發現控制台中已經沒有輸出日志內容了,但是在python代碼文件的相同目錄下會生成一個名為'my.log'的日志文件,該文件中的內容為:

在上面的基礎上,我們再來設置下日期/時間格式

此時會在my.log日志文件中看到如下輸出內容:

掌握了上面的內容之后,已經能夠滿足我們平時開發中需要的日志記錄功能。

6. 其他說明

幾個要說明的內容:
  • 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關鍵詞參數的說明:
  • exc_info: 其值為布爾值,如果該參數的值設置為True,則會將異常異常信息添加到日志消息中。如果沒有異常信息則添加None到日志信息中。
  • stack_info: 其值也為布爾值,默認值為False。如果該參數的值設置為True,棧信息將會被添加到日志信息中。
  • extra: 這是一個字典(dict)參數,它可以用來自定義消息格式中所包含的字段,但是它的key不能與logging模塊定義的字段沖突。
一個例子:

在日志消息中添加exc_info和stack_info信息,並添加兩個自定義的字端 ip和user

輸出結果:

四、logging模塊日志流處理流程


在介紹logging模塊的高級用法之前,很有必要對logging模塊所包含的重要組件以及其工作流程做個全面、簡要的介紹,這有助於我們更好的理解我們所寫的代碼(將會觸發什么樣的操作)。

1. logging日志模塊四大組件

在介紹logging模塊的日志流處理流程之前,我們先來介紹下logging模塊的四大組件:

logging模塊就是通過這些組件來完成日志處理的,上面所使用的logging模塊級別的函數也是通過這些組件對應的類來實現的。

這些組件之間的關系描述:
  • 日志器(logger)需要通過處理器(handler)將日志信息輸出到目標位置,如:文件、sys.stdout、網絡等;
  • 不同的處理器(handler)可以將日志輸出到不同的位置;
  • 日志器(logger)可以設置多個處理器(handler)將同一條日志記錄輸出到不同的位置;
  • 每個處理器(handler)都可以設置自己的過濾器(filter)實現日志過濾,從而只保留感興趣的日志;
  • 每個處理器(handler)都可以設置自己的格式器(formatter)實現同一條日志以不同的格式輸出到不同的地方。

簡單點說就是:日志器(logger)是入口,真正干活兒的是處理器(handler),處理器(handler)還可以通過過濾器(filter)和格式器(formatter)對要輸出的日志內容做過濾和格式化等處理操作。

2. logging日志模塊相關類及其常用方法介紹

下面介紹下與logging四大組件相關的類:Logger, Handler, Filter, Formatter。

Logger類

Logger對象有3個任務要做:

  • 1)向應用程序代碼暴露幾個方法,使應用程序可以在運行時記錄日志消息;
  • 2)基於日志嚴重等級(默認的過濾設施)或filter對象來決定要對哪些日志進行后續處理;
  • 3)將日志消息傳送給所有感興趣的日志handlers。

Logger對象最常用的方法分為兩類:配置方法 和 消息發送方法

最常用的配置方法如下:

關於Logger.setLevel()方法的說明:

內建等級中,級別最低的是DEBUG,級別最高的是CRITICAL。例如setLevel(logging.INFO),此時函數參數為INFO,那么該logger將只會處理INFO、WARNING、ERROR和CRITICAL級別的日志,而DEBUG級別的消息將會被忽略/丟棄。

logger對象配置完成后,可以使用下面的方法來創建日志記錄:

說明:

  • Logger.exception()與Logger.error()的區別在於:Logger.exception()將會輸出堆棧追蹤信息,另外通常只是在一個exception handler中調用該方法。
  • Logger.log()與Logger.debug()、Logger.info()等方法相比,雖然需要多傳一個level參數,顯得不是那么方便,但是當需要記錄自定義level的日志時還是需要該方法來完成。

那么,怎樣得到一個Logger對象呢?一種方式是通過Logger類的實例化方法創建一個Logger類的實例,但是我們通常都是用第二種方式--logging.getLogger()方法。

logging.getLogger()方法有一個可選參數name,該參數表示將要返回的日志器的名稱標識,如果不提供該參數,則其值為'root'。若以相同的name參數值多次調用getLogger()方法,將會返回指向同一個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類

Handler對象的作用是(基於日志消息的level)將消息分發到handler指定的位置(文件、網絡、郵件等)。Logger對象可以通過addHandler()方法為自己添加0個或者更多個handler對象。比如,一個應用程序可能想要實現以下幾個日志需求:

  • 1)把所有日志都發送到一個日志文件中;
  • 2)把所有嚴重級別大於等於error的日志發送到stdout(標准輸出);
  • 3)把所有嚴重級別為critical的日志發送到一個email郵件地址。
    這種場景就需要3個不同的handlers,每個handler復雜發送一個特定嚴重級別的日志到一個特定的位置。

一個handler中只有非常少數的方法是需要應用開發人員去關心的。對於使用內建handler對象的應用開發人員來說,似乎唯一相關的handler方法就是下面這幾個配置方法:

 

Formater類

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新增的參數,可取值為 '%', '{'和 '$',如果不指定該參數則默認使用'%'
Filter類

Filter可以被Handler和Logger用來做比level更細粒度的、更復雜的過濾功能。Filter是一個過濾器基類,它只允許某個logger層級下的日志事件通過過濾。該類定義如下:

  1.  
    class logging.Filter(name='')
  2.  
    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數量等。

3. logging日志流處理流程

下面這個圖描述了日志流的處理流程:

 

 

我們來描述下上面這個圖的日志流處理流程:

  • 1)(在用戶代碼中進行)日志記錄函數調用,如:logger.info(...),logger.debug(...)等;
  • 2)判斷要記錄的日志級別是否滿足日志器設置的級別要求(要記錄的日志級別要大於或等於日志器設置的級別才算滿足要求),如果不滿足則該日志記錄會被丟棄並終止后續的操作,如果滿足則繼續下一步操作;
  • 3)根據日志記錄函數調用時摻入的參數,創建一個日志記錄(LogRecord類)對象;
  • 4)判斷日志記錄器上設置的過濾器是否拒絕這條日志記錄,如果日志記錄器上的某個過濾器拒絕,則該日志記錄會被丟棄並終止后續的操作,如果日志記錄器上設置的過濾器不拒絕這條日志記錄或者日志記錄器上沒有設置過濾器則繼續下一步操作--將日志記錄分別交給該日志器上添加的各個處理器;
  • 5)判斷要記錄的日志級別是否滿足處理器設置的級別要求(要記錄的日志級別要大於或等於該處理器設置的日志級別才算滿足要求),如果不滿足記錄將會被該處理器丟棄並終止后續的操作,如果滿足則繼續下一步操作;
  • 6)判斷該處理器上設置的過濾器是否拒絕這條日志記錄,如果該處理器上的某個過濾器拒絕,則該日志記錄會被當前處理器丟棄並終止后續的操作,如果當前處理器上設置的過濾器不拒絕這條日志記錄或當前處理器上沒有設置過濾器測繼續下一步操作;
  • 7)如果能到這一步,說明這條日志記錄經過了層層關卡允許被輸出了,此時當前處理器會根據自身被設置的格式器(如果沒有設置則使用默認格式)將這條日志記錄進行格式化,最后將格式化后的結果輸出到指定位置(文件、網絡、類文件的Stream等);
  • 8)如果日志器被設置了多個處理器的話,上面的第5-8步會執行多次;
  • 9)這里才是完整流程的最后一步:判斷該日志器輸出的日志消息是否需要傳遞給上一級logger(之前提到過,日志器是有層級關系的)的處理器,如果propagate屬性值為1則表示日志消息將會被輸出到處理器指定的位置,同時還會被傳遞給parent日志器的handlers進行處理直到當前日志器的propagate屬性為0停止,如果propagate值為0則表示不向parent日志器的handlers傳遞該消息,到此結束。

可見,一條日志信息要想被最終輸出需要依次經過以下幾次過濾:

  • 日志器等級過濾;
  • 日志器的過濾器過濾;
  • 日志器的處理器等級過濾;
  • 日志器的處理器的過濾器過濾;

需要說明的是: 關於上面第9個步驟,如果propagate值為1,那么日志消息會直接傳遞交給上一級logger的handlers進行處理,此時上一級logger的日志等級並不會對該日志消息進行等級過濾。

五、使用logging四大組件記錄日志


現在,我們對logging模塊的重要組件及整個日志流處理流程都應該有了一個比較全面的了解,下面我們來看一個例子。

1. 需求

現在有以下幾個日志記錄的需求:

  • 1)要求將所有級別的所有日志都寫入磁盤文件中
  • 2)all.log文件中記錄所有的日志信息,日志格式為:日期和時間 - 日志級別 - 日志信息
  • 3)error.log文件中單獨記錄error及以上級別的日志信息,日志格式為:日期和時間 - 日志級別 - 文件名[:行號] - 日志信息
  • 4)要求all.log在每天凌晨進行日志切割

2. 分析

  • 1)要記錄所有級別的日志,因此日志器的有效level需要設置為最低級別--DEBUG;
  • 2)日志需要被發送到兩個不同的目的地,因此需要為日志器設置兩個handler;另外,兩個目的地都是磁盤文件,因此這兩個handler都是與FileHandler相關的;
  • 3)all.log要求按照時間進行日志切割,因此他需要用logging.handlers.TimedRotatingFileHandler; 而error.log沒有要求日志切割,因此可以使用FileHandler;
  • 4)兩個日志文件的格式不同,因此需要對這兩個handler分別設置格式器;

3. 代碼實現

all.log文件輸出

error.log文件輸出

 

 

 
        

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM