2-django進階之日誌功能

Django中的日誌功能

1、日誌相關概念

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

1.日誌的做用

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

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

若是應用的日誌信息足夠詳細和豐富,還能夠用來作用戶行爲分析,如:分析用戶的操做行爲、類型洗好、地域分佈以及其它更多的信息,由此能夠實現改進業務、提升商業利益。python

2.日誌的等級

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

  • 做爲開發人員,在開發一個應用程序時須要什麼日誌信息?在應用程序正式上線後須要什麼日誌信息?
  • 做爲應用運維人員,在部署開發環境時須要什麼日誌信息?在部署生產環境時須要什麼日誌信息?

在軟件開發階段或部署開發環境時,爲了儘量詳細的查看應用程序的運行狀態來保證上線後的穩定性,咱們可能須要把該應用程序全部的運行日誌所有記錄下來進行分析,這是很是耗費機器性能的。當應用程序正式發佈或在生產環境部署應用程序時,咱們一般只須要記錄應用程序的異常信息、錯誤信息等,這樣既能夠減少服務器的I/O壓力,也能夠避免咱們在排查故障時被淹沒在日誌的海洋裏。那麼,怎樣才能在不改動應用程序代碼的狀況下實如今不一樣的環境記錄不一樣詳細程度的日誌呢?這就是日誌等級的做用了,咱們經過配置文件指定咱們須要的日誌等級就能夠了。django

不一樣的應用程序所定義的日誌等級可能會有所差異,分的詳細點的會包含如下幾個等級:緩存

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

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

本節開始問題提到過,一條日誌信息對應的是一個事件的發生,而一個事件一般須要包括如下幾個內容:服務器

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

上面這些都是一條日誌記錄中可能包含的字段信息,固然還能夠包括一些其餘信息,如進程ID、進程名稱、線程ID、線程名稱等。日誌格式就是用來定義一條日誌記錄中包含那些字段的,且日誌格式一般都是能夠自定義的。網絡

說明:

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

4.日誌功能的實現

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

2、logging模塊簡介

logging模塊定義的函數和類爲應用程序和庫的開發實現了一個靈活的事件日誌系統。logging模塊是Python的一個標準庫模塊,由標準庫模塊提供日誌記錄API的關鍵好處是全部Python模塊均可以使用這個日誌記錄功能。因此,你的應用日誌能夠將你本身的日誌信息與來自第三方模塊的信息整合起來。

1. logging模塊的日誌級別

logging模塊默認定義瞭如下幾個日誌等級,它容許開發人員自定義其餘日誌級別,可是這是不被推薦的,尤爲是在開發供別人使用的庫時,由於這會致使日誌級別的混亂。

級別 描述
CRITICAL/FATAL 50 關鍵錯誤/消息
ERROR 40 錯誤
WARNING 30 警告消息
INFO 20 通知消息
DEBUG 10 調試
NOTSET 0 無級別

若是設置日誌級別爲WARNING,則經過日誌記錄器則輸入WARNING級別如下的信息是不會被處理的。

開發應用程序或部署開發環境時,可使用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.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模塊提供的模塊級別的那些函數實際上也是經過這幾個組件的相關實現類來記錄日誌的,只是在建立這些類的實例時設置了一些默認值。

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


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

  • 能夠經過logging模塊定義的模塊級別的方法去完成簡單的日誌記錄
  • 只有級別大於或等於日誌記錄器指定級別的日誌記錄纔會被輸出,小於該級別的日誌記錄將會被丟棄。

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

也能夠這樣寫:

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

輸出結果:

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

2. 那麼問題來了

問題1:爲何前面兩條日誌沒有被打印出來?

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

問題2:打印出來的日誌信息中各字段表示什麼意思?爲何會這樣輸出?

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

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

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

"%(levelname)s:%(name)s:%(message)s"

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異常。

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 線程名稱

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

輸出結果:

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.

全部等級的日誌信息都被輸出了,說明配置生效了。

在配置日誌器日誌級別的基礎上,在配置下日誌輸出目標文件和日誌格式
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.")

