python之logging模塊


一. 日志相關概念

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

1.1 日志的作用

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

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

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

1.2 日志的等級

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

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

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

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

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

1.3 日志字段信息與日志格式

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

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

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

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

1.4 日志功能的實現

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

二. logging模塊簡介

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

2.1 logging模塊的日志級別

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

最詳細的日志信息,典型應用場景是問題診斷

級別 數值 描述
CRITICAL 50 當發生嚴重錯誤,導致應用程序不能繼續運行時記錄的信息
ERROR 40 由於一個更嚴重的問題導致某些功能不能正常運行時記錄的信息
WARNING 30,默認 當某些不期望的事情發生時記錄的信息(如,磁盤可用空間較低),但是此時應用程序還是正常運行的
INFO 20 信息詳細程度僅次於DEBUG,通常只記錄關鍵節點信息,用於確認一切都是按照我們預期的那樣進行工作
DEBUG 10 最詳細的日志信息,典型應用場景是 問題診斷
NOTSET 0  NOTSET表示所有級別的日志消息都要記錄,包括用戶定義級別

 

 

 

 

 

 

 

 

 

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

說明:

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

2.2 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)函數用於指定“要記錄的日志級別”、“日志格式”、“日志輸出位置”、“日志文件的打開模式”等信息,其他幾個都是用於記錄各個級別日志的函數。

logging模塊的四大組件

組件 說明
loggers 提供應用程序代碼直接使用的接口
handlers 用於將日志記錄發送到指定的目的位置
filters 提供更細粒度的日志過濾功能,用於決定哪些日志記錄將會被輸出(其它的日志記錄將會被忽略)
formatters 用於控制日志信息的最終輸出格式

 

 

 

 

 

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

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

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

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

3.1 最簡單的日志輸出

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

import logging

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.")
View Code

也可以這樣寫:

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.")
View Code

輸出結果:

WARNING:root:This is a warning log.
ERROR:root:This is a error log.
CRITICAL:root:This is a critical log.
View Code

 

 3.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.3 logging.basicConfig()函數說明

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

logging.basicConfig(**kwargs)

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

參數名稱 描述
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異常。

 

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

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

字段/屬性名稱 使用格式 描述
asctime %(asctime)s 日志事件發生的時間--人類可讀時間,如:2003-07-08 16:49:45,896
created %(created)f 日志事件發生的時間--時間戳,就是當時調用time.time()函數返回的值
relativeCreated %(relativeCreated)d 日志事件發生的時間相對於logging模塊加載時間的相對毫秒數(目前還不知道干嘛用的)
msecs %(msecs)d 日志事件發生事件的毫秒部分
levelname %(levelname)s 該日志記錄的文字形式的日志級別('DEBUG', 'INFO', 'WARNING', 'ERROR', 'CRITICAL')
levelno %(levelno)s 該日志記錄的數字形式的日志級別(10, 20, 30, 40, 50)
name %(name)s 所使用的日志器名稱,默認是'root',因為默認使用的是 rootLogger
message %(message)s 日志記錄的文本內容,通過 msg % args計算得到的
pathname %(pathname)s 調用日志記錄函數的源碼文件的全路徑
filename %(filename)s pathname的文件名部分,包含文件后綴
module %(module)s filename的名稱部分,不包含后綴
lineno %(lineno)d 調用日志記錄函數的源代碼所在的行號
funcName %(funcName)s 調用日志記錄函數的函數名
process %(process)d 進程ID
processName %(processName)s 進程名稱,Python 3.1新增
thread %(thread)d 線程ID
threadName %(thread)s 線程名稱

 

3.5 經過配置的日志輸出

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

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.")
View Code

輸出結果:

DEBUG:root:This is a debug log.
INFO:root:This is a info log.
WARNING:root:This is a warning log.
ERROR:root:This is a error log.
CRITICAL:root:This is a critical log.
View Code

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

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

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.")
View Code

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

