[TOC]
## **一、日志相關概念**
日志是一種可以追蹤某些軟件運行時所發生事件的方法。軟件開發人員可以向他們的代碼中調用日志記錄相關的方法來表明發生了某些事情。一個事件可以用一個可包含可選變量數據的消息來描述。此外,事件也有重要性的概念,這個重要性也可以被稱為嚴重性級別(level)。
<br />
### **1.日志的作用**
通過log的分析,可以方便用戶了解系統或軟件、應用的運行情況;如果你的應用log足夠豐富,也可以分析以往用戶的操作行為、類型喜好、地域分布或其他更多信息;如果一個應用的log同時也分了多個級別,那么可以很輕易地分析得到該應用的健康狀況,及時發現問題并快速定位、解決問題,補救損失。
簡單來講就是,我們通過記錄和分析日志可以了解一個系統或軟件程序運行情況是否正常,也可以在應用程序出現故障時快速定位問題。比如,做運維的同學,在接收到報警或各種問題反饋后,進行問題排查時通常都會先去看各種日志,大部分問題都可以在日志中找到答案。再比如,做開發的同學,可以通過IDE控制臺上輸出的各種日志進行程序調試。對于運維老司機或者有經驗的開發人員,可以快速的通過日志定位到問題的根源。可見,日志的重要性不可小覷。日志的作用可以簡單總結為以下3點:
* 程序調試
* 了解軟件程序運行情況,是否正常
* 軟件程序運行故障分析與問題定位
如果應用的日志信息足夠詳細和豐富,還可以用來做用戶行為分析,如:分析用戶的操作行為、類型洗好、地域分布以及其它更多的信息,由此可以實現改進業務、提高商業利益。
<br />
### **2.日志的等級**
我們先來思考下下面的兩個問題:
* 作為開發人員,在開發一個應用程序時需要什么日志信息?在應用程序正式上線后需要什么日志信息?
* 作為應用運維人員,在部署開發環境時需要什么日志信息?在部署生產環境時需要什么日志信息?
在軟件開發階段或部署開發環境時,為了盡可能詳細的查看應用程序的運行狀態來保證上線后的穩定性,我們可能需要把該應用程序所有的運行日志全部記錄下來進行分析,這是非常耗費機器性能的。當應用程序正式發布或在生產環境部署應用程序時,我們通常只需要記錄應用程序的異常信息、錯誤信息等,這樣既可以減小服務器的I/O壓力,也可以避免我們在排查故障時被淹沒在日志的海洋里。那么,怎樣才能在不改動應用程序代碼的情況下實現在不同的環境記錄不同詳細程度的日志呢?這就是日志等級的作用了,我們通過配置文件指定我們需要的日志等級就可以了。
不同的應用程序所定義的日志等級可能會有所差別,分的詳細點的會包含以下幾個等級:
* DEBUG
* INFO
* NOTICE
* WARNING
* ERROR
* CRITICAL
* ALERT
* EMERGENCY
<br />
### **3.日志字段信息與日志格式**
本節開始問題提到過,一條日志信息對應的是一個事件的發生,而一個事件通常需要包括以下幾個內容:
* 事件發生時間
* 事件發生位置
* 事件的嚴重程度--日志級別
* 事件內容
上面這些都是一條日志記錄中可能包含的字段信息,當然還可以包括一些其他信息,如進程ID、進程名稱、線程ID、線程名稱等。日志格式就是用來定義一條日志記錄中包含那些字段的,且日志格式通常都是可以自定義的。
> ***說明:***
>
> 輸出一條日志時,日志內容和日志級別是需要開發人員明確指定的。對于而其它字段信息,只需要是否顯示在日志中就可以了。
<br />
### **4.日志功能的實現**
幾乎所有開發語言都會內置日志相關功能,或者會有比較優秀的第三方庫來提供日志操作功能,比如:log4j,log4php等。它們功能強大、使用簡單。Python自身也提供了一個用于記錄日志的標準庫模塊--logging。
<br />
<br />
## **二、logging模塊簡介**
logging模塊定義的函數和類為應用程序和庫的開發實現了一個靈活的事件日志系統。logging模塊是Python的一個標準庫模塊,由標準庫模塊提供日志記錄API的關鍵好處是所有Python模塊都可以使用這個日志記錄功能。所以,你的應用日志可以將你自己的日志信息與來自第三方模塊的信息整合起來。
<br />
### **1. 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模塊也可以指定日志記錄器的日志級別,只有級別大于或等于該指定日志級別的日志記錄才會被輸出,小于該等級的日志記錄將會被丟棄。
<br />
### **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)`函數用于指定“要記錄的日志級別”、“日志格式”、“日志輸出位置”、“日志文件的打開模式”等信息,其他幾個都是用于記錄各個級別日志的函數。
<br />
#### **logging模塊的四大組件**
| 組件 | 說明 |
| --- | --- |
| loggers | 提供應用程序代碼直接使用的接口 |
| handlers | 用于將日志記錄發送到指定的目的位置 |
| filters | 提供更細粒度的日志過濾功能,用于決定哪些日志記錄將會被輸出(其它的日志記錄將會被忽略) |
| formatters | 用于控制日志信息的最終輸出格式 |
> ***說明:***logging模塊提供的模塊級別的那些函數實際上也是通過這幾個組件的相關實現類來記錄日志的,只是在創建這些類的實例時設置了一些默認值。
<br />
<br />
## **三、使用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.
~~~
<br />
### **2. 那么問題來了**
**問題1:為什么前面兩條日志沒有被打印出來?**
這是因為logging模塊提供的日志記錄函數所使用的日志器設置的日志級別是`WARNING`,因此只有`WARNING`級別的日志記錄以及大于它的`ERROR`和`CRITICAL`級別的日志記錄被輸出了,而小于它的`DEBUG`和`INFO`級別的日志記錄被丟棄了。
<br />
**問題2:打印出來的日志信息中各字段表示什么意思?為什么會這樣輸出**
上面輸出結果中每行日志記錄的各個字段含義分別是:
~~~
日志級別:日志器名稱:日志內容
~~~
之所以會這樣輸出,是因為logging模塊提供的日志記錄函數所使用的日志器設置的日志格式默認是BASIC\_FORMAT,其值為:
~~~
"%(levelname)s:%(name)s:%(message)s"
~~~
<br />
**問題3:如果將日志記錄輸出到文件中,而不是打印到控制臺?**
因為在logging模塊提供的日志記錄函數所使用的日志器設置的處理器所指定的日志輸出位置默認為:
`sys.stderr`。
<br />
**問題4:我是怎么知道這些的?**
查看這些日志記錄函數的實現代碼,可以發現:當我們沒有提供任何配置信息的時候,這些函數都會去調用`logging.basicConfig(**kwargs)`方法,且不會向該方法傳遞任何參數。繼續查看`basicConfig()`方法的代碼就可以找到上面這些問題的答案了。
<br />
**問題5:怎么修改這些默認設置呢?**
其實很簡單,在我們調用上面這些日志記錄函數之前,手動調用一下basicConfig()方法,把我們想設置的內容以參數的形式傳遞進去就可以了。
<br />
### **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異常。 |
<br />
### **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 | 線程名稱 |
<br />
### **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.
~~~
掌握了上面的內容之后,已經能夠滿足我們平時開發中需要的日志記錄功能。
<br />
### **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'})
~~~
<br />
## **四、logging模塊日志流處理流程**
在介紹logging模塊的高級用法之前,很有必要對logging模塊所包含的重要組件以及其工作流程做個全面、簡要的介紹,這有助于我們更好的理解我們所寫的代碼(將會觸發什么樣的操作)。
<br />
### **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)對要輸出的日志內容做過濾和格式化等處理操作。
<br />
### **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數量等。
<br />
### **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的日志等級并不會對該日志消息進行等級過濾。
<br />
## **五、使用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
~~~
<br />
## **六、配置logging的幾種方式**
作為開發者,我們可以通過以下3中方式來配置logging:
* 1)使用Python代碼顯式的創建loggers, handlers和formatters并分別調用它們的配置函數;
* 2)創建一個日志配置文件,然后使用`fileConfig()`函數來讀取該文件的內容;
* 3)創建一個包含配置信息的dict,然后把它傳遞個`dictConfig()`函數;
具體說明請參考另一篇博文[《python之配置日志的幾種方式》](http://www.cnblogs.com/yyds/p/6885182.html)
<br />
## **七、向日志輸出中添加上下文信息**
除了傳遞給日志記錄函數的參數外,有時候我們還想在日志輸出中包含一些額外的上下文信息。比如,在一個網絡應用中,可能希望在日志中記錄客戶端的特定信息,如:遠程客戶端的IP地址和用戶名。這里我們來介紹以下幾種實現方式:
* 通過向日志記錄函數傳遞一個`extra`參數引入上下文信息
* 使用LoggerAdapters引入上下文信息
* 使用Filters引入上下文信息
具體說明請參考另一篇博文[《Python之向日志輸出中添加上下文信息》](http://www.cnblogs.com/yyds/p/6897964.html)
關于Python logging的更多高級用法,請參考文檔[>](https://docs.python.org/3.5/howto/logging-cookbook.html)。
- Linux
- Linux 文件權限概念
- 重點總結
- Linux 文件與目錄管理
- 2.1 文件與目錄管理
- 2.2 文件內容查閱
- 文件與文件系統的壓縮,打包與備份
- 3.1 Linux 系統常見的壓縮指令
- 3.2 打包指令: tar
- vi/vim 程序編輯器
- 4.1 vi 的使用
- 4.2 vim編輯器刪除一行或者多行內容
- 進程管理
- 5.1 常用命令使用技巧
- 5.2 進程管理
- 系統服務 (daemons)
- 6.1 通過 systemctl 管理服務
- Linux 系統目錄結構
- Linux yum命令
- linux系統查看、修改、更新系統時間(自動同步網絡時間)
- top linux下的任務管理器
- Linux基本配置
- CentOS7開啟防火墻
- CentOS 使用yum安裝 pip
- strace 命令
- Linux下設置固定IP地址
- 查看Linux磁盤及內存占用情況
- Mysql
- 關系數據庫概述
- 數據庫技術
- 數據庫基礎語句
- 查詢語句(--重點--)
- 約束
- 嵌套查詢(子查詢)
- 表emp
- MySQL數據庫練習
- 01.MySQL數據庫練習數據
- 02.MySQL數據庫練習題目
- 03.MySQL數據庫練習-答案
- Mysql遠程連接數據庫
- Python
- python基礎
- Python3中字符串、列表、數組的轉換方法
- python字符串
- python安裝、pip基本用法、變量、輸入輸出、流程控制、循環
- 運算符及優先級、數據類型及常用操作、深淺拷貝
- 虛擬環境(virtualenv)
- 網絡編程
- TCP/IP簡介
- TCP編程
- UDP編程
- 進程和線程
- 訪問數據庫
- 使用SQLite
- 使用MySQL
- Web開發
- HTML簡介
- Python之日志處理(logging模塊)
- 函數式編程
- 高階函數
- python報錯解決
- 啟動Python時報“ImportError: No module named site”錯誤
- python實例
- 01- 用python解決數學題
- 02- 冒泡排序
- 03- 郵件發送(smtplib)
- Django
- 01 Web應用
- Django3.2 教程
- Django簡介
- Django環境安裝
- 第一個Django應用
- Part 1:請求與響應
- Part 2:模型與后臺
- Part 3:視圖和模板
- Part 4:表單和類視圖
- Part 5:測試
- Part 6:靜態文件
- Part 7:自定義admin
- 第一章:模型層
- 實戰一:基于Django3.2可重用登錄與注冊系統
- 1. 搭建項目環境
- 2. 設計數據模型
- 3. admin后臺
- 4. url路由和視圖
- 5. 前端頁面設計
- 6. 登錄視圖
- 7. Django表單
- 8. 圖片驗證碼
- 9. session會話
- 10. 注冊視圖
- 實戰二:Django3.2之CMDB資產管理系統
- 1.項目需求分析
- 2.模型設計
- 3.數據收集客戶端
- 4.收集Windows數據
- 5.Linux下收集數據
- 6.新資產待審批區
- 7.審批新資產
- django 快速搭建blog
- imooc-Django全棧項目開發實戰
- redis
- 1.1 Redis簡介
- 1.2 安裝
- 1.3 配置
- 1.4 服務端和客戶端命令
- 1.5 Redis命令
- 1.5.1 Redis命令
- 1.5.2 鍵(Key)
- 1.5.3 字符串(string)
- 1.5.4 哈希(Hash)
- 1.5.5 列表(list)
- 1.5.6 集合(set)
- 1.5.7 有序集合(sorted set)
- Windows
- Win10安裝Ubuntu子系統
- win10遠程桌面身份驗證錯誤,要求的函數不受支持
- hm軟件測試
- 02 linux基本命令
- Linux終端命令格式
- Linux基本命令(一)
- Linux基本命令(二)
- 02 數據庫
- 數據庫簡介
- 基本概念
- Navicat使用
- SQL語言
- 高級
- 03 深入了解軟件測試
- day01
- 04 python基礎
- 語言基礎
- 程序中的變量
- 程序的輸出
- 程序中的運算符
- 數據類型基礎
- 數據序列
- 數據類型分類
- 字符串
- 列表
- 元組
- 字典
- 列表與元組的區別詳解
- 函數
- 案例綜合應用
- 列表推導式
- 名片管理系統
- 文件操作
- 面向對象基礎(一)
- 面向對象基礎(二)
- 異常、模塊
- 05 web自動化測試
- Day01
- Day02
- Day03
- Day04
- Day05
- Day06
- Day07
- Day08
- 06 接口自動化測試
- 軟件測試面試大全2020
- 第一章 測試理論
- 軟件測試面試
- 一、軟件基礎知識
- 二、網絡基礎知識
- 三、數據庫
- SQL學生表 — 1
- SQL學生表 — 2
- SQL查詢 — 3
- SQL經典面試題 — 4
- 四、linux
- a. linux常用命令
- 五、自動化測試
- 自動化測試
- python 筆試題
- selenium面試題
- 如何判斷一個頁面上元素是否存在?
- 如何提高腳本的穩定性?
- 如何定位動態元素?
- 如何通過子元素定位父元素?
- 如果截取某一個元素的圖片,不要截取全部圖片
- 平常遇到過哪些問題?如何解決的
- 一個元素明明定位到了,點擊無效(也沒報錯),如果解決?
- selenium中隱藏元素如何定位?(hidden、display: none)
- 六、接口測試
- 接口測試常規面試題
- 接口自動化面試題
- json和字典dict的區別?
- 測試的數據你放在哪?
- 什么是數據驅動,如何參數化?
- 下個接口請求參數依賴上個接口的返回數據
- 依賴于登錄的接口如何處理?
- 依賴第三方的接口如何處理
- 不可逆的操作,如何處理,比如刪除一個訂單這種接口如何測試
- 接口產生的垃圾數據如何清理
- 一個訂單的幾種狀態如何全部測到,如:未處理,處理中,處理失敗,處理成功
- python如何連接數據庫操作?
- 七、App測試
- 什么是activity?
- Activity生命周期?
- Android四大組件
- app測試和web測試有什么區別?
- android和ios測試區別?
- app出現ANR,是什么原因導致的?
- App出現crash原因有哪些?
- app對于不穩定偶然出現anr和crash時候你是怎么處理的?
- app的日志如何抓取?
- logcat查看日志步驟
- 你平常會看日志嗎, 一般會出現哪些異常
- 抓包工具
- fiddler
- Wireshark
- 安全/滲透測試
- 安全性測試都包含哪些內容?
- 開放性思維題
- 面試題
- 字節測試面試
- 一、計算機網絡
- 二、操作系統
- 三、數據庫
- 四、數據結構與算法
- 五、Python
- 六、Linux
- 七、測試用例
- 八、智力/場景題
- 九、開放性問題
- python3_收集100+練習題(面試題)
- python3_100道題目答案
- 接口測試
- 接口測試實例_01
- python+requests接口自動化測試框架實例詳解
- 性能測試
- 性能測試流程
- 性能測試面試題
- 如何編寫性能測試場景用例
- 性能測試:TPS和QPS的區別
- jmeter
- jmeter安裝配置教程
- Jmeter性能測試 入門
- PyCharm
- 快捷工具
- 1-MeterSphere
- 一、安裝和升級
- 2- MobaXterm 教程
- 3-fiddler抓包
- 4-Xshell
- Xshell的安裝和使用
- Xshell遠程連接失敗怎么解決
- 5-Vmware
- Vmware提示以獨占方式鎖定此配置文件失敗
- Windows10徹底卸載VMWare虛擬機步驟
- VM ware無法關機,虛擬機繁忙
- VMware虛擬機下載與安裝
- 解決VM 與 Device/Credential Guard 不兼容。在禁用 Device/Credential Guard 后,可以運行 VM 的方法
- VMware虛擬機鏡像克隆與導入
- 6-WPS
- 1.WPS文檔里的批注怎么刪除
- 2.wps表格中設置圖表的坐標
- 3. wps快速繪制數學交集圖
- 7-MongoDB
- Win10安裝配置MongoDB
- Navicat 15.x for MongoDB安裝破解教程
- Apache
- apache層的賬戶權限控制,以及apache黑名單白名單過濾功能
- HTTP / HTTPS協議
- HTTP協議詳解
- 代理
- 狀態碼詳解
- HTTPS詳解
- Selenium3+python3
- (A) selenium
- selenium自動化環境搭建(Windows10)
- 火狐firebug和firepath插件安裝方法(最新)
- 元素定位工具和方法
- Selenium3+python3自動化
- 新手學習selenium路線圖---學前篇
- 1-操作瀏覽器基本方法
- 2-八種元素定位方法
- 3-CSS定位語法
- 4-登錄案例
- 5-定位一組元素find_elements
- 6-操作元素(鍵盤和鼠標事件)
- 7-多窗口、句柄(handle)
- 8-iframe
- 9-select下拉框
- 10-alert\confirm\prompt
- 11-JS處理滾動條
- 12-單選框和復選框(radiobox、checkbox)
- 13-js處理日歷控件(修改readonly屬性)
- 14-js處理內嵌div滾動條
- 15-table定位
- 16-js處理多窗口
- 17-文件上傳(send_keys)
- 18-獲取百度輸入聯想詞
- 19-處理瀏覽器彈窗
- 20-獲取元素屬性
- 21-判斷元素存在
- 22-爬頁面源碼(page_source)
- 23-顯式等待(WebDriverWait)
- 24-關于面試的題
- 25-cookie相關操作
- 26-判斷元素(expected_conditions)
- 27-判斷title(title_is)
- 28-元素定位參數化(find_element)
- 29-18種定位方法(find_elements)
- 30- js解決click失效問題
- 31- 判斷彈出框存在(alert_is_present)
- 32- 登錄方法(參數化)
- 33- 判斷文本(text_to_be_present_in_element)
- 34- unittest簡介
- 35- unittest執行順序
- 36- unittest之裝飾器(@classmethod)
- 37- unittest之斷言(assert)
- 38- 捕獲異常(NoSuchElementException)
- 39- 讀取Excel數據(xlrd)
- 40- 數據驅動(ddt)
- 41- 異常后截圖(screenshot)
- 42- jenkins持續集成環境搭建
- 43- Pycharm上python和unittest兩種運行方式
- 44- 定位的坑:class屬性有空格
- 45- 只截某個元素的圖
- 46- unittest多線程執行用例
- 47- unittest多線程生成報告(BeautifulReport)
- 48- 多線程啟動多個不同瀏覽器
- (B) python3+selenium3實現web UI功能自動化測試框架
- (C) selenium3常見報錯處理
- 書籍
- (D)Selenium3自動化測試實戰--基于Python語
- 第4章 WebDriver API
- 4.1 從定位元素開始
- 4.2 控制瀏覽器
- 4.3 WebDriver 中的常用方法
- 4.4 鼠標操作
- 4.5 鍵盤操作
- 4.6 獲得驗證信息
- 4.7 設置元素等待
- 4.8 定位一組元素
- 4.9 多表單切換
- 4.10 多窗口切換
- 4.11 警告框處理
- 4.12 下拉框處理
- 4.13 上傳文件
- 4.14 下載文件
- 4.15 操作cookie
- 4.16 調用JavaScript
- 4.17 處理HTML5視頻播放
- 4.18 滑動解鎖
- 4.19 窗口截圖
- 第5章 自動化測試模型
- 5.3 模塊化與參數化
- 5.4 讀取數據文件
- 第6章 unittest單元測試框架
- 6.1 認識unittest