logging模塊:python
標準庫裏面的logging模塊,在前面學習線程安全時曾用來解決print被打斷的問題,這裏會介紹logging模塊的功能。安全
logging模塊是線程安全的,不須要客戶作任何特殊的工做。它經過使用線程鎖實現了這一點; 有一個鎖來序列化訪問模塊的共享數據,每一個處理程序還建立一個鎖來序列化訪問其底層 I/O。模塊化
日誌記錄級別:函數
級別 | 數值 |
---|---|
CRITICAL |
50 |
ERROR |
40 |
WARNING |
30,默認 |
INFO |
20 |
DEBUG |
10 |
NOTSET |
0 |
定義的記錄級別越低,信息越多,級別越高,信息越少。學習
日誌記錄格式化字符串:spa
屬性名 | 格式 | 描述 |
---|---|---|
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
修改日期格式:code
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]orm
輸出到文件:
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---
loggerLevel --> FilterConditions --> HandlerLevel --> 父LoggerFilter --> 父LoggerHandler --> RootHandler --> 標準輸出或記錄到日誌:
import logging,datetime FORMAT = "%(asctime)s %(thread)d %(message)s" logging.basicConfig(level=logging.WARNING,format=FORMAT,datefmt="[%Y-%m-%d %H:%M:%S]") "--------------root--------------------" root = logging.getLogger() print(1,root.getEffectiveLevel()) "--------------log1-------------------" log1 = logging.getLogger('s') log1.setLevel(logging.ERROR) print(2,log1.getEffectiveLevel()) h1 = logging.FileHandler('h1.log') h1.setLevel(logging.INFO) log1.addHandler(h1) "--------------log2-------------------" log2 = logging.getLogger('s.s2') log2.setLevel(logging.WARNING) print(3,log2.getEffectiveLevel()) # h2 = logging.StreamHandler() h2 = logging.FileHandler('h2.log') h2.setLevel(logging.WARNING) # 最低40 f2 = logging.Filter('s.s3') # log2 = s.s2 , s, s.s2 s.s3 h2.addFilter(f2) log2.addHandler(h2) log2.warning('4,log2 warning -- {}'.format(datetime.datetime.now())) 運行結果: 1 30 2 40 3 30 [2017-12-19 14:23:43] 2508 4,log2 warning -- 2017-12-19 14:23:43.364928 #h1.log 4,log2 warning -- 2017-12-19 14:23:43.364928 #h2.log 空,沒有寫入
官網流程圖:
官網的介紹流程圖中第三步,在檢查Filter過濾條件不知足時就直接終止。但在上面例子中,log2 --> h2 --> h2Filter --> h2Handler ,當log2傳入的消息級別不知足h2Filter條件時,沒有直接終止,而是繼續向上傳遞給了父logger的Handler(h1),知足h1級別,最後寫入了h1.log。因此,官網上這一步不知道是否是正確的,但上面例子的實驗結果卻又證實官網的流程是錯誤的。
總結:
1. 每個Logger實例的level如同入口,讓水流進來,若是這個門檻過高,信息就進不來。例如log2.info('log3 info'),若是log2定義的級別高於info級別,就不會又信息經過log2
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的propagate屬性是否爲True,Ture則把這個消息會繼續發給父Logger,當前Logger的父Logger稱爲當前Logger,新的Logger的全部Handler繼續處理消息。
4. logger實例初始的propagate屬性爲True,即容許想父logger傳遞消息
5. logger.basicConfig
若是root沒有handler,就默認建立一個StreamHandler,若是設置了filename,就建立一個FileHandler。若是設置了format參數,就會用它生成一個formatter對象,並把這個formatter加入到剛纔建立的handler上,而後把這些handler加入到root.handlers列表上。level 是設置給root.logger的。
若是root.handlers列表不爲空,logging.basicConfig的調用什麼都不作。