2017-05-08 14:29:53,783 - DEBUG - This is a debug log.
2017-05-08 14:29:53,784 - INFO - This is a info log.
2017-05-08 14:29:53,784 - WARNING - This is a warning log.
2017-05-08 14:29:53,784 - ERROR - This is a error log.
2017-05-08 14:29:53,784 - CRITICAL - This is a critical log.
View Code

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

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.")
View Code

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

05/08/2017 14:29:04 PM - DEBUG - This is a debug log.
05/08/2017 14:29:04 PM - INFO - This is a info log.
05/08/2017 14:29:04 PM - WARNING - This is a warning log.
05/08/2017 14:29:04 PM - ERROR - This is a error log.
05/08/2017 14:29:04 PM - CRITICAL - This is a critical log.
View Code

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

3.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

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'})
View Code

輸出結果:

05/08/2017 16:35:00 PM - WARNING - Tom[47.98.53.222] - Some one delete the log file.
NoneType
Stack (most recent call last):
  File "C:/Users/wader/PycharmProjects/LearnPython/day06/log.py", line 45, in <module>
    logging.warning("Some one delete the log file.", exc_info=True, stack_info=True, extra={'user': 'Tom', 'ip':'47.98.53.222'})
View Code

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

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

4.1 logging日志模塊四大組件

在介紹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)對要輸出的日志內容做過濾和格式化等處理操作。

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

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

Logger類

Logger對象有3個任務要做:

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

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

最常用的配置方法如下

方法 描述
Logger.setLevel() 設置日志器將會處理的日志消息的最低嚴重級別
Logger.addHandler() 和 Logger.removeHandler() 為該logger對象添加 和 移除一個handler對象
Logger.addFilter() 和 Logger.removeFilter() 為該logger對象添加 和 移除一個filter對象

 

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

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

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

方法 描述
Logger.debug(), Logger.info(), Logger.warning(), Logger.error(), Logger.critical() 創建一個與它們的方法名對應等級的日志記錄
Logger.exception() 創建一個類似於Logger.error()的日志消息
Logger.log() 需要獲取一個明確的日志level參數來創建一個日志記錄

 

說明:

  • 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的有效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方法就是下面這幾個配置方法:

方法 描述
Handler.setLevel() 設置handler將會處理的日志消息的最低嚴重級別
Handler.setFormatter() 為handler設置一個格式器對象
Handler.addFilter() 和 Handler.removeFilter() 為handler添加 和 刪除一個過濾器對象

 

 需要說明的是,應用程序代碼不應該直接實例化和使用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類

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層級下的日志事件通過過濾。該類定義如下:

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數量等。

4.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模塊的重要組件及整個日志流處理流程都應該有了一個比較全面的了解,下面我們來看一個例子。

5.1 需求

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

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

5.2 分析

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

5.3 代碼實現

import logging
import logging.handlers
import datetime

logger = 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')
View Code

all.log文件輸出

2017-05-13 16:12:40,612 - DEBUG - debug message
2017-05-13 16:12:40,612 - INFO - info message
2017-05-13 16:12:40,612 - WARNING - warning message
2017-05-13 16:12:40,612 - ERROR - error message
2017-05-13 16:12:40,613 - CRITICAL - critical message
View Code

error.log文件輸出

2017-05-13 16:12:40,612 - ERROR - log.py[:81] - error message
2017-05-13 16:12:40,613 - CRITICAL - log.py[:82] - critical message
View Code

 六. 配置logging的幾種方式

作為開發者,我們可以通過以下3中方式來配置logging:

  • 1)使用Python代碼顯式的創建loggers, handlers和formatters並分別調用它們的配置函數;
  • 2)創建一個日志配置文件,然后使用fileConfig()函數來讀取該文件的內容;
  • 3)創建一個包含配置信息的dict,然后把它傳遞給dictConfig()函數;

