在一個軟件中,日志是可以說必不可少的一個組成部分,通常會在定位客戶問題或者記錄軟件使用情況等場景中會用到。logging模板塊是Python的一個內置標准庫,用於實現對日志的控制輸出,對於平常的日志輸出,甚至是系統級的日志輸出,也都可以使用logging模塊來進行實現。
一、使用basicConfig進行簡單的一次性配置
basicConfig一次性配置,簡單示例:
# -*- coding:utf-8 -*- import logging import datetime # filename:設置日志輸出文件,以天為單位輸出到不同的日志文件,以免單個日志文件日志信息過多, # 日志文件如果不存在則會自動創建,但前面的路徑如log文件夾必須存在,否則會報錯 log_file = 'log/sys_%s.log' % datetime.datetime.strftime(datetime.datetime.now(), '%Y-%m-%d') # level:設置日志輸出的最低級別,即低於此級別的日志都不會輸出 # 在平時開發測試的時候可以設置成logging.debug以便定位問題,但正式上線后建議設置為logging.WARNING,既可以降低系統I/O的負荷,也可以避免輸出過多的無用日志信息 log_level = logging.WARNING # format:設置日志的字符串輸出格式 log_format = '%(asctime)s[%(levelname)s]: %(message)s' logging.basicConfig(filename=log_file, level=logging.WARNING, format=log_format) logger = logging.getLogger() # 以下日志輸出由於level被設置為了logging.WARNING,所以debug和info的日志不會被輸出 logger.debug('This is a debug message!') logger.info('This is a info message!') logger.warning('This is a warning message!') logger.error('This is a error message!') logger.critical('This is a critical message!')
運行后,日志文件sys_2019-04-14.log的內容如下:
2019-04-14 23:34:42,444[WARNING]: This is a warning message! 2019-04-14 23:34:42,444[ERROR]: This is a error message! 2019-04-14 23:34:42,444[CRITICAL]: This is a critical message!
logging.basicConfig:用於對logging模塊整個日志輸出的一次性配置,也就是說多次配置時以第一次配置的為准,之后再使用basicConfig進行配置則無效。
logging.basicConfig的參數:
filename:設置日志輸出的文件,默認輸出到控制台。
filemode:設置打開日志文件的方式,默認為“a”,即追加。
format:設置日志輸出的字符串格式,具體的格式有如下幾種:
- %(name)s:日志記錄器的名稱
- %(levelno)s:日志級別數值
- %(levelname)s:日志級別名稱
- %(pathname)s:輸出日志時當前文件的絕對路徑
- %(filename)s:輸出日志時當前文件名(包含后綴)
- %(module)s:輸出日志時的模塊名(即%(filename)s不包含后綴名)
- %(funcName)s:輸出日志時所在函數名
- %(lineno)d:輸出日志時在文件中的行號
- %(asctime)s:輸出日志時的時間
- %(thread)d:輸出日志的當前線程ID
- %(threadName)s:輸出日志的當前線程名稱
- %(process)s:輸出日志的當前進程ID
- %(processName)s:輸出日志的當前進程名稱
- %(message)s:輸出日志的內容
datefmt:設置日志時間字符串的輸出格式,默認時間字符串格式為%Y-%m-%d %H:%M:%S。
style:設置format字符串格式化的風格,可以是“%”,“{”或“$”,默認是“%”。
level:設置日志的級別,具體有以下幾種(由高到低):
- CRITICAL:致命錯誤
- ERROR:嚴重錯誤
- WARNING:需要給出的提示
- INFO:一般的日志信息
- DEBUG:在debug過程中的debug信息
stream:指定日志的輸出Stream,可以是sys.stderr,sys.stdout或者是文件(即使用open函數打開的文件流,但是這個文件流logging模塊不會主動關閉它),默認是sys.stderr,如果同時指定了filename參數和stream參數,那stream參數就會被忽略。
二、使用Handler將日志同時輸出到文件和控制台
添加Handler打印日志,簡單示例:
# -*- coding:utf-8 -*- import logging import datetime logger = logging.getLogger() # 設置此logger的最低日志級別,之后添加的Handler級別如果低於這個設置,則以這個設置為最低限制 logger.setLevel(logging.INFO) # 創建一個FileHandler,將日志輸出到文件 log_file = 'log/sys_%s.log' % datetime.datetime.strftime(datetime.datetime.now(), '%Y-%m-%d') file_handler = logging.FileHandler(log_file) # 設置此Handler的最低日志級別 file_handler.setLevel(logging.WARNING) # 設置此Handler的日志輸出字符串格式 log_formatter = logging.Formatter('%(asctime)s[%(levelname)s]: %(message)s') file_handler.setFormatter(log_formatter) # 創建一個StreamHandler,將日志輸出到Stream,默認輸出到sys.stderr stream_handler = logging.StreamHandler() stream_handler.setLevel(logging.INFO) # 將不同的Handler添加到logger中,日志就會同時輸出到不同的Handler控制的輸出中 # 注意如果此logger在之前使用basicConfig進行基礎配置,因為basicConfig會自動創建一個Handler,所以此logger將會有3個Handler # 會將日志同時輸出到3個Handler控制的輸出中 logger.addHandler(file_handler) logger.addHandler(stream_handler) # 文件中將會輸出WARNING及以上級別的日志 # 控制台將會輸出INFO及以上級別的日志 logger.debug('This is a debug message!') logger.info('This is a info message!') logger.warning('This is a warning message!') logger.error('This is a error message!') logger.critical('This is a critical message!')
運行后,日志文件sys_2019-04-15.log的內容如下:
2019-04-15 21:50:40,292[WARNING]: This is a warning message! 2019-04-15 21:50:40,292[ERROR]: This is a error message! 2019-04-15 21:50:40,293[CRITICAL]: This is a critical message!
控制台打印的內容如下:
This is a info message! This is a warning message! This is a error message! This is a critical message!
通過給logger添加不同的Handler,可以將日志同時輸出到不同的地方,需要注意的是使用basicConfig進行一次性基礎配置時,會根據配置內容自動創建一個Handler,所以如果之后再添加了N個Handler,實際上總的Handler數量有N+1個。
一般在logging或者logging.handlers下就可以你想要的Handler,不同的Handler會以不同的方式輸出到不同的地方,以下是幾種常用的Handler:
from logging import FileHandler: 以“a”(追加)的方式將日志輸出到文件,如果文件不存在,則自動創建該文件。
from logging import StreamHandler: 將日志輸出到Stream,比如sys.stderr、sys.stdour、文件流等。
from logging.handlers import RotatingFileHandler: 將日志輸出到文件,可以通過設置文件大小,文件達到上限后自動創建一個新的文件來繼續輸出文件。
from logging.handlers import TimedRotatingFileHandler: 將日志輸出到文件,可以通過設置時間,使日志根據不同的時間自動創建並輸出到不同的文件中。
from logging.handlers import HTTPHandler: 將日志發送到一個HTTP服務器。
from logging.handlers import SMTPHandler: 將日志發送到一個指定的郵件地址。
三、使用不同的日志級別輸出日志
最低級別:logger.setLevel為設置logger的最低日志級別,如果handler中也設置了級別,則不能低於這個級別,低於這個級別的設置是無效的。
不同級別的意義:在開發或者部署應用程序時,需要盡可能詳盡的信息來進行開發和調試,這時候用的比較多的是來自DEBUG和INFO級別的日志信息,但是到了正式上線或者生產環境時,應該使用WARNING和CRITICAL級別的日志信息以降低機器的I/O壓力和提高獲取到錯誤信息的效率。
日志量:日志的信息量應該是與日志級別成反比的:DEBUG>INFO>WARNING>ERROR>CRITICAL。
不同級別的設置:只有日志級別大於或等於指定日志級別的日志才會被輸出,所以在程序中可以設置一個比較高的日志級別,比如WARNING,開發和調試的時候修改為DEBUG或INFO以便得到更多的日志信息,正式上線的時候這些日志還是保持WARNING的級別。
四、輸出traceback.format_exc異常信息
可以在打印指定信息的同時在下一行打印出traceback.format_exc異常信息,如:logger.error(msg, exc_info=True)。如果指定了exc_info為True來打印錯誤消息,但是沒有發生錯誤的話,就會在消息的下一行打印“NoneType: None”。
注:對於參數exc_info,其實info,debug等函數也有這個關鍵字參數,但是不推薦使用這些低級別的打印函數來打印錯誤消息。
五、配置日志:配置文件和字典配置
參考文章:http://www.cnblogs.com/yyds/p/6885182.html
配置文件方式:將配置文件使用特定規則配置好對應section和option,然后通過logging.config.fileConfig函數加載並使用文件中的配置信息。
配置文件配置logging,簡單示例:
如下為配置文件log_cfg.ini的配置內容
[loggers] keys=root, console [handlers] keys=consolehandler, filehandler [formatters] keys=consoleformatter, fileformatter [logger_root] level=DEBUG handlers=filehandler [logger_console] level=WARNING handlers=consolehandler qualname=console propagate=0 [handler_filehandler] class=FileHandler args=('log/sys.log', ) level=WARNING formatter=fileformatter [handler_consolehandler] class=logging.StreamHandler args=(sys.stdout, ) level=DEBUG formatter=consoleformatter [formatter_fileformatter] format=%(asctime)s[%(name)s][%(levelname)s]: %(message)s [formatter_consoleformatter] format=%(asctime)s[%(levelname)s]: %(message)s
如下為py代碼
# -*- coding:utf-8 -*- import logging.config # 加載配置文件 logging.config.fileConfig('log_cfg.ini') # 獲取配置好的logger logger = logging.getLogger('console') logger.debug('This is a debug message!') logger.info('This is a info message!') logger.warning('This is a warning message!') logger.error('This is a error message!') logger.critical('This is a critical message!') # 獲取沒有配置的logger時,會自動使用root中配置的Handler any_logger = logging.getLogger('any') any_logger.critical('This is a test message!')
運行結果:
如下為sys.log文件內容
2019-04-21 21:39:43,029[any][CRITICAL]: This is a test message!
如下為控制台輸出
2019-04-21 21:39:43,029[WARNING]: This is a warning message! 2019-04-21 21:39:43,029[ERROR]: This is a error message! 2019-04-21 21:39:43,029[CRITICAL]: This is a critical message!
配置規則:
- loggers:此項為必須配置的section,並使用keys來指定需要配置的logger。keys中指定的logger必須要在文件中有對應的配置,且keys中必須包含root這個logger,但相反,已配置的logger可以不用在loggers下指定,可以在需要使用的使用再添加上去。
- handlers:此項為必須配置的section,並使用keys來指定需要配置的handler。keys中指定的handler必須要在文件中有對應的配置,但相反,已配置的handler可以不用在handlers下指定,可以在需要使用的使用再添加上去。
- formatters:此項為必須配置的section,並使用keys來指定需要配置的formatter。keys中指定的formatter必須要在文件中有對應的配置,但相反,已配置的formatter可以不用在formatters下指定,可以在需要使用的使用再添加上去。
- logger:命名方式為logger_xxx,xxx為在loggers中指定的logger名稱。對於root這個logger,必須指定level和handlers這兩個option,而對於非root的其他logger,除了level和handlers必須配置外,還需要配置qualname,代碼中使用logging.getLogger()時讀取的就是qualname這個option。另外對於非root的logger,還可以指定propagate(可選項),其默認值為1,表示會將消息傳遞給父logger的handler進行處理,也可以設置為0,表示不往上層傳遞消息。
- handler:命名方式為handler_xxx,xxx為在handlers中指定的handler名稱,class和args為必須配置的option,表示創建handler的類和對應的初始化參數,class可以是相對於logging模塊的類,也可以是直接使用import進行導入的類,args則必須是元組格式,哪怕只有一個參數也要是元組的格式。level和formatter為可選的option,且指定的formatter也一定要是在formatters和formatter中已配置好的。
- formatter:命名方式為formatter_xxx,xxx為在formatters中指定的formatter名稱,這個section下的option都是可選的,即可以不配置任何option項。但是一般會配置format,用於指定輸出字符串的格式,也可以指定datefmt,用於指定輸出的時間字符串格式,默認為%Y-%m-%d %H:%M:%S。
- 獲取沒有配置的logger:如果代碼中獲取的是沒有配置的logger,則會默認以root的handler來進行處理。
字典配置方式:將字典或者類字典的配置文件(如JSON格式的配置文件或者YAML格式的配置文件)使用特定規則配置好對應key和value,通過logging.config.dictConfig函數加載並使用里面的配置信息。這里需要說明的是,dictConfig只需要一個可以解析成字典的對象即可,可以是Python的字典或者其他配置文件,只要能構建出這個字典就行。
以YAML格式的文件來配置字典方式配置logging,簡單示例:
如下為配置文件log_cfg.yml的配置內容
version: 1 root: level: DEBUG handlers: [filehandler, ] loggers: console: level: WARNING handlers: [consolehandler, ] propagate: no handlers: filehandler: class: logging.FileHandler filename: log/sys.log level: WARNING formatter: fileformatter consolehandler: class: logging.StreamHandler stream: ext://sys.stdout level: DEBUG formatter: consoleformatter formatters: fileformatter: format: '%(asctime)s[%(name)s][%(levelname)s]: %(message)s' consoleformatter: format: '%(asctime)s[%(levelname)s]: %(message)s'
如下為py代碼
# -*- coding:utf-8 -*- import logging.config import yaml # 加載配置文件 with open('log_cfg.yml') as log_cfg_file: log_dictcfg = yaml.safe_load(log_cfg_file) logging.config.dictConfig(log_dictcfg) # 獲取配置好的logger logger = logging.getLogger('console') logger.debug('This is a debug message!') logger.info('This is a info message!') logger.warning('This is a warning message!') logger.error('This is a error message!') logger.critical('This is a critical message!') # 獲取沒有配置的logger時,會自動使用root中配置的Handler,如果沒有配置root,則會使用默認的root any_logger = logging.getLogger('any') any_logger.critical('This is a test message!')
輸出結果:同上一個配置文件方式的示例的輸出是相同的。
配置規則:
- version:必選項,表示配置規則的版本號,不過可用值只有1。
- root:可選項,用於配置root,如果沒有配置,則會自動創建一個默認的root。
- loggers:可選項,用於配置logger,這里propagate可以配置為yes/no,也可以配置為1/0、True/False等,當然也可以不配置,默認為yes下的效果。
- handlers:可選項,用於配置handler,配置handler時,除了class為必選項外,其他的都是可選項,且class的值必須是能import的,不能直接是FileHandler這種相對於logging模塊的路徑。另外,每個handler的配置字典中,除了class、level、formatter和filters外,其他的配置信息會傳給class對應類的初始化函數中,比如上面配置文件中的filename和stream。
- formatters:可選項,用於配置formatter,可以配置format和datefmt等信息。
- 外部對象訪問:外部對象是指不能直接進行訪問,需要import導入的對象,這時候需要加一個ext://前綴以便識別,然后系統就會import這個前綴后面的內容。
六、日志中輸出額外信息:日志傳參、LoggerAdapter
參考文章:http://www.cnblogs.com/yyds/p/6897964.html
直接傳參,簡單示例:
ip = '127.0.0.1' username = 'Jason' # 第一個參數為格式化消息字符串,第二個及之后的參數為對應的變量 logger.warning('[%s][%s]This is a warning message!', ip, username)
使用extra參數,簡單示例:
logger = logging.getLogger() logger.setLevel(logging.DEBUG) console_handler = logging.StreamHandler() console_handler.setLevel(logging.WARNING) # 在formatter中定義好需要額外傳遞參數,且在之后的日志輸出必須給出這些額外參數的字典值 console_formatter = logging.Formatter('%(asctime)s[%(ip)s][%(username)s]: %(message)s') console_handler.setFormatter(console_formatter) logger.addHandler(console_handler) # 將對應的字典信息傳給extra extra = {'ip': '127.0.0.1', 'username': 'Jason'} logger.warning('This is a warning message!', extra=extra) # 如果不傳入extra參數,則會報錯 # logger.warning('This is a warning message!')
使用LoggerAdapter設置默認的extra參數,簡單示例:
# -*- coding:utf-8 -*- import logging class IpUserLoggerAdapter(logging.LoggerAdapter): # 原process方法就兩行代碼,即使用默認的extra值 # 重寫process方法,如果沒有傳入extra,才使用默認值 def process(self, msg, kwargs): if 'extra' not in kwargs: kwargs['extra'] = self.extra return msg, kwargs def get_ipuser_logger(): logger = logging.getLogger() logger.setLevel(logging.DEBUG) console_handler = logging.StreamHandler() console_handler.setLevel(logging.WARNING) console_formatter = logging.Formatter('%(asctime)s[%(ip)s][%(user)s][%(levelname)s]: %(message)s') console_handler.setFormatter(console_formatter) logger.addHandler(console_handler) # 設置默認ip和user,這里的key要和formatter中的key對應 local_extra = { 'ip': '127.0.0.1', 'user': 'Jason' } return IpUserLoggerAdapter(logger, local_extra) if __name__ == '__main__': ipuser_logger = get_ipuser_logger() ipuser_logger.debug('This is a debug message!') ipuser_logger.info('This is a info message!') ipuser_logger.warning('This is a warning message!') # 打印一個臨時的ip和user ipuser_logger.error('This is a error message!', extra={'ip': '0.0.0.0', 'user': 'anyone'}) ipuser_logger.critical('This is a critical message!')
運行后,控制台輸出為:
2019-04-24 21:46:44,433[127.0.0.1][Jason][WARNING]: This is a warning message! 2019-04-24 21:46:44,433[0.0.0.0][anyone][ERROR]: This is a error message! 2019-04-24 21:46:44,433[127.0.0.1][Jason][CRITICAL]: This is a critical message!
七、子logger和共享配置
子logger:在logging模塊中,logger是類似於樹的層級結構,父logger和子logger之間使用點號“.”連接和識別,如:logging.getLogger('root'),logging.getLogger('root.child'),logging.getLogger('root.child.grand')……
配置共享:子logger擁有父logger相同的配置,如果在子logger中又另外增加了一些配置,那么子logger不僅擁有父logger的配置,也有自己新增的配置,不過需要注意的是就算子logger新增的配置信息與父logger相同(比如文件),也會執行為它單獨執行一次,即父logger和子logger的配置互不影響,並且子logger輸出一條日志時會先執行子logger的配置,再執行父logger的配置。
隔離父looger:logger中有一個屬性propagate,默認為True,即子logger會共享父logger的配置,可以手動設置為False,這樣子logger執行完后就不會再去執行父logger的配置了。
全局使用:在程序運行過程中,如果想要使用之前使用的logger,不需要每次都運行一次對應的配置,直接使用logging.getLogger(name)得到對應名稱的logger即可。
注:在使用logging.getLogger(name=None)時如果沒有傳入logger的名稱name,那么會自動返回名為root的logger。
簡單示例:
# -*- coding:utf-8 -*- import logging import datetime # 創建一個父logger logger = logging.getLogger('main') logger.setLevel(logging.INFO) # 給父logger添加一個文件輸出的Handler log_file = 'log/sys_%s.log' % datetime.datetime.strftime(datetime.datetime.now(), '%Y-%m-%d') file_handler = logging.FileHandler(log_file) file_handler.setLevel(logging.WARNING) log_formatter = logging.Formatter('%(asctime)s[%(name)s][%(levelname)s]: %(message)s') file_handler.setFormatter(log_formatter) logger.addHandler(file_handler) # 向文件輸出一條日志 logger.error('This is a error message!') # 創建一個子logger child_logger = logging.getLogger('main.child') # 給子logger添加一個輸出到控制台的Handler stream_handler = logging.StreamHandler() stream_handler.setLevel(logging.WARNING) child_logger.addHandler(stream_handler)
# 設置propagate屬性為False可以隔離父logger的配置
# child_logger.propagate = False # 同時向文件和控制台輸出一條日志 child_logger.warning('This is a warning message!')
運行后,日志文件sys_2019-04-16.log文件內容如下:
2019-04-16 00:09:53,154[main][ERROR]: This is a error message! 2019-04-16 00:09:53,154[main.child][WARNING]: This is a warning message!
控制台打印的內容如下:
This is a warning message!