此時會發現控制檯中已經沒有輸出日誌內容了,可是在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.
在上面的基礎上,咱們再來設置下日期/時間格式
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.")

此時會在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.

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

輸出結果:

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

4、logging模塊日誌流處理流程

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

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

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

3. logging日誌流處理流程

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

clipboard.png

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

  • 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的日誌等級並不會對該日誌消息進行等級過濾。

5、使用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. 代碼實現

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')

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

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

轉自https://www.cnblogs.com/yyds/...

Django中使用日誌

python的日誌簡單用法

# 設置日誌等級,不設置默認爲WARNING ,logging.basicConfig(level=logging.DEBUG)中也能夠設置
# logging.getLogger()方法將會獲得一個Logger對象,默認的root記錄器。
# Logger.setLevel() 設置日誌器將會處理的日誌消息的最低嚴重級別
logging.getLogger().setLevel(logging.INFO)
formatter = '%(asctime)s: %(filename)s/%(funcName)s at %(lineno)s->%(message)s'

# 配置日誌的信息與處理器FileHandler,filename 要指定日誌輸出的文件名
logging.basicConfig(format=formatter,
                    datefmt='%Y-%m-%d %H:%M:%S',
                    filename='art.log',
                    filemode='a')

logging.warning('--當前頁面要被緩存5秒---')

例子1:

在myapps新建一個log_包,在init.py中配置

import logging

logging.getLogger().setLevel(logging.WARNING)

# 能夠自定義日誌的格式%(格式名)s,在日誌輸入消息時,使用extra = {'格式名','消息值'}
logging.basicConfig(format='%(asctime)s in %(filename)s at %(lineno)d of user %(username)s :%(message)s',
                    datefmt='%Y-%m-%d %H:%M:%S',filename='t.log', filemode='a')

在 myapps下的art這個app下的tests.py中寫

from django.test import TestCase
import log_
import logging
# Create your tests here.

class TestLogger(TestCase):
    def testLog(self):
        self.assertEqual('1','1')
        logging.info('測試 1=1 成功!', extra={'username':'D'})

    def testErrorLog(self):
        try:
            self.assertEqual('1','2')
            logging.info('測試 1=2 成功', extra={'username':'D'})
        except:
            logging.error('測試 1=2 失敗!',extra={'username':'D'})

t.log

2018-08-17 09:49:10 in tests.py at 16 of user D :測試 1=2 失敗!

在Django的配置文件settings.py 中加入以下LOGGING配置

#########################
## Django Logging  BEGIN
#########################

#LOGGING_DIR 日誌文件存放目錄
LOGGING_DIR = "/home/xxt/logs"
if not os.path.exists(LOGGING_DIR):
    os.mkdir(LOGGING_DIR)

import logging

LOGGING = {
    'version': 1,
    'disable_existing_loggers': False,
    'formatters': {
        'standard': {
            'format': '[%(levelname)s][%(asctime)s][%(filename)s][%(funcName)s][%(lineno)d] > %(message)s'
        },
        'simple': {
            'format': '[%(levelname)s]> %(message)s',
            'datefmt': '%Y-%m-%d %H:%M:%S'
        },
    },
    'filters': {
        'require_debug_true': {
            '()': 'django.utils.log.RequireDebugTrue',
        },
    },
    'handlers': {
        'console': {
            'level': 'DEBUG',
            'filters': ['require_debug_true'],
            'class': 'logging.StreamHandler',
            'formatter': 'simple'
        },
        'file_handler': {
             'level': 'INFO',
             'class': 'logging.handlers.TimedRotatingFileHandler',
             'filename': '%s/django.log' % LOGGING_DIR,
             'formatter':'standard',
             'encoding': 'utf-8'
        }, # 用於文件輸出
        'mail_admins': {
            'level': 'ERROR',
            'class': 'django.utils.log.AdminEmailHandler',
             'formatter':'standard'
        },
    },
    'loggers': {
        'mdjango': {
            # 一個記錄器中可使用多個處理器
            'handlers': ['console','file_handler'],
            'level':'DEBUG',
            'propagate': True,
        },
        'django.request': {
            'handlers': ['mail_admins'],
            'level': 'ERROR',
            'propagate': False,
        },
    }
}
    