需要說明的是,logging.basicConfig()也屬於第一種方式,它只是對loggers, handlers和formatters的配置函數進行了封裝。另外,第二種配置方式相對於第一種配置方式的優點在於,它將配置信息和代碼進行了分離,這一方面降低了日志的維護成本,同時還使得非開發人員也能夠去很容易地修改日志配置。

6.1 使用Python代碼實現日志配置

import logging
import sys
logger = logging.getLogger('simple_logger')
logger.setLevel(logging.DEBUG)

# 創建一個流處理器handler並設置其日志級別為DEBUG
handler = logging.StreamHandler(sys.stdout)
handler.setLevel(logging.DEBUG)

# 創建一個格式器formatter並將其添加到處理器handler
formatter = logging.Formatter("%(asctime)s - %(name)s - %(levelname)s - %(message)s")
handler.setFormatter(formatter)

# 為日志器logger添加上面創建的處理器handler
logger.addHandler(handler)

# 日志輸出
logger.debug('debug message')
logger.info('info message')
logger.warning('warn message')
logger.error('error message')
logger.critical('critical message')
View Code

運行輸出:

2018-07-30 10:19:58,839 - simple_logger - DEBUG - debug message
2018-07-30 10:19:58,839 - simple_logger - INFO - info message
2018-07-30 10:19:58,839 - simple_logger - WARNING - warn message
2018-07-30 10:19:58,839 - simple_logger - ERROR - error message
2018-07-30 10:19:58,839 - simple_logger - CRITICAL - critical message
View Code

6.2 使用配置文件和fileConfig()函數實現日志配置

 現在我們通過配置文件的方式來實現與上面同樣的功能:

import logging
import logging.config
import sys

# 讀取日志配置文件內容

logging.config.fileConfig('logging.conf')

# 創建一個日志器logger
logger = logging.getLogger('simpleExample')

# 日志輸出
logger.debug('debug message')
logger.info('info message')
logger.warn('warn message')
logger.error('error message')
logger.critical('critical message')
View Code

配置文件內容如下:

[loggers]
keys=root,simpleExample

[handlers]
keys=fileHandler,consoleHandler

[formatters]
keys=simpleFormatter

[logger_root]
level=DEBUG
handlers=fileHandler

[logger_simpleExample]
level=DEBUG
handlers=consoleHandler
qualname=simpleExample
propagate=0

[handler_consoleHandler]
class=StreamHandler
args=(sys.stdout,)
level=DEBUG
formatter=simpleFormatter

[handler_fileHandler]
class=FileHandler
args=('logging.log', 'a')
level=ERROR
formatter=simpleFormatter

[formatter_simpleFormatter]
format=%(asctime)s - %(name)s - %(levelname)s - %(message)s
datefmt=
View Code

運行輸出:

2018-07-30 10:30:06,815 - simpleExample - DEBUG - debug message
2018-07-30 10:30:06,815 - simpleExample - INFO - info message
2018-07-30 10:30:06,815 - simpleExample - WARNING - warn message
2018-07-30 10:30:06,815 - simpleExample - ERROR - error message
2018-07-30 10:30:06,816 - simpleExample - CRITICAL - critical message
View Code

6.2.1 關於fileConfig()函數的說明:

該函數實際上是對configparser模塊的封裝。

函數定義:

該函數定義在loging.config模塊下:

logging.config.fileConfig(fname, defaults=None, disable_existing_loggers=True)

參數:

  • fname:表示配置文件的文件名或文件對象
  • defaults:指定傳給ConfigParser的默認值
  • disable_existing_loggers:這是一個布爾型值,默認值為True(為了向后兼容)表示禁用已經存在的logger,除非它們或者它們的祖先明確的出現在日志配置中;如果值為False則對已存在的loggers保持啟動狀態。

6.2.2 配置文件格式說明:

上面提到過,fileConfig()函數是對ConfigParser/configparser模塊的封裝,也就是說fileConfig()函數是基於ConfigParser/configparser模塊來理解日志配置文件的。換句話說,fileConfig()函數所能理解的配置文件基礎格式是與ConfigParser/configparser模塊一致的,只是在此基礎上對文件中包含的sectionoption做了一下規定和限制,比如:

