1、日志的相關概念
日志是一種可以追蹤某些軟件運行時所發生事件的方法。軟件開發人員可以向他們的代碼中調用日志記錄相關的方法來表明發生了某些事情。一個事件可以用一個可包含可選變量數據的消息來描述。此外,事件也有重要性的概念,這個重要性也可以被稱為嚴重性級別(level)
日志的作用
通過log的分析,克以方便用戶了解系統軟件,和應用的運行情況,如果你的log足夠豐富,也可以分析出用戶的行為,類型喜好,地域分布等等。
若果一個應用的log同時也分了多個級別,那么可以很輕易的分析出應用的健康情況,及時發線問題並快速定位問題點,彌補損失。
簡單的來說 我們可以通過記錄和分析日志 可以了解一個系統或軟件的運行情況是否正常,也可以快速定位問題點。
比如:
運維的同學在接收警報或個種問題反饋后 ,進行問題排查都會先看各種日志,大部分問題都是可以在日志中找到答案。
開發同學,可以通過ide控制台數出各種日志進行調試。
對於老運維或者有經驗的開發人員,可以快速的通過日志定位到問題的根源。可見,日志的重要性不可小覷。日志的作用可以簡單總結為以下3點:
- 程序調試
- 了解軟件運行情況,是否正常
- 應用出現故障快速定位
如果應用的日志信息足夠詳細和豐富,還可以用來做用戶行為分析,如:分析用戶的操作行為、類型洗好、地域分布以及其它更多的信息,由此可以實現改進業務、提高商業利益。
日志的等級
我們先來思考下面的兩個問題:
- 作為開發人員,在開發應用程序的時候需要什么日志信息,應用程序上線之后需要什么日志信息
- 作為運維人員,在部署開發環境需要什么日志信息,在部署生產環境之后需要什么日志信息
在軟件開發階段或部署開發環境時,為了盡可能詳細的查看應用程序的運行狀態來保證上線后的穩定性,我們可能需要把該應用程序所有的運行日志全部記錄下來進行分析,這是非常耗費機器性能的。當應用程序正式發布或在生產環境部署應用程序時,我們通常只需要記錄應用程序的異常信息、錯誤信息等,這樣既可以減小服務器的I/O壓力,也可以避免我們在排查故障時被淹沒在日志的海洋里。那么,怎樣才能在不改動應用程序代碼的情況下實現在不同的環境記錄不同詳細程度的日志呢?這就是日志等級的作用了,我們通過配置文件指定我們需要的日志等級就可以了。
不同的應用程序所定義的日志等級可能會有所差別,分別詳細點的包含一下幾個等級:
一般用的時候會一起討論日志級別的顏色。
詳細作用:https://www.infoq.cn/article/five-levels-of-logging
ALL 最低等級的,用於打開所有日志記錄。
TRACE designates finer-grained informational events than the DEBUG.Since:1.2.12,很低的日志級別,一般不會使用。
DEBUG 指出細粒度信息事件對調試應用程序是非常有幫助的,主要用於開發過程中打印一些運行信息。
INFO 消息在粗粒度級別上突出強調應用程序的運行過程。打印一些你感興趣的或者重要的信息,這個可以用於生產環境中輸出程序運行的一些重要信息,但是不能濫用,避免打印過多的日志。
WARN 表明會出現潛在錯誤的情形,有些信息不是錯誤信息,但是也要給程序員的一些提示。
ERROR 指出雖然發生錯誤事件,但仍然不影響系統的繼續運行。打印錯誤和異常信息,如果不想輸出太多的日志,可以使用這個級別。
FATAL 指出每個嚴重的錯誤事件將會導致應用程序的退出。這個級別比較高了。重大錯誤,這種級別你可以直接停止程序了。
OFF 最高等級的,用於關閉所有日志記錄。
日志字段信息與日志格式
一條日志信息對應的是一個事件的發生,而一個事件通常需要包括以下幾個內容:
- 事件發生時間
- 事件發生位置
- 事件嚴重程度--也就是日志級別
- 事件內容
出了上面這些 還可以包括什么其他信息,進程id,進程名稱,線程id
說明:
輸出一條日志時,日志內容和日志級別是需要開發人員明確指定的。對於而其它字段信息,只需要是否顯示在日志中就可以了。
日志功能的實現
幾乎所有的語言都有日志的相關功能,或者比較優秀的地三方庫
python就有一個標准庫logging模塊
2、logging模塊簡介
logging模塊定義的函數和類為應用程序和庫的開發實現了一個靈活的事件日志系統。logging模塊是Python的一個標准庫模塊,由標准庫模塊提供日志記錄API的關鍵好處是所有Python模塊都可以使用這個日志記錄功能。所以,你的應用日志可以將你自己的日志信息與來自第三方模塊的信息整合起來。
logging模塊的日志級別
logging模塊默認定義了以下幾個日志等級,它允許開發人員自定義其他日志級別,但是這是不被推薦的,尤其是在開發供別人使用的庫時,因為這會導致日志級別的混亂。
不能修改日志級別
| 級別 | 值 | 描述 |
|---|---|---|
| CRITICAL/FATAL | 50 | 關鍵錯誤/消息 |
| ERROR | 40 | 錯誤 |
| WARNING | 30 | 警告消息 |
| INFO | 20 | 通知消息 |
| DEBUG | 10 | 調試 |
| NOTSET | 0 | 無級別 |
- 開飯應用程序或部署開發環境時
- 可以使用debug或info級別的日志獲取盡可能詳細的日志信息
- 應用上線或部署生產環境時
- 應該使用warning或error或critcal級別的日志來降低機器的I/O壓力和提高獲取錯誤日志信息效率。
- 日志級別的日志級別的指定通常是在配置文件進行指定。
當某個應用程序指定日志級別后,應用程序會記錄所有日志級別大於或者小於指定日志級別的日志信息,
而不僅僅是指定日志級別的日志信息nginx、php等應用程序以及這里要提高的python的logging模塊都是這樣的。同樣,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模塊定制模塊級別的方法完成簡單日志記錄
- 只有級別大於或等於日志記錄器級別才能輸出,小於的會唄拋棄
最簡單的日志輸出
先寫一個簡單不同的日志級別
import logging
logging.debug("debug log") #程序信息
logging.info("info log") # 用戶級別信息
logging.warning("warning log") # 警告信息 潛在報警
logging.error("error log") # 有錯但不影響運行
logging.fatal("fatal log") # 必須查看 可以停止程序
# 問前兩個沒有輸出
也可以
logging.log(logging.DEBUG,"debug log")
logging.log(logging.INFO,"INFO log")
logging.log(logging.WARNING,"WARNING log")
logging.log(logging.ERROR,"ERROR log")
logging.log(logging.FATAL,"FATAL log")
輸出結果:
WARNING:root:debug log
ERROR:root:debug log
CRITICAL:root:debug log
問題來了
問:為什么前兩條為什么沒有打印
這是因為logging模塊提供的日志記錄函數的日志級別WARNING(警告級別),因此WARNING級別大於它的error和fatal 輸出小於的就被丟棄了
問:打印出來的是什么意識
因為:
WARNING:root:debug log
# 日志級別:日志器名稱:日志內容
為什是這樣的呢 日志器設置的日志格式默認的是BASIC_FORMAT
BASIC_FORMAT = "%(levelname)s:%(name)s:%(message)s" # 源碼是460行
logging.basicCongfig()函數說明
這個方法用於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。 |
| handlers | 需要說明的是:filename、stream和handlers這三個配置項只能有一個存在,不能同時出現2個或3個,否則會引發ValueError異常。 |
import logging
import time
import datetime
log_format = " %(process)d -%(module)s - %(name)s - %(asctime)s PM - %(levelname)s - %(message)s" # 固定寫法
log_date = "%s"%datetime.datetime.now().date() # 定制時間
logging.basicConfig(
filemode="a", # 文件打開模式
filename="test", # 文件名
format=log_format, # 格式化
level=logging.DEBUG, # 級別定制
datefmt=log_date, # 定制時間格式
)
logging.debug("debug log") #程序信息
logging.info("info log") # 用戶級別信息
logging.warning("warning log") # 警告信息 潛在報警
logging.error("error log") # 有錯但不影響運行
logging.fatal("fatal log") # 必須查看 可以停
# 問前兩個沒有輸出
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 | 線程名稱 |
經過配置的日志輸出
先簡單配置下日志器的日志級別
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.")
此時我們就發現已經輸出我的文件里面去了
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.
坑
- logging.basicConfig()是一個簡單的配置工具,一次性的配置工具,也就是說只有在第一次該函數被調用的時候會氣作用 ,多次調用並不會累加。
- logging.warning("%s is %s warning log."%("This",1))不能這樣用。只能作為將變量數據作為第二個參數*args的值進行傳遞logging.warning("%s is %d warning log.g","This",3)
- 日志器(Logger)是有層級關系的,上面調用的logging模塊級別的函數所使用的日志器是
RootLogger類的實例,其名稱為'root',它是處於日志器層級關系最頂層的日志器,且該實例是以單例模式存在的 - logging.debug(), logging.info()等方法的定義中,除了msg和args參數外,還有一個**kwargs參數。它們支持3個關鍵字參數:
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模塊所包含的重要組件以及其工作流程做個全面、簡要的介紹,這有助於我們更好的理解我們所寫的代碼(將會觸發什么樣的操作)。
logging日志模塊四大組件
在介紹logging模塊的日志流處理流程之前,我們先來介紹下logging模塊的四大組件:
| 組件名稱 | 對應類名 | 功能描述 |
|---|---|---|
| 日志器 | Logger | 提供了應用程序可一直使用的接口 |
| 處理器 | Handler | 將logger創建的日志記錄發送到合適的目的輸出 |
| 過濾器 | Filter | 提供了更細粒度的控制工具來決定輸出哪條日志記錄,丟棄哪條日志記錄 |
| 格式器 | Formatter | 決定日志記錄的最終輸出格式 |
Logging是通過這些組件來完成日志處理的上面所使用的logging函數也是通過這幾個類來實現的
這些組件之間的關系描述:
- 日志器(logger)需要通過處理器(handler)將日志信息輸出到目標位置,如:文件、sys.stdout、網絡等;
- 不同的處理器(handler)可以將日志輸出到不同的位置;
- 日志器(logger)可以設置多個處理器(handler)將同一條日志記錄輸出到不同的位置;
- 每個處理器(handler)都可以設置自己的過濾器(filter)實現日志過濾,從而只保留感興趣的日志;
- 每個處理器(handler)都可以設置自己的格式器(formatter)實現同一條日志以不同的格式輸出到不同的地方。
簡單的說日志器是入口,真正干活的是處理器,處理器還可通過過濾器和格式器對要輸出的日志做過濾和格式化操作。
logging日志模塊相關類及其常用方法介紹
下面介紹下與logging四大組件相關的類:Logger, Handler, Filter, Formatt
Logger類
logger對象有3個任務
- 向應用程序暴露代碼幾個方法,使應用程序可以在運行時記錄日志
- 基於日志的等級過濾filter對象來決定要對那些日志進行后續處理
- 將日志信息傳給所有感興趣的日志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對象呢?一種方式是通過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數量等。
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的日志等級並不會對該日志消息進行等級過濾。
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
print(datetime.datetime.now())
logger = logging.getLogger('mylogger')
logger.setLevel(logging.DEBUG)
# 設置郵箱存入本地加入時間間隔
rf_handler = logging.handlers.TimedRotatingFileHandler('all.log', when='midnight', interval=1, backupCount=7, atTime=datetime.datetime.now())
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 - %(name)s - %(levelname)s - %(filename)s[:%(lineno)d] - %(message)s"))
# 設置日志發送郵箱郵箱
ret = logging.handlers.SMTPHandler(
mailhost=("smtp.163.com",25), # 設置服務器
fromaddr="chunsheng199433@163.com", # 設置用戶客戶端
toaddrs=["jiangyi@oldboyedu.com"],# 設置服務端
subject="logging from my app", # 設置標題
credentials=["chunsheng199433@163.com","*******"], #設置你的用戶名和密碼
)
ret.setLevel(logging.DEBUG)
ret.setFormatter(logging.Formatter("%(asctime)s - %(levelname)s - %(message)s"))
# ret.setFormatter(logging.Formatter("%(asctime)s - %(name)s - %(levelname)s - %(filename)s[:%(lineno)d] - %(message)s"))
logger.addHandler(rf_handler)
logger.addHandler(ret)
logger.addHandler(f_handler)
logger.debug('debug message')
logger.info('info message')
logger.warning('warning message')
logger.error('error message')
logger.critical("critical 我是警告")
6、django使用日志
settings.py
BASE_LOG_DIR = os.path.join(BASE_DIR, "logs")
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': os.path.join(BASE_LOG_DIR, 'django.log'),
'formatter': 'standard',
'encoding': 'utf-8'
}, # 用於文件輸出
'error': { # error 定義類型錯誤
'level': 'ERROR',
'class': 'logging.handlers.RotatingFileHandler', # 保存到文件,自動切
'filename': os.path.join(BASE_LOG_DIR, 'django.errorlog'), # 日志文件
'maxBytes': 1024 * 1024 * 50, # 日志大小 50M
'backupCount': 5,
'formatter': 'standard',
'encoding': 'utf-8',
},
'mail_admins': {
'level': 'ERROR',
'class': 'django.utils.log.AdminEmailHandler',
'formatter': 'standard'
},
},
'loggers': {
'mdjango': {
# 一個記錄器中可以使用多個處理器
'handlers': ['console', 'file_handler',"error"], # 掛載
'level': 'DEBUG',
'propagate': True,
},
'django.request': {
'handlers': ['mail_admins'],
'level': 'ERROR',
'propagate': False,
},
}
}
logger = logging.getLogger("mdjango")
views.py
from django.shortcuts import render,HttpResponse
import logging
from django_log.settings import logger
# Create your views here.
def login(request):
logger.info("IndexHandler request Handler begin")
logger.debug('query total:')
logger.error("error")
logger.warning("warning")
return HttpResponse("ok")
自己定義
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")
