[Python 多線程] logging模塊、Logger類 (八)
logging模塊:
標準庫裏面的logging模塊,在前面學習線程安全時曾用來解決print被打斷的問題,這裏會介紹logging模塊的功能。
logging模塊是線程安全的,不需要客戶做任何特殊的工作。它通過使用線程鎖實現了這一點; 有一個鎖來序列化訪問模塊的共享數據,每個處理程序還創建一個鎖來序列化訪問其底層 I/O。
日誌記錄級別:
級別 | 數值 |
---|---|
CRITICAL |
50 |
ERROR |
40 |
WARNING |
30,默認 |
INFO |
20 |
DEBUG |
10 |
NOTSET |
0 |
定義的記錄級別越低,信息越多,級別越高,信息越少。
日誌記錄格式化字符串:
屬性名 | 格式 | 描述 |
---|---|---|
asctime | %(asctime)s |
易讀的時間格式: 默認情況下是‘2003-07-08 16:49:45,896‘的形式(逗號之後的數字是毫秒部分的時間) |
filename | %(filename)s |
路徑名的文件名部分。 |
funcName | %(funcName)s |
日誌調用所在的函數名 |
levelname | %(levelname)s |
消息的級別名稱(‘DEBUG‘ , ‘INFO‘ , ‘WARNING‘ , ‘ERROR‘ , ‘CRITICAL‘ ). |
levelno | %(levelno)s |
對應數字格式的日誌級別 (DEBUG , INFO , WARNING , ERROR ,CRITICAL ). |
lineno | %(lineno)d |
發出日誌記錄調用的源碼行號 (如果可用)。 |
module | %(module)s |
所在的模塊名(如test6.py模塊則記錄test6) |
message | %(message)s |
記錄的信息 |
name | %(name)s |
調用的logger記錄器的名稱 |
process | %(process)d |
進程ID |
processName | %(processName)s |
進程名 |
thread | %(thread)d |
線程ID |
threadName | %(threadName)s |
線程名 |
使用basicConfig方法配置logging記錄格式:
格式 | 描述 |
---|---|
filename |
指定使用指定的文件名而不是StreamHandler創建FileHandler。 |
filemode |
指定打開文件的模式,如果指定了filename(如果文件模式未指定,則默認為‘a‘)。 |
format |
為處理程序使用指定的格式字符串。 |
datefmt |
使用指定的日期/時間格式。 |
level |
將根記錄器級別設置為指定的級別。 |
handlers |
如果指定,這應該是一個已經創建的處理程序的叠代器添加到根記錄器。任何尚未設置格式化程序的處理程序都將被分配在此函數中創建的默認格式化程序。 |
舉例:
import threading import logging FORMAT = "%(asctime)s %(thread)d %(message)s" logging.basicConfig(level=logging.INFO,format=FORMAT) def add(x,y): logging.warning("{} {}".format(threading.enumerate(),x+y)) t = threading.Timer(1,add,args=(4,5)) t.start() 運行結果: 2017-12-17 15:40:34,226 123145367023616 [<_MainThread(MainThread, stopped 4320629568)>, <Timer(Thread-1, started 123145367023616)>] 9
修改日期格式:
DATEFMT ="[%Y-%m-%d %H:%M:%S]" FORMAT = "%(asctime)s %(thread)d %(message)s" logging.basicConfig(level=logging.INFO,format=FORMAT,datefmt=DATEFMT)
[2017-12-17 15:45:18]
輸出到文件:
logging.basicConfig(level=logging.INFO,format=FORMAT,datefmt=DATEFMT,filename=‘class_test.log‘)
文件路徑不指定,默認為當前模塊路徑。
import threading import logging DATEFMT ="[%Y-%m-%d %H:%M:%S]" FORMAT = "%(asctime)s %(thread)d %(message)s" logging.basicConfig(level=logging.INFO,format=FORMAT,datefmt=DATEFMT,filename=‘class_test.log‘) def add(x,y): logging.warning("{} {}".format(threading.enumerate(),x+y)) t = threading.Timer(1,add,args=(4,5)) t.start() 輸出結果會追加寫入當前模塊路徑的class_test.log文件: [2017-12-17 15:50:13] 123145503244288 [<_MainThread(MainThread, stopped 4320629568)>, <Timer(Thread-1, started 123145503244288)>] 9
Logger類:
構造
使用工廠方法返回一個Logger實例。
logging.
getLogger
([name=None])
指定name,返回一個名稱為name的Logger實例。如果再次使用相同的名字,是實例化一個對象。未指定name,返回Logger實例,名稱是root,即根Logger。
Logger是層次結構的,使用 ‘.‘ 點號分割,如‘a‘、‘a.b‘或‘a.b.c.d‘,‘a‘是‘a.b‘的父parent,a.b是a的子child。對於foo來說,名字為foo.bar、foo.bar.baz、foo.bam都是foo的後代。
舉例:
import logging DATEFMT ="[%Y-%m-%d %H:%M:%S]" FORMAT = "%(asctime)s %(thread)d %(message)s" logging.basicConfig(level=logging.INFO,format=FORMAT,datefmt=DATEFMT,filename=‘class_test.log‘) root = logging.getLogger() print(root.name,type(root),root.parent,id(root)) logger = logging.getLogger(__name__) print(logger.name, type(logger), id(logger), id((logger.parent))) logger1 = logging.getLogger(__name__ + ".ok") print(logger1.name, type(logger1), id(logger1), id((logger1.parent))) print(logger1.parent,id(logger1.parent)) 運行結果: root <class ‘logging.RootLogger‘> None 4367575248 __main__ <class ‘logging.Logger‘> 4367575864 4367575248 __main__.ok <class ‘logging.Logger‘> 4367575920 4367575864 <logging.Logger object at 0x10453eb38> 4367575864
子child的級別設置,不影響父parent的級別:
import logging FORMAT = "%(asctime)s %(thread)d %(message)s" logging.basicConfig(level=logging.WARNING,format=FORMAT,datefmt="[%Y-%m-%d %H:%M:%S]") root = logging.getLogger() print(1,root,id(root)) #RootLogger,根Logger root.info(‘my root‘) #低於定義的WARNING級別,所以不會記錄 loga = logging.getLogger(__name__) #Logger繼承自RootLogger print(2,loga,id(loga),id(loga.parent)) print(3,loga.getEffectiveLevel()) #數值形式的有效級別 loga.warning(‘before‘) loga.setLevel(28) #設置級別為28 print(4,loga.getEffectiveLevel()) loga.info(‘after‘)# loga.warning(‘after1‘) 運行結果: [2017-12-17 16:31:20] 4320629568 before 1 <logging.RootLogger object at 0x104534f28> 4367535912 2 <logging.Logger object at 0x1044ef630> 4367250992 4367535912 3 30 4 28 [2017-12-17 16:31:20] 4320629568 after1
Handler:
Handler控制日誌信息的輸出目的地,可以是控制臺、文件。
可以單獨設置level
可以單獨設置格式
可以設置過濾器
Handler
StreamHandler #不指定使用sys.strerr
FileHandler #文件
_StderrHandler #標準輸出
NullHandler #什麽都不做
level的繼承:
import logging FORMAT = "%(asctime)s %(thread)d %(message)s" logging.basicConfig(level=logging.INFO,format=FORMAT,datefmt="[%Y-%m-%d %H:%M:%S]") root = logging.getLogger() #根Logger級別為INFO 20 print(‘root:‘,root.getEffectiveLevel()) log1 = logging.getLogger(‘s‘) log1.setLevel(logging.ERROR) #級別為ERROR 40 print(‘log1:‘,log1.getEffectiveLevel()) log1.error(‘log1 error‘) log2 = logging.getLogger(‘s.s1‘) #繼承自log1 40,無法使用warning log2.setLevel(logging.WARNING) #設置為WARNING 30,才可以使用warning print(‘log2:‘,log2.getEffectiveLevel()) log2.warning(‘log2 warning‘) 運行結果: [2017-12-17 16:52:22] 4320629568 log1 error root: 20 log1: 40 [2017-12-17 16:52:22] 4320629568 log2 warning log2: 30
logger實例,如果設置了level,就用它和信息的級別比較,否則,繼承最近的祖先的level。
handler處理:
import logging FORMAT = "%(asctime)s %(thread)d %(message)s" logging.basicConfig(level=logging.INFO,format=FORMAT,datefmt="[%Y-%m-%d %H:%M:%S]") root = logging.getLogger() print(1,root.getEffectiveLevel()) #RootLogger,根Logger log1 = logging.getLogger(‘s‘) print(2,log1.getEffectiveLevel()) h1 = logging.FileHandler(‘test.log‘) h1.setLevel(logging.WARNING) log1.addHandler(h1) print(3,log1.getEffectiveLevel()) log2 = logging.getLogger(‘s.s2‘) print(4,log2.getEffectiveLevel()) h2 = logging.FileHandler(‘test1.log‘) h2.setLevel(logging.WARNING) log1.addHandler(h2) print(3,log1.getEffectiveLevel()) log2.warning(‘log2 info---‘) 運行結果: 1 20 [2017-12-17 19:02:53] 7956 log2 info--- 2 20 3 20 4 20 3 20
test.log和test1.log最終都會記錄一份"log2 info---"
同樣,handler也可以設置使用logging.Formatter()設置格式和Logging.Filter()設置過濾器:
import logging FORMAT = "%(asctime)s %(thread)d %(message)s" logging.basicConfig(level=logging.INFO,format=FORMAT,datefmt="[%Y-%m-%d %H:%M:%S]") root = logging.getLogger() print(1,root.getEffectiveLevel()) #RootLogger,根Logger log1 = logging.getLogger(‘s‘)#模塊化用__module__,函數化用__name__作為Logger名,Logger同名內存中也只有一個 print(2,log1.getEffectiveLevel()) h1 = logging.FileHandler(‘test.log‘) h1.setLevel(logging.WARNING) fmt1 = logging.Formatter(‘[%(asctime)s] %(thread)s %(threadName)s log1-handler1 %(message)s‘) h1.setFormatter(fmt1) #重新個性化定義記錄的格式化字符串 log1.addHandler(h1) filter1 = logging.Filter(‘s‘) #過濾器 會記錄s, s.s2的信息 log1.addFilter(filter1) print(3,log1.getEffectiveLevel()) log2 = logging.getLogger(‘s.s2‘) print(4,log2.getEffectiveLevel()) h2 = logging.FileHandler(‘test1.log‘) h2.setLevel(logging.WARNING) log1.addHandler(h2) filter1 = logging.Filter(‘s.s2‘) #過濾器不會記錄s.s2的消息,只會記錄自己的消息 log1.addFilter(filter1) print(3,log1.getEffectiveLevel()) log1.warning(‘log1 warning===‘) log2.warning(‘log2 warning---‘) 運行結果: test.log: #handler1記錄了到了log1和log2的信息 [2017-12-17 19:43:12,654] 5872 MainThread log1-handler1 log1 warning=== [2017-12-17 19:43:12,654] 5872 MainThread log1-handler1 log2 warning--- test1.log: #handler2只記錄了它自己的信息 log2 warning---
渣圖:
總結:
1. 每一個Logger實例的level如同入口,讓水流進來,如果這個門檻太高,信息就進不來
2. 如果level沒有設置,就用父logger的,如果父logger的level也沒有設置,繼續找父的父的,最終找到root上,如果root設置了就用它的,如果root沒有設置,root的默認值是WARNING
3. 在某個logger上產生某種級別的信息,首先和logger的level檢查,如果消息level低於logger的EffectiveLevl有效級別,消息丟棄,不會再向父logger傳遞該消息。如果通過(大於等於)檢查後,消息交給logger所有的handler處理,每一個handler需要和自己level比較來決定是否處理。如果沒有一個handler,或者消息已經被handler處理過了,這個消息會繼續發送給父logger處理。
4. 父logger拿到消息,會重復第三條的過程,直至根logger
[Python 多線程] logging模塊、Logger類 (八)