??? 在現實生活中,記錄日志非常重要。銀行轉賬時會有轉賬記錄;如果有出現什么問題,人們可以通過日志數據來搞清楚到底發生了什么。
????對于系統開發、調試以及運行,記錄日志都是同樣的重要。如果沒有日志記錄,程序崩潰時你幾乎就沒辦法弄明白到底發生了什么事情。
1、簡單使用
import?logging logging.debug('debug?message') logging.info('info?message') logging.warn('warn?message') logging.error('error?message') logging.critical('critical?message')
執行結果:
WARNING:root:warn?message ERROR:root:error?message CRITICAL:root:critical?message
默認情況下,logging模塊將日志打印到屏幕上(stdout),日志級別為WARNING(即只有日志級別高于WARNING的日志信息才會輸出),日志格式如下圖所示:
1.1 日志級別
級別 | 數字形式 | 何時使用 |
DEBUG | 10 | 詳細信息,典型地調試問題時會感興趣。 |
INFO | 20 | 證明事情按預期工作。 |
WARNING | 30 | 表明發生了一些意外,或者不久的將來會發生問題(如‘磁盤滿了’)。軟件還是在正常工作。 |
ERROR | 40 | 由于更嚴重的問題 |
CRITICAL | 50 | 嚴重錯誤,表明軟件已,軟件已不能執行一些功能了。不能繼續運行了。 |
NOSET | 0 | getattr(日志、記錄級。upper()) |
1.2 簡單配置
import?logging #?通過下面的方式進行簡單配置輸出方式與日志級別 logging.basicConfig(filename='logger.log',?level=logging.INFO) logging.debug('debug?message') logging.info('info?message') logging.warn('warn?message') logging.error('error?message') logging.critical('critical?message')
執行結果:
標準輸出(屏幕)未顯示任何信息,發現當前工作目錄下生成了logger.log,內容如下:
INFO:root:info?message WARNING:root:warn?message ERROR:root:error?message CRITICAL:root:critical?message
因為通過level=logging.INFO設置日志級別為INFO,所以所有的日志信息均輸出出來了。
2、重要的概念
Logger ?? ??? ? 記錄器,暴露了應用程序代碼能直接使用的接口。
Handler ?? ???? 處理器,將(記錄器產生的)日志記錄發送至合適的目的地。
Filter ?? ??? ???? 過濾器,提供了更好的粒度控制,它可以決定輸出哪些日志記錄。
Formatter ?? ?格式化器,指明了最終輸出中日志記錄的布局。
1.2.1 Logger 記錄器
Logger是一個樹形層級結構,在使用接口debug,info,warn,error,critical之前必須創建Logger實例,即創建一個記錄器,如果沒有顯式的進行創建,則默認創建一個root logger,并應用默認的日志級別(WARN),處理器Handler(StreamHandler,即將日志信息打印輸出在標準輸出上),和格式化器Formatter(默認的格式即為第一個簡單使用程序中輸出的格式)。
創建方法:?logger?=?logging.getLogger(logger_name)
創建Logger實例后,可以使用以下方法進行日志級別設置,增加處理器Handler。
logger.setLevel(logging.ERROR) # 設置日志級別為ERROR,即只有日志級別大于等于ERROR的日志才會輸出
logger.addHandler(handler_name)?#?為Logger實例增加一個處理器 logger.removeHandler(handler_name)?#?為Logger實例刪除一個處理器
2.2 Handler 處理器
Handler處理器類型有很多種,比較常用的有三個,StreamHandler,FileHandler,NullHandler,詳情可以訪問Python logging.handlers
創建StreamHandler之后,可以通過使用以下方法設置日志級別,設置格式化器Formatter,增加或刪除過濾器Filter。
ch.setLevel(logging.WARN)?#?指定日志級別,低于WARN級別的日志將被忽略 ch.setFormatter(formatter_name)?#?設置一個格式化器formatter ch.addFilter(filter_name)?#?增加一個過濾器,可以增加多個 ch.removeFilter(filter_name)?#?刪除一個過濾器
StreamHandler
創建方法:?sh?=?logging.StreamHandler(stream=None)
FileHandler
創建方法:?fh?=?logging.FileHandler(filename,?mode='a',?encoding=None,?delay=False)
NullHandler
NullHandler類位于核心logging包,不做任何的格式化或者輸出。本質上它是個“什么都不做”的handler,由庫開發者使用。
2.3 Formatter 格式化器
使用Formatter對象設置日志信息最后的規則、結構和內容,默認的時間格式為%Y-%m-%d %H:%M:%S。
創建方法:?formatter?=?logging.Formatter(fmt=None,?datefmt=None)
其中,fmt是消息的格式化字符串,datefmt是日期字符串。如果不指明fmt,將使用'%(message)s'。如果不指明datefmt,將使用ISO8601日期格式。
2.4 Filter 過濾器
Handlers和Loggers可以使用Filters來完成比級別更復雜的過濾。Filter基類只允許特定Logger層次以下的事件。例如用‘A.B’初始化的Filter允許Logger ‘A.B’, ‘A.B.C’, ‘A.B.C.D’, ‘A.B.D’等記錄的事件,logger‘A.BB’, ‘B.A.B’ 等就不行。 如果用空字符串來初始化,所有的事件都接受。
創建方法:?filter?=?logging.Filter(name='')
以下是相關概念總結:
熟悉了這些概念之后,有另外一個比較重要的事情必須清楚,即Logger是一個樹形層級結構;Logger可以包含一個或多個Handler和Filter,即Logger與Handler或Fitler是一對多的關系;
一個Logger實例可以新增多個Handler,一個Handler可以新增多個格式化器或多個過濾器,而且日志級別將會繼承。
?
3、Logging工作流程
第一次導入logging模塊或使用reload函數重新導入logging模塊,logging模塊中的代碼將被執行,這個過程中將產生logging日志系統的默認配置。
自定義配置(可選)。logging標準模塊支持三種配置方式: dictConfig,fileConfig,listen。其中,dictConfig是通過一個字典進行配置Logger,Handler,Filter,Formatter;fileConfig則是通過一個文件進行配置;而listen則監聽一個網絡端口,通過接收網絡數據來進行配置。當然,除了以上集體化配置外,也可以直接調用Logger,Handler等對象中的方法在代碼中來顯式配置。
使用logging模塊的全局作用域中的getLogger函數來得到一個Logger對象實例(其參數即是一個字符串,表示Logger對象實例的名字,即通過該名字來得到相應的Logger對象實例)。
使用Logger對象中的debug,info,error,warn,critical等方法記錄日志信息。
4、logging模塊使用過程
4.1 logging模塊處理流程
判斷日志的等級是否大于Logger對象的等級,如果大于,則往下執行,否則,流程結束。
產生日志。第一步,判斷是否有異常,如果有,則添加異常信息。第二步,處理日志記錄方法(如debug,info等)中的占位符,即一般的字符串格式化處理。
使用注冊到Logger對象中的Filters進行過濾。如果有多個過濾器,則依次過濾;只要有一個過濾器返回假,則過濾結束,且該日志信息將丟棄,不再處理,而處理流程也至此結束。否則,處理流程往下執行。
在當前Logger對象中查找Handlers,如果找不到任何Handler,則往上到該Logger對象的父Logger中查找;如果找到一個或多個Handler,則依次用Handler來處理日志信息。但在每個Handler處理日志信息過程中,會首先判斷日志信息的等級是否大于該Handler的等級,如果大于,則往下執行(由Logger對象進入Handler對象中),否則,處理流程結束。
執行Handler對象中的filter方法,該方法會依次執行注冊到該Handler對象中的Filter。如果有一個Filter判斷該日志信息為假,則此后的所有Filter都不再執行,而直接將該日志信息丟棄,處理流程結束。
?使用Formatter類格式化最終的輸出結果。 注:Formatter同上述第2步的字符串格式化不同,它會添加額外的信息,比如日志產生的時間,產生日志的源代碼所在的源文件的路徑等等。
真正地輸出日志信息(到網絡,文件,終端,郵件等)。至于輸出到哪個目的地,由Handler的種類來決定。
注:以上內容摘抄自第三條參考資料,內容略有改動,轉載特此聲明。
5、日志配置
5.1 配置方式
顯式創建記錄器Logger、處理器Handler和格式化器Formatter,并進行相關設置;
通過簡單方式進行配置,使用basicConfig()函數直接進行配置;
通過配置文件進行配置,使用fileConfig()函數讀取配置文件;
通過配置字典進行配置,使用dictConfig()函數讀取配置信息;
通過網絡進行配置,使用listen()函數進行網絡配置。
5.2 basicConfig關鍵字參數
關鍵字 | 描述 |
filename | 創建一個FileHandler,使用指定的文件名,而不是使用StreamHandler。 |
filemode | 如果指明了文件名,指明打開文件的模式(如果沒有指明filemode,默認為'a')。 |
format | handler使用指明的格式化字符串。 |
datefmt | 使用指明的日期/時間格式。 |
level | 指明根logger的級別。 |
stream | 使用指明的流來初始化StreamHandler。該參數與'filename'不兼容,如果兩個都有,'stream'被忽略。 |
5.3 有用的format格式
格式 | 描述 |
%(name)s | Logger的名字 |
%(levelno)s | 數字形式的日志級別 |
%(levelname)s | 文本形式的日志級別 |
%(pathname)s | 調用日志輸出函數的模塊的完整路徑名,可能沒有 |
%(filename)s | 調用日志輸出函數的模塊的文件名 |
%(module)s | 調用日志輸出函數的模塊名 |
%(funcName)s | 調用日志輸出函數的函數名 |
%(lineno)d | 調用日志輸出函數的語句所在的代碼行 |
%(created)f | 當前時間,用UNIX標準的表示時間的浮 點數表示 |
%(relativeCreated)d | 輸出日志信息時的,自Logger創建以 來的毫秒數 |
%(asctime)s | 字符串形式的當前時間。默認格式是 “2003-07-08 16:49:45,896”。逗號后面的是毫秒 |
%(thread)d | 線程ID。可能沒有 |
%(threadName)s | 線程名。可能沒有 |
%(process)d | 進程ID。可能沒有 |
%(message)s | 用戶輸出的消息 |
5.4 配置示例
5.4.1 顯式配置
使用程序logger.py如下:
import?logging #?create?logger logger_name?=?"example" file_log?=?"accesss.log" logger?=?logging.getLogger(logger_name) logger.setLevel(logging.DEBUG) #?create?file?handler fh?=?logging.FileHandler(file_log) fh.setLevel(logging.WARN) #?create?formatter fmt?=?"%(asctime)-15s?%(levelname)s?%(filename)s?%(lineno)d?%(process)d?%(message)s" datefmt?=?"%a?%d?%b?%Y?%H:%M:%S" formatter?=?logging.Formatter(fmt,?datefmt) #?add?handler?and?formatter?to?logger fh.setFormatter(formatter) logger.addHandler(fh) #?print?log?info logger.debug('debug?message') logger.info('info?message') logger.warn('warn?message') logger.error('error?message') logger.critical('critical?message')
5.4.2 文件配置
配置文件logging.conf如下:
[loggers] keys=root,example01 [logger_root] level=DEBUG handlers=hand01,hand02 [logger_example01] handlers=hand01,hand02 qualname=example01 propagate=0 [handlers] keys=hand01,hand02 [handler_hand01] class=StreamHandler level=INFO formatter=form02 args=(sys.stderr,) [handler_hand02] class=FileHandler level=DEBUG formatter=form01 args=('log.log',?'a') [formatters] keys=form01,form02 [formatter_form01] format=%(asctime)s?%(filename)s[line:%(lineno)d]?%(levelname)s?%(message)s
使用程序logger.py如下:
import?logging import?logging.config logging.config.fileConfig("logging.conf") #?create?logger logger_name?=?"example" logger?=?logging.getLogger(logger_name) logger.debug('debug?message') logger.info('info?message') logger.warn('warn?message') logger.error('error?message') logger.critical('critical?message')
5.4.3 字典配置
import?logging import?logging.config logger?=?logging.getLogger(__name__) #?load?config?from?file? #?logging.config.fileConfig('logging.ini',?disable_existing_loggers=False) #?or,?for?dictConfig logging.config.dictConfig({'version':?1,??????????????'disable_existing_loggers':?False,??#?this?fixes?the?problem'formatters':?{'standard':?{'format':?'%(asctime)s?[%(levelname)s]?%(name)s:?%(message)s'},},'handlers':?{'default':?{'level':'INFO',????'class':'logging.StreamHandler',},??},'loggers':?{'':?{??????????????????'handlers':?['default'],????????'level':?'INFO',??'propagate':?True??}} }) logger.info('It?works!')
5.4.4 監聽配置
可以使用logging.config.listen(port=DEFAULT_LOGGING_CONFIG_PORT)進行完善本文。
5.4.5 JSON配置
配置文件logging.json如下:
{"version":?1,"disable_existing_loggers":?false,"formatters":?{"simple":?{"format":?"%(asctime)s?-?%(name)s?-?%(levelname)s?-?%(message)s"}},"handlers":?{"console":?{"class":?"logging.StreamHandler","level":?"DEBUG","formatter":?"simple","stream":?"ext://sys.stdout"},"info_file_handler":?{"class":?"logging.handlers.RotatingFileHandler","level":?"INFO","formatter":?"simple","filename":?"info.log","maxBytes":?10485760,"backupCount":?20,"encoding":?"utf8"},"error_file_handler":?{"class":?"logging.handlers.RotatingFileHandler","level":?"ERROR","formatter":?"simple","filename":?"errors.log","maxBytes":?10485760,"backupCount":?20,"encoding":?"utf8"}},"loggers":?{"my_module":?{"level":?"ERROR","handlers":?["console"],"propagate":?"no"}},"root":?{"level":?"INFO","handlers":?["console",?"info_file_handler",?"error_file_handler"]} }
使用程序logger.py如下:
import?json import?logging.configdef?setup_logging(default_path='logging.json',?default_level=logging.INFO,env_key='LOG_CFG' ):"""Setup?logging?configuration"""path?=?default_pathvalue?=?os.getenv(env_key,?None)if?value:path?=?valueif?os.path.exists(path):with?open(path,?'rt')?as?f:config?=?json.load(f)logging.config.dictConfig(config)else:logging.basicConfig(level=default_level)
5.4.6 YAML配置
配置文件logging.yaml如下:
?--- version:?1 disable_existing_loggers:?False formatters:simple:format:?"%(asctime)s?-?%(name)s?-?%(levelname)s?-?%(message)s" handlers:console:class:?logging.StreamHandlerlevel:?DEBUGformatter:?simplestream:?ext://sys.stdoutinfo_file_handler:class:?logging.handlers.RotatingFileHandlerlevel:?INFO????????????formatter:?simplefilename:?info.logmaxBytes:?10485760?#?10MBbackupCount:?20encoding:?utf8error_file_handler:class:?logging.handlers.RotatingFileHandlerlevel:?ERROR????????????formatter:?simplefilename:?errors.logmaxBytes:?10485760?#?10MBbackupCount:?20encoding:?utf8 loggers:my_module:level:?ERRORhandlers:?[console]propagate:?no root:level:?INFOhandlers:?[console,?info_file_handler,?error_file_handler] ...
使用程序logger.py如下:
import?os import?logging.config import?yaml def?setup_logging(default_path='logging.yaml',?default_level=logging.INFO,env_key='LOG_CFG' ):"""Setup?logging?configuration"""path?=?default_pathvalue?=?os.getenv(env_key,?None)if?value:path?=?valueif?os.path.exists(path):with?open(path,?'rt')?as?f:config?=?yaml.load(f.read())logging.config.dictConfig(config)else:lo
6、使用 __name__ 作為 logger 的名稱
雖然不是非得將 logger 的名稱設置為 __name__ ,但是這樣做會給我們帶來諸多益處。在 python 中,變量 __name__ 的名稱就是當前模塊的名稱。比如,在模塊 “foo.bar.my_module” 中調用 logger.getLogger(__name__) 等價于調用logger.getLogger(“foo.bar.my_module”) 。當你需要配置 logger 時,你可以配置到 “foo” 中,這樣包 foo 中的所有模塊都會使用相同的配置。當你在讀日志文件的時候,你就能夠明白消息到底來自于哪一個模塊。
7、捕捉異常并使用 traceback 記錄它
出問題的時候記錄下來是個好習慣,但是如果沒有 traceback ,那么它一點兒用也沒有。你應該捕獲異常并用 traceback 把它們記錄下來。比如下面這個例子:
使用參數 exc_info=true 調用 logger 方法, traceback 會輸出到 logger 中。你可以看到下面的結果:
try:open('/path/to/does/not/exist',?'rb') except?(SystemExit,?KeyboardInterrupt):raise except?Exception,?e:logger.error('Failed?to?open?file',?exc_info=True)
你也可以調用 logger.exception(msg, _args),它等價于 logger.error(msg, exc_info=True, _args)。
千萬不要在模塊層次獲取 Logger,除非 disable_existing_loggers 被設置為 False
你可以看到很多在模塊層次獲取 logger 的例子(在這篇文章我也使用了很多,但這僅僅為了讓示例更短一些)。它們看上去沒什么壞處,但事實上,這兒是有陷阱的 – 如果你像這樣在模塊中使用 Logger,Python 會保留從文件中讀入配置前所有創建的所有 logger。
my_module.py
import?logging logger?=?logging.getLogger(__name__) def?foo():logger.info('Hi,?foo') class?Bar(object):def?bar(self):logger.info('Hi,?bar') main.py? import?logginglogger?=?logging.getLogger(__name__)def?foo():logger.info('Hi,?foo')class?Bar(object):def?bar(self):logger.info('Hi,?bar')
本應該在日志中看到記錄,但是你卻什么也沒有看到。為什么呢?這就是因為你在模塊層次創建了 logger,然后你又在加載日志配置文件之前就導入了模塊。logging.fileConfig 與 logging.dictConfig 默認情況下會使得已經存在的 logger 失效。所以,這些配置信息不會應用到你的 Logger 上。你最好只在你需要 logger 的時候才獲得它。反正創建或者取得 logger 的成本很低。你可以這樣寫你的代碼:
import?loggingdef?foo():logger?=?logging.getLogger(__name__)logger.info('Hi,?foo')class?Bar(object):def?__init__(self,?logger=None):self.logger?=?logger?or?logging.getLogger(__name__)def?bar(self):self.logger.info('Hi,?bar')
這樣,logger 就會在你加載配置后才會被創建。這樣配置信息就可以正常應用。
python2.7 之后的版本中 fileConfg 與 dictConfig 都新添加了 “disable_existing_loggers” 參數,將其設置為 False,上面提到的問題就可以解決了。例如:
8、使用旋轉文件句柄
如果你用 FileHandler 寫日志,文件的大小會隨著時間推移而不斷增大。最終有一天它會占滿你所有的磁盤空間。為了避免這種情況出現,你可以在你的生成環境中使用 RotatingFileHandler 替代 FileHandler。
9、如果你有多個服務器可以啟用一個專用的日志服務器
當你有多個服務器和不同的日志文件時,你可以創建一個集中式的日志系統來收集重要的(大多數情況是警告或者錯誤消息)信息。然后通過監測這些日志信息,你就可以很容易地發現系統中的問題了。
10、總結
Python 的日志庫設計得如此之好,真是讓人欣慰,我覺得這是標準庫中最好的一部分了,你不得不選擇它。它很靈活,你可以用你自己的 handler 或者 filter。已經有很多的第三方的 handler 了,比如 pyzmq 提供的 ZeroMQ 日志句柄,它允許你通過 zmq 套接字發送日志消息。如果你還不知道怎么正確的使用日志系統,這篇文章將會非常有用。有了很好的日志記錄實踐,你就能非常容易地發現系統中的問題。這是很非常值得投資的。:)
日志模塊使用總結:
????1、加載logging模塊2、創建一個logger,并設置service用戶記錄日志logger?=?logging.getLogger("service"),使用%(name)s記錄3、設置logger級別logger.setLevel(logging.DEBUG)4、創建一個handler,日志流向(文件或控制臺,默認是控制臺)#?創建一個handler,用于寫入日志文件fh?=?logging.FileHandler('access.log')#?再創建一個handler,用于輸出到控制臺ch?=?logging.StreamHandler()5、#?定義handler的輸出格式formatterformatter?=?logging.Formatter('%(asctime)s?-?%(filename)s[line:%(lineno)d]?-?%(name)s?-?%(levelname)s?-?%(message)s')fh.setFormatter(formatter)ch.setFormatter(formatter)6、給logger添加handlerlogger.addHandler(fh)logger.addHandler(ch)7、記錄一條日志logger.debug('logger?debug?message')logger.info('logger?info?message')logger.warning('logger?warning?message')logger.error('logger?error?message')logger.critical('logger?critical?message')
轉載于:https://blog.51cto.com/xuanwei/1954983