1)配置文件中一定要包含loggershandlersformatters這些section,它們通過keys這個option來指定該配置文件中已經定義好的loggers、handlers和formatters,多個值之間用逗號分隔;另外loggers這個section中的keys一定要包含root這個值;

2)loggershandlersformatters中所指定的日志器、處理器和格式器都需要在下面以單獨的section進行定義。seciton的命名規則為[logger_loggerName][formatter_formatterName][handler_handlerName]

3)定義logger的section必須指定levelhandlers這兩個option,level的可取值為DEBUGINFOWARNINGERRORCRITICALNOTSET,其中NOTSET表示所有級別的日志消息都要記錄,包括用戶定義級別;handlers的值是以逗號分隔的handler名字列表,這里出現的handler必須出現在[handlers]這個section中,並且相應的handler必須在配置文件中有對應的section定義;

4)對於非root logger來說,除了levelhandlers這兩個option之外,還需要一些額外的option,其中qualname是必須提供的option,它表示在logger層級中的名字,在應用代碼中通過這個名字得到logger;propagate是可選項,其默認是為1,表示消息將會傳遞給高層次logger的handler,通常我們需要指定其值為0,這個可以看下下面的例子;另外,對於非root logger的level如果設置為NOTSET,系統將會查找高層次的logger來決定此logger的有效level。

5)定義handler的section中必須指定classargs這兩個option,levelformatter為可選option;class表示用於創建handler的類名,args表示傳遞給class所指定的handler類初始化方法參數,它必須是一個元組(tuple)的形式,即便只有一個參數值也需要是一個元組的形式;level與logger中的level一樣,而formatter指定的是該處理器所使用的格式器,這里指定的格式器名稱必須出現在formatters這個section中,且在配置文件中必須要有這個formatter的section定義;如果不指定formatter則該handler將會以消息本身作為日志消息進行記錄,而不添加額外的時間、日志器名稱等信息;

6)定義formatter的sectioin中的option都是可選的,其中包括format用於指定格式字符串,默認為消息字符串本身;datefmt用於指定asctime的時間格式,默認為'%Y-%m-%d %H:%M:%S'class用於指定格式器類名,默認為logging.Formatter; 

說明:

配置文件中的class指定類名時,該類名可以是相對於logging模塊的相對值,如:FileHandlerhandlers.TimeRotatingFileHandler;也可以是一個絕對路徑值,通過普通的import機制來解析,如自定義的handler類mypackage.mymodule.MyHandler,但是mypackage需要在Python可用的導入路徑中--sys.path。

6.2.3 對於propagate屬性的說明

實例1:

我們把logging.conf中simpleExample這個handler定義中的propagate屬性值改為1,或者刪除這個option(默認值就是1):

[loggers]
keys=root,simpleExample

[handlers]
keys=fileHandler,consoleHandler

[formatters]
keys=simpleFormatter

[logger_root]
level=DEBUG
handlers=fileHandler

[logger_simpleExample]
level=DEBUG
handlers=consoleHandler
qualname=simpleExample
propagate=1

[handler_consoleHandler]
class=StreamHandler
args=(sys.stdout,)
level=DEBUG
formatter=simpleFormatter

[handler_fileHandler]
class=FileHandler
args=('logging.log', 'a')
level=ERROR
formatter=simpleFormatter

[formatter_simpleFormatter]
format=%(asctime)s - %(name)s - %(levelname)s - %(message)s
datefmt=
View Code

現在來執行同樣的代碼:

 讀取日志配置文件內容
logging.config.fileConfig('logging.conf')

# 創建一個日志器logger
logger = logging.getLogger('simpleExample')

# 日志輸出
logger.debug('debug message')
logger.info('info message')
logger.warn('warn message')
logger.error('error message')
logger.critical('critical message')
View Code

我們會發現,除了在控制台有輸出信息時候,在logging.log文件中也有內容輸出:

2018-07-30 10:55:51,231 - simpleExample - ERROR - error message
2018-07-30 10:55:51,231 - simpleExample - CRITICAL - critical message
View Code

這說明simpleExample這個logger在處理完日志記錄后,把日志記錄傳遞給了上級的root logger再次做處理,所有才會有兩個地方都有日志記錄的輸出。通常,我們都需要顯示的指定propagate的值為0,防止日志記錄向上層logger傳遞。

實例2:

現在,我們試着用一個沒有在配置文件中定義的logger名稱來獲取logger:

mport logging
import logging.config
import sys

# 讀取日志配置文件內容
logging.config.fileConfig('logging.conf')

# 用一個沒有在配置文件中定義的logger名稱來創建一個日志器logger
logger = logging.getLogger('simpleExample1')

# 日志輸出
logger.debug('debug message')
logger.info('info message')
logger.warning('warn message')
logger.error('error message')
logger.critical('critical message')
View Code

運行程序后,我們會發現控制台沒有任何輸出,而logging.log文件中又多了兩行輸出:

2018-07-30 11:02:39,651 - simpleExample1 - ERROR - error message
2018-07-30 11:02:39,651 - simpleExample1 - CRITICAL - critical message
View Code

這是因為,當一個日志器沒有被設置任何處理器時,系統會去查找該日志器的上層日志器上所設置的日志處理器來處理日志記錄。simpleExample1在配置文件中沒有被定義,因此logging.getLogger(simpleExample1)這行代碼這是獲取了一個logger實例,並沒有給它設置任何處理器,但是它的上級日志器--root logger在配置文件中有定義且設置了一個FileHandler處理器,simpleExample1處理器最終通過這個FileHandler處理器將日志記錄輸出到logging.log文件中了。

6.3 使用字典配置信息和dictConfig()函數實現日志配置

Python 3.2中引入的一種新的配置日志記錄的方法--用字典來保存logging配置信息。這相對於上面所講的基於配置文件來保存logging配置信息的方式來說,功能更加強大,也更加靈活,因為我們可把很多的數據轉換成字典。比如,我們可以使用JSON格式的配置文件、YAML格式的配置文件,然后將它們填充到一個配置字典中;或者,我們也可以用Python代碼構建這個配置字典,或者通過socket接收pickled序列化后的配置信息。總之,你可以使用你的應用程序可以操作的任何方法來構建這個配置字典。

這個例子中,我們將使用YAML格式來完成與上面同樣的日志配置。

首先需要安裝PyYAML模塊:pip install PyYAM

import logging
import logging.config
import yaml

with open('logging.yml', 'r') as f_conf:
    dict_conf = yaml.load(f_conf)
logging.config.dictConfig(dict_conf)

logger = logging.getLogger('simpleExample')
logger.debug('debug message')
logger.info('info message')
logger.warn('warn message')
logger.error('error message')
logger.critical('critical message')
View Code

logging.yml配置文件的內容:

version: 1
formatters:
  simple:
    format: '%(asctime)s - %(name)s - %(levelname)s - %(message)s'
handlers:
  console:
    class: logging.StreamHandler
    level: DEBUG
    formatter: simple
    stream: ext://sys.stdout
  console_err:
    class: logging.StreamHandler
    level: ERROR
    formatter: simple
    stream: ext://sys.stderr
loggers:
  simpleExample:
    level: DEBUG
    handlers: [console]
    propagate: yes
root:
  level: DEBUG
  handlers: [console_err]
View Code

輸出結果:

2017-05-21 14:19:31,089 - simpleExample - DEBUG - debug message
2017-05-21 14:19:31,089 - simpleExample - INFO - info message
2017-05-21 14:19:31,089 - simpleExample - WARNING - warn message
2017-05-21 14:19:31,089 - simpleExample - ERROR - error message
2017-05-21 14:19:31,090 - simpleExample - CRITICAL - critical message
View Code

6.3.1 關於dictConfig()函數的說明:

該函數實際上是對configparser模塊的封裝。

函數定義:

該函數定義在loging.config模塊下:

logging.config.dictConfig(config)

該函數可以從一個字典對象中獲取日志配置信息,config參數就是這個字典對象。關於這個字典對象的內容規則會在下面進行描述。

 6.3.2 配置字典說明

無論是上面提到的配置文件,還是這里的配置字典,它們都要描述出日志配置所需要創建的各種對象以及這些對象之間的關聯關系。比如,可以先創建一個名額為“simple”的格式器formatter;然后創建一個名為“console”的處理器handler,並指定該handler輸出日志所使用的格式器為"simple";然后再創建一個日志器logger,並指定它所使用的處理器為"console"。

傳遞給dictConfig()函數的字典對象只能包含下面這些keys,其中version是必須指定的key,其它key都是可選項:

key名稱 描述
version 必選項,其值是一個整數值,表示配置格式的版本,當前唯一可用的值就是1
formatters 可選項,其值是一個字典對象,該字典對象每個元素的key為要定義的格式器名稱,value為格式器的配置信息組成的dict,如format和datefmt
filters 可選項,其值是一個字典對象,該字典對象每個元素的key為要定義的過濾器名稱,value為過濾器的配置信息組成的dict,如name
handlers 可選項,其值是一個字典對象,該字典對象每個元素的key為要定義的處理器名稱,value為處理器的配置信息組成的dcit,如class、level、formatter和filters,其中class為必選項,其它為可選項;其他配置信息將會傳遞給class所指定的處理器類的構造函數,如下面的handlers定義示例中的stream、filename、maxBytes和backupCount等
loggers 可選項,其值是一個字典對象,該字典對象每個元素的key為要定義的日志器名稱,value為日志器的配置信息組成的dcit,如level、handlers、filters 和 propagate(yes
root 可選項,這是root logger的配置信息,其值也是一個字典對象。除非在定義其它logger時明確指定propagate值為no,否則root logger定義的handlers都會被作用到其它logger上
incremental 可選項,默認值為False。該選項的意義在於,如果這里定義的對象已經存在,那么這里對這些對象的定義是否應用到已存在的對象上。值為False表示,已存在的對象將會被重新定義。
disable_existing_loggers 可選項,默認值為True。該選項用於指定是否禁用已存在的日志器loggers,如果incremental的值為True則該選項將會被忽略

 實例1:

配置自己的日志文件

"""
logging配置
"""

import os
import logging.config

# 定義三種日志輸出格式 開始

standard_format = '[%(asctime)s][%(threadName)s:%(thread)d][task_id:%(name)s][%(filename)s:%(lineno)d]' \
                  '[%(levelname)s][%(message)s]' #其中name為getlogger指定的名字

simple_format = '[%(levelname)s][%(asctime)s][%(filename)s:%(lineno)d]%(message)s'

id_simple_format = '[%(levelname)s][%(asctime)s] %(message)s'

# 定義日志輸出格式 結束

logfile_dir = os.path.dirname(os.path.abspath(__file__))  # log文件的目錄

logfile_name = 'all2.log'  # log文件名

# 如果不存在定義的日志目錄就創建一個
if not os.path.isdir(logfile_dir):
    os.mkdir(logfile_dir)

# log文件的全路徑
logfile_path = os.path.join(logfile_dir, logfile_name)

# log配置字典
LOGGING_DIC = {
    'version': 1,
    'disable_existing_loggers': False,
    'formatters': {
        'standard': {
            'format': standard_format
        },
        'simple': {
            'format': simple_format
        },
    },
    'filters': {},
    'handlers': {
        #打印到終端的日志
        'console': {
            'level': 'DEBUG',
            'class': 'logging.StreamHandler',  # 打印到屏幕
            'formatter': 'simple'
        },
        #打印到文件的日志,收集info及以上的日志
        'default': {
            'level': 'DEBUG',
            'class': 'logging.handlers.RotatingFileHandler',  # 保存到文件
            'formatter': 'standard',
            'filename': logfile_path,  # 日志文件
            'maxBytes': 1024*1024*5,  # 日志大小 5M
            'backupCount': 5,
            'encoding': 'utf-8',  # 日志文件的編碼,再也不用擔心中文log亂碼了
        },
    },
    'loggers': {
        #logging.getLogger(__name__)拿到的logger配置
        '': {
            'handlers': ['default', 'console'],  # 這里把上面定義的兩個handler都加上,即log數據既寫入文件又打印到屏幕
            'level': 'DEBUG',
            'propagate': True,  # 向上(更高level的logger)傳遞
        },
    },
}


def load_my_logging_cfg():
    logging.config.dictConfig(LOGGING_DIC)  # 導入上面定義的logging配置
    logger = logging.getLogger(__name__)  # 生成一個log實例
    logger.info('It works!')  # 記錄該文件的運行狀態

if __name__ == '__main__':
    load_my_logging_cfg()
my_logging.py

測試配置的日志文件

"""
MyLogging Test
"""

import time
import logging
import my_logging  # 導入自定義的logging配置

logger = logging.getLogger(__name__)  # 生成logger實例


def demo():
    logger.debug("start range... time:{}".format(time.time()))
    logger.info("中文測試開始。。。")
    for i in range(10):
        logger.debug("i:{}".format(i))
        time.sleep(0.2)
    else:
        logger.debug("over range... time:{}".format(time.time()))
    logger.info("中文測試結束。。。")

if __name__ == "__main__":
    my_logging.load_my_logging_cfg()  # 在你程序文件的入口加載自定義logging配置
    demo()
View Code

輸出到all2.log的內容:

[2018-07-30 11:46:29,020][MainThread:12340][task_id:my_logging][my_logging.py:75][INFO][It works!]
[2018-07-30 11:46:29,020][MainThread:12340][task_id:__main__][logging_test.py:13][DEBUG][start range... time:1532922389.0201607]
[2018-07-30 11:46:29,021][MainThread:12340][task_id:__main__][logging_test.py:14][INFO][中文測試開始。。。]
[2018-07-30 11:46:29,021][MainThread:12340][task_id:__main__][logging_test.py:16][DEBUG][i:0]
[2018-07-30 11:46:29,221][MainThread:12340][task_id:__main__][logging_test.py:16][DEBUG][i:1]
[2018-07-30 11:46:29,421][MainThread:12340][task_id:__main__][logging_test.py:16][DEBUG][i:2]
[2018-07-30 11:46:29,622][MainThread:12340][task_id:__main__][logging_test.py:16][DEBUG][i:3]
[2018-07-30 11:46:29,822][MainThread:12340][task_id:__main__][logging_test.py:16][DEBUG][i:4]
[2018-07-30 11:46:30,023][MainThread:12340][task_id:__main__][logging_test.py:16][DEBUG][i:5]
[2018-07-30 11:46:30,224][MainThread:12340][task_id:__main__][logging_test.py:16][DEBUG][i:6]
[2018-07-30 11:46:30,424][MainThread:12340][task_id:__main__][logging_test.py:16][DEBUG][i:7]
[2018-07-30 11:46:30,624][MainThread:12340][task_id:__main__][logging_test.py:16][DEBUG][i:8]
[2018-07-30 11:46:30,825][MainThread:12340][task_id:__main__][logging_test.py:16][DEBUG][i:9]
[2018-07-30 11:46:31,026][MainThread:12340][task_id:__main__][logging_test.py:19][DEBUG][over range... time:1532922391.026031]
[2018-07-30 11:46:31,026][MainThread:12340][task_id:__main__][logging_test.py:20][INFO][中文測試結束。。。]
View Code
 
        

注意:

1)有了上述方式我們的好處是:所有與logging模塊有關的配置都寫到字典中就可以了,更加清晰,方便管理

2)我們需要解決的問題是:
    1、從字典加載配置:logging.config.dictConfig(settings.LOGGING_DIC)
    2、拿到logger對象來產生日志
    logger對象都是配置到字典的loggers 鍵對應的子字典中的
    按照我們對logging模塊的理解,要想獲取某個東西都是通過名字,也就是key來獲取的
    於是我們要獲取不同的logger對象就是
    logger=logging.getLogger('loggers子字典的key名')
但問題是:如果我們想要不同logger名的logger對象都共用一段配置,那么肯定不能在loggers子字典中定義n個key  
我們的解決方式是,定義一個空的key
    'loggers': {
        '': {
            'handlers': ['default', 'console'], 
            'level': 'DEBUG',
            'propagate': True, 
        },

}

這樣我們再取logger對象時
logging.getLogger(__name__),不同的文件__name__不同,這保證了打印日志時標識信息不同,但是拿着該名字去loggers里找key名時卻發現找不到,於是默認使用key=''的配置
另外一個django的logging配置,和上面類似
#logging_config.py
LOGGING = {
    'version': 1,
    'disable_existing_loggers': False,
    'formatters': {
        'standard': {
            'format': '[%(asctime)s][%(threadName)s:%(thread)d][task_id:%(name)s][%(filename)s:%(lineno)d]'
                      '[%(levelname)s][%(message)s]'
        },
        'simple': {
            'format': '[%(levelname)s][%(asctime)s][%(filename)s:%(lineno)d]%(message)s'
        },
        'collect': {
            'format': '%(message)s'
        }
    },
    'filters': {
        'require_debug_true': {
            '()': 'django.utils.log.RequireDebugTrue',
        },
    },
    'handlers': {
        #打印到終端的日志
        'console': {
            'level': 'DEBUG',
            'filters': ['require_debug_true'],
            'class': 'logging.StreamHandler',
            'formatter': 'simple'
        },
        #打印到文件的日志,收集info及以上的日志
        'default': {
            'level': 'INFO',
            'class': 'logging.handlers.RotatingFileHandler',  # 保存到文件,自動切
            'filename': os.path.join(BASE_LOG_DIR, "xxx_info.log"),  # 日志文件
            'maxBytes': 1024 * 1024 * 5,  # 日志大小 5M
            'backupCount': 3,
            'formatter': 'standard',
            'encoding': 'utf-8',
        },
        #打印到文件的日志:收集錯誤及以上的日志
        'error': {
            'level': 'ERROR',
            'class': 'logging.handlers.RotatingFileHandler',  # 保存到文件,自動切
            'filename': os.path.join(BASE_LOG_DIR, "xxx_err.log"),  # 日志文件
            'maxBytes': 1024 * 1024 * 5,  # 日志大小 5M
            'backupCount': 5,
            'formatter': 'standard',
            'encoding': 'utf-8',
        },
        #打印到文件的日志
        'collect': {
            'level': 'INFO',
            'class': 'logging.handlers.RotatingFileHandler',  # 保存到文件,自動切
            'filename': os.path.join(BASE_LOG_DIR, "xxx_collect.log"),
            'maxBytes': 1024 * 1024 * 5,  # 日志大小 5M
            'backupCount': 5,
            'formatter': 'collect',
            'encoding': "utf-8"
        }
    },
    'loggers': {
        #logging.getLogger(__name__)拿到的logger配置
        '': {
            'handlers': ['default', 'console', 'error'],
            'level': 'DEBUG',
            'propagate': True,
        },
        #logging.getLogger('collect')拿到的logger配置
        'collect': {
            'handlers': ['console', 'collect'],
            'level': 'INFO',
        }
    },
}


# -----------
# 用法:拿到倆個logger

logger = logging.getLogger(__name__) #線上正常的日志
collect_logger = logging.getLogger("collect") #領導說,需要為領導們單獨定制領導們看的日志
Django的logging配置

 

 

 

 

 

 

 

>>>>>>>待續


免責聲明!

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



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