Python自動化運維之常用模塊—logging
在現實生活中,記錄日誌非常重要。銀行轉賬時會有轉賬記錄;如果有出現什麽問題,人們可以通過日誌數據來搞清楚到底發生了什麽。
對於系統開發、調試以及運行,記錄日誌都是同樣的重要。如果沒有日誌記錄,程序崩潰時你幾乎就沒辦法弄明白到底發生了什麽事情。
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‘)
執行結果:
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.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
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 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,上面提到的問題就可以解決了。例如:
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的輸出格式formatter formatter = logging.Formatter(‘%(asctime)s - %(filename)s[line:%(lineno)d] - %(name)s - %(levelname)s - %(message)s‘) fh.setFormatter(formatter) ch.setFormatter(formatter) 6、給logger添加handler logger.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‘)
本文出自 “炫維” 博客,請務必保留此出處http://xuanwei.blog.51cto.com/11489734/1954983
Python自動化運維之常用模塊—logging