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---
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的調用什么都不做。
