下面的代碼展現了logging最基本的用法。html
1 # -*- coding: utf-8 -*- 2 3 import logging 4 import sys 5 6 # 獲取logger實例,若是參數爲空則返回root logger 7 logger = logging.getLogger("AppName") 8 9 # 指定logger輸出格式 10 formatter = logging.Formatter('%(asctime)s %(levelname)-8s: %(message)s') 11 12 # 文件日誌 13 file_handler = logging.FileHandler("test.log") 14 file_handler.setFormatter(formatter) # 能夠經過setFormatter指定輸出格式 15 16 # 控制檯日誌 17 console_handler = logging.StreamHandler(sys.stdout) 18 console_handler.formatter = formatter # 也能夠直接給formatter賦值 19 20 # 爲logger添加的日誌處理器 21 logger.addHandler(file_handler) 22 logger.addHandler(console_handler) 23 24 # 指定日誌的最低輸出級別,默認爲WARN級別 25 logger.setLevel(logging.INFO) 26 27 # 輸出不一樣級別的log 28 logger.debug('this is debug info') 29 logger.info('this is information') 30 logger.warn('this is warning message') 31 logger.error('this is error message') 32 logger.fatal('this is fatal message, it is same as logger.critical') 33 logger.critical('this is critical message') 34 35 # 2016-10-08 21:59:19,493 INFO : this is information 36 # 2016-10-08 21:59:19,493 WARNING : this is warning message 37 # 2016-10-08 21:59:19,493 ERROR : this is error message 38 # 2016-10-08 21:59:19,493 CRITICAL: this is fatal message, it is same as logger.critical 39 # 2016-10-08 21:59:19,493 CRITICAL: this is critical message 40 41 # 移除一些日誌處理器 42 logger.removeHandler(file_handler)
1 # 格式化輸出 2 3 service_name = "Booking" 4 logger.error('%s service is down!' % service_name) # 使用python自帶的字符串格式化,不推薦 5 logger.error('%s service is down!', service_name) # 使用logger的格式化,推薦 6 logger.error('%s service is %s!', service_name, 'down') # 多參數格式化 7 logger.error('{} service is {}'.format(service_name, 'down')) # 使用format函數,推薦 8 9 # 2016-10-08 21:59:19,493 ERROR : Booking service is down!
當你使用logging模塊記錄異常信息時,不須要傳入該異常對象,只要你直接調用logger.error()
或者 logger.exception()
就能夠將當前異常記錄下來。python
1 # 記錄異常信息 2 3 try: 4 1 / 0 5 except: 6 # 等同於error級別,可是會額外記錄當前拋出的異常堆棧信息 7 logger.exception('this is an exception message') 8 9 # 2016-10-08 21:59:19,493 ERROR : this is an exception message 10 # Traceback (most recent call last): 11 # File "D:/Git/py_labs/demo/use_logging.py", line 45, in 12 # 1 / 0 13 # ZeroDivisionError: integer division or modulo by zero
這是最基本的入口,該方法參數能夠爲空,默認的logger名稱是root,若是在同一個程序中一直都使用同名的logger,其實會拿到同一個實例,使用這個技巧就能夠跨模塊調用一樣的logger來記錄日誌。git
另外你也能夠經過日誌名稱來區分同一程序的不一樣模塊,好比這個例子。github
1 logger = logging.getLogger("App.UI") 2 logger = logging.getLogger("App.Service")
Formatter對象定義了log信息的結構和內容,構造時須要帶兩個參數:app
fmt
,默認會包含最基本的level
和 message
信息datefmt
,默認爲 2003-07-08 16:49:45,896 (%Y-%m-%d %H:%M:%S)
fmt
中容許使用的變量能夠參考下表。socket
Logging有以下級別: DEBUG,INFO,WARNING,ERROR,CRITICAL
默認級別是WARNING,logging模塊只會輸出指定level以上的log。這樣的好處, 就是在項目開發時debug用的log,在產品release階段不用一一註釋,只須要調整logger的級別就能夠了,很方便。函數
最經常使用的是StreamHandler和FileHandler, Handler用於向不一樣的輸出端打log。
Logging包含不少handler, 可能用到的有下面幾種this
logging的配置大體有下面幾種方式。spa
logging.config.fileConfig(filepath)
basicConfig()
提供了很是便捷的方式讓你配置logging模塊並立刻開始使用,能夠參考下面的例子。具體能夠配置的項目請查閱官方文檔。線程
1 import logging 2 3 logging.basicConfig(filename='example.log',level=logging.DEBUG) 4 logging.debug('This message should go to the log file') 5 6 logging.basicConfig(format='%(levelname)s:%(message)s', level=logging.DEBUG) 7 logging.debug('This message should appear on the console') 8 9 logging.basicConfig(format='%(asctime)s %(message)s', datefmt='%m/%d/%Y %I:%M:%S %p') 10 logging.warning('is when this event was logged.')
備註: 其實你甚至能夠什麼都不配置直接使用默認值在控制檯中打log,用這樣的方式替換print語句對往後項目維護會有很大幫助。
若是你但願經過配置文件來管理logging,能夠參考這個官方文檔。在log4net或者log4j中這是很常見的方式。
1 # logging.conf 2 [loggers] 3 keys=root 4 5 [logger_root] 6 level=DEBUG 7 handlers=consoleHandler 8 #,timedRotateFileHandler,errorTimedRotateFileHandler 9 10 ################################################# 11 [handlers] 12 keys=consoleHandler,timedRotateFileHandler,errorTimedRotateFileHandler 13 14 [handler_consoleHandler] 15 class=StreamHandler 16 level=DEBUG 17 formatter=simpleFormatter 18 args=(sys.stdout,) 19 20 [handler_timedRotateFileHandler] 21 class=handlers.TimedRotatingFileHandler 22 level=DEBUG 23 formatter=simpleFormatter 24 args=('debug.log', 'H') 25 26 [handler_errorTimedRotateFileHandler] 27 class=handlers.TimedRotatingFileHandler 28 level=WARN 29 formatter=simpleFormatter 30 args=('error.log', 'H') 31 32 ################################################# 33 [formatters] 34 keys=simpleFormatter, multiLineFormatter 35 36 [formatter_simpleFormatter] 37 format= %(levelname)s %(threadName)s %(asctime)s: %(message)s 38 datefmt=%H:%M:%S 39 40 [formatter_multiLineFormatter] 41 format= ------------------------- %(levelname)s ------------------------- 42 Time: %(asctime)s 43 Thread: %(threadName)s 44 File: %(filename)s(line %(lineno)d) 45 Message: 46 %(message)s 47 48 datefmt=%Y-%m-%d %H:%M:%S
假設以上的配置文件放在和模塊相同的目錄,代碼中的調用以下。
1 import os 2 filepath = os.path.join(os.path.dirname(__file__), 'logging.conf') 3 logging.config.fileConfig(filepath) 4 return logging.getLogger()
你有可能會看到你打的日誌會重複顯示屢次,可能的緣由有不少,但總結下來無非就一個,日誌中使用了重複的handler。
1
2
3
4
5
6
7
8
9
10
11
12
13
|
import logging
logging.basicConfig(level=logging.DEBUG)
fmt = '%(levelname)s:%(message)s'
console_handler = logging.StreamHandler()
console_handler.setFormatter(logging.Formatter(fmt))
logging.getLogger().addHandler(console_handler)
logging.info('hello!')
# INFO:root:hello!
# INFO:hello!
|
上面這個例子出現了重複日誌,由於在第3行調用basicConfig()
方法時系統會默認建立一個handler,若是你再添加一個控制檯handler時就會出現重複日誌。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
|
import logging
def get_logger():
fmt = '%(levelname)s:%(message)s'
console_handler = logging.StreamHandler()
console_handler.setFormatter(logging.Formatter(fmt))
logger = logging.getLogger('App')
logger.setLevel(logging.INFO)
logger.addHandler(console_handler)
return logger
def call_me():
logger = get_logger()
logger.info('hi')
call_me()
call_me()
# INFO:hi
# INFO:hi
# INFO:hi
|
在這個例子裏hi
竟然打印了三次,若是再調用一次call_me()
呢?我告訴你會打印6次。why? 由於你每次調用get_logger()
方法時都會給它加一個新的handler,你是自食其果。正常的作法應該是全局只配置logger一次。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
|
import logging
def get_logger():
fmt = '%(levelname)s: %(message)s'
console_handler = logging.StreamHandler()
console_handler.setFormatter(logging.Formatter(fmt))
logger = logging.getLogger('App')
logger.setLevel(logging.INFO)
logger.addHandler(console_handler)
return logger
def foo():
logging.basicConfig(format='[%(name)s]: %(message)s')
logging.warn('some module use root logger')
def main():
logger = get_logger()
logger.info('App start.')
foo()
logger.info('App shutdown.')
main()
# INFO: App start.
# [root]: some module use root logger
# INFO: App shutdown.
# [App]: App shutdown.
|
爲嘛最後的App shutdown
打印了兩次?因此在Stackoverflow上不少人都問,我應該怎麼樣把root logger關掉,root logger太坑爹坑媽了。只要你在程序中使用過root logger,那麼默認你打印的全部日誌都算它一份。上面的例子沒有什麼很好的辦法,我建議你招到那個沒有通過大腦就使用root logger的人,亂棍打死他或者開除他。
若是你真的想禁用root logger,有兩個不是辦法的辦法:
1
2
|
logging.getLogger().handlers = [] # 刪除全部的handler
logging.getLogger().setLevel(logging.CRITICAL) # 將它的級別設置到最高
|
Python中的日誌模塊做爲標準庫的一部分,功能仍是比較完善的。我的以爲上手簡單,另外也支持好比過濾,文件鎖等高級功能,能知足大多數項目需求。
不過切記,當心坑。