在現實生活中,記錄日誌很是重要。銀行轉帳時會有轉帳記錄;若是有出現什麼問題,人們能夠經過日誌數據來搞清楚到底發生了什麼。
對於系統開發、調試以及運行,記錄日誌都是一樣的重要。若是沒有日誌記錄,程序崩潰時你幾乎就沒辦法弄明白到底發生了什麼事情。
一、簡單使用
python
import logging logging.debug('debug message') logging.info('info message') logging.warn('warn message') logging.error('error message') logging.critical('critical message')
執行結果:
json
WARNING:root:warn message ERROR:root:error message CRITICAL:root:critical message
默認狀況下,logging模塊將日誌打印到屏幕上(stdout),日誌級別爲WARNING(即只有日誌級別高於WARNING的日誌信息纔會輸出),日誌格式以下圖所示:
1.1 日誌級別
bash
級別服務器 |
數字形式網絡 |
什麼時候使用python2.7 |
DEBUGide |
10函數 |
詳細信息,典型地調試問題時會感興趣。佈局 |
INFOthis |
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,因此全部的日誌信息均輸出出來了。
二、重要的概念
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能夠新增多個格式化器或多個過濾器,並且日誌級別將會繼承。
三、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等方法記錄日誌信息。
四、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.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.config def setup_logging( default_path='logging.json', default_level=logging.INFO, env_key='LOG_CFG' ): """Setup logging configuration """ path = default_path value = os.getenv(env_key, None) if value: path = value if 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.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 # 10MB backupCount: 20 encoding: utf8 error_file_handler: class: logging.handlers.RotatingFileHandler level: ERROR formatter: simple filename: errors.log maxBytes: 10485760 # 10MB 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 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_path value = os.getenv(env_key, None) if value: path = value if os.path.exists(path): with open(path, 'rt') as f: config = yaml.load(f.read()) logging.config.dictConfig(config) else: lo
六、使用 __name__ 做爲 logger 的名稱
雖然不是非得將 logger 的名稱設置爲 __name__ ,可是這樣作會給咱們帶來諸多益處。在 python 中,變量 __name__ 的名稱就是當前模塊的名稱。好比,在模塊 「foo.bar.my_module」 中調用 logger.getLogger(__name__) 等價於調用logger.getLogger(「foo.bar.my_module」) 。當你須要配置 logger 時,你能夠配置到 「foo」 中,這樣包 foo 中的全部模塊都會使用相同的配置。當你在讀日誌文件的時候,你就可以明白消息到底來自於哪個模塊。
七、捕捉異常並使用 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 logging logger = 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 logging def 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,上面提到的問題就能夠解決了。例如:
八、使用旋轉文件句柄
若是你用 FileHandler 寫日誌,文件的大小會隨着時間推移而不斷增大。最終有一天它會佔滿你全部的磁盤空間。爲了不這種狀況出現,你能夠在你的生成環境中使用 RotatingFileHandler 替代 FileHandler。
九、若是你有多個服務器能夠啓用一個專用的日誌服務器
當你有多個服務器和不一樣的日誌文件時,你能夠建立一個集中式的日誌系統來收集重要的(大多數狀況是警告或者錯誤消息)信息。而後經過監測這些日誌信息,你就能夠很容易地發現系統中的問題了。
十、總結
Python 的日誌庫設計得如此之好,真是讓人欣慰,我以爲這是標準庫中最好的一部分了,你不得不選擇它。它很靈活,你能夠用你本身的 handler 或者 filter。已經有不少的第三方的 handler 了,好比 pyzmq 提供的 ZeroMQ 日誌句柄,它容許你經過 zmq 套接字發送日誌消息。若是你還不知道怎麼正確的使用日誌系統,這篇文章將會很是有用。有了很好的日誌記錄實踐,你就能很是容易地發現系統中的問題。這是很很是值得投資的。:)
日誌模塊使用總結:
一、加載logging模塊 二、建立一個logger,並設置service用戶記錄日誌 logger = logging.getLogger("service"),使用%(name)s記錄 三、設置logger級別 logger.setLevel(logging.DEBUG) 四、建立一個handler,日誌流向(文件或控制檯,默認是控制檯) # 建立一個handler,用於寫入日誌文件 fh = logging.FileHandler('access.log') # 再建立一個handler,用於輸出到控制檯 ch = logging.StreamHandler() 五、# 定義handler的輸出格式formatter formatter = logging.Formatter('%(asctime)s - %(filename)s[line:%(lineno)d] - %(name)s - %(levelname)s - %(message)s') fh.setFormatter(formatter) ch.setFormatter(formatter) 六、給logger添加handler logger.addHandler(fh) logger.addHandler(ch) 七、記錄一條日誌 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')