logger = logging.getLogger("mdjango")

#########################
## Django Logging  END
#########################

setting.py中的例子

# settings.py
import logging

LOGGING  ={
    'version':1,
    'disable_existing_loggers':False,
    'formatters':{#格式化
        'simple':{
            'format':'[%(asctime)s] %(filename)s %(lineno)d-->%(message)s',
            'datefmt':'%Y-%m-%d %H:%M:%S'
                },
        'console':{
             'format':'[%(asctime)s][%(levelname)s] %(pathname)s %(lineno)d -> %(message)s',
            'datefmt':'%Y-%m-%d %H:%M:%S'
                }
    },
    'handlers':{#處理器
        'console':{
            'level':'INFO',
            'class':'logging.StreamHandler',
            'formatter':'console'
        },
        'fileHandler':{
            'level':'INFO',
            'class':'logging.handlers.TimedRotatingFileHandler',
            'formatter':'simple',
            'filename':'art.log'
        }

    },
    'loggers':{#記錄器
        'mdjango':{
            'handlers':['console','fileHandler'],
            'level':'INFO',
            'propagate':False
        }

    }
}

tests.py

from django.test import TestCase
import logging
logger = logging.getLogger('mdjango')
# logger.setLevel(logging.INFO),這裏不須要寫,由於setting.py中寫過了,不然要寫

class TestLogger(TestCase):
    def testDjangoLog(self):
        self.assertIn(1, [1,2,3])
        #logging.getLogger('mdjango').info('1 在 [1,2,3]列表中 測試成功') 
        logger.info('1 在 [1,2,3]列表中 測試成功')

art.log

[2018-08-17 10:37:47] tests.py 20-->1 在 [1,2,3]列表中 測試成功

控制檯輸出

[2018-08-17 10:37:47][INFO] F:\PycharmProjects\django\HArtPro\myapps\art\tests.py 20 -> 1 在 [1,2,3]列表中 測試成功

最新日誌內容存入: django.log

前一天:django.log.2018-05-29

前前一天:django.log.2018-05-28

django.log.2018-05-22

在具體的業務邏輯代碼中加入日誌記錄

from Project.settings import  logger
logger.info("IndexHandler request Handler begin")
logger.debug('query total:' + str(total))

觀察日誌打印狀況。

通常python使用日誌功能(非django框架)

import logging

def logging_init():
   app_name = "AppName"
   log_file_name = "myapp_test.log"
   logger = logging.getLogger(app_name)
   format_str = "[%(asctime)s][%(levelname)s]> %(message)s"
   formatter = logging.Formatter(format_str)
   file_handler = logging.FileHandler(log_file_name)
   file_handler.setFormatter(formatter)
   logger.addHandler(file_handler)
   logger.setLevel(logging.INFO)
   return logger

單例模式

import logging

class Singleton(object):
   _instance = None

   def __new__(cls, *args, **kwargs):
      if not cls._instance:
         cls._instance = super(Singleton, cls).__new__(cls,
                                    *args, **kwargs)
      return cls._instance

app_name = "appName"
log_file = "test.log"

class SingletonLogger(Singleton):
   def __init__(self):
      super(SingletonLogger, self).__init__()
      self.logger = logging.getLogger(app_name)
      format_str = "[%(asctime)s][%(levelname)s]> %(message)s"
      formatter = logging.Formatter(format_str)
      file_handler = logging.FileHandler(log_file)
      file_handler.setFormatter(formatter)
      self.logger.addHandler(file_handler)
      self.logger.setLevel(logging.INFO)

   def debug(self, data):
      self.logger.debug(data)

   def info(self, data):
      self.logger.info(data)

   def warning(self, data):
      self.logger.warning(data)


   def error(self, data):
      self.logger.error(data)

 #執行函數       
def test_log():
   logger = SingletonLogger()
   #output the log msg
   logger.debug("this is the debug message")
   logger.info("this is the info message")
   logger.warning("this is the warning message")
   logger.error("this is the error message")
相關文章
相關標籤/搜索