Python的logging庫是標準庫中用來實現日誌的庫,功能強大,並且使用起來也算是方便。該庫提供了不少個不一樣的Handler,用來對日誌進行不一樣的處理。例如FileHandler用來將日誌記錄到文件,RotateFileHandler用來將日誌記錄到文件並且支持日誌文件滾動備份,還有本文中所說的HttpHandler,能夠將日誌經過HTTP請求發送到服務器上。api
使用Python的logging模塊的過程大約有以下幾個步驟:服務器
根據配置文件、配置字典或者調用方法的方式初始化日誌配置,並獲取一個logger。函數
調用logger實例的以下方法來發出一條日誌:critical, error, warning, info, debug。這些方法的定義以下,以info爲例:性能
logger.info(fmt, *args, exc_info, extra)
P.S. 本文的目的不是說明logging如何使用,因此具體的用法請參考官方文檔。url
當logger對象調用info等方法發出一條日誌時,他能夠接受像C語言中的printf函數或者Python3中的pritnf函數同樣的前兩個參數:格式化字符串和對應的參數列表,用來表示要發出的日誌的內容。當logging模塊真的要發出這條日誌時,纔會對字符串進行格式化,而且加入最終的日誌字符串中。所以,在Python參考手冊(第4版)中(19.7節,289頁)有強調了以下這一點:發出日誌消息時,應該避免在發出消息時帶有字符串格式化的代碼(即格式化一條消息,而後把結果傳遞到日誌記錄模塊中)。緣由是,直接傳遞格式化後的字符串會致使參數被徹底求值,這個有多是非必要的,會致使日誌性能降低。舉個例子:debug
正確方式: logger.info("hello, %s", "myname") 錯誤方式: logger.info("hello, %s" % "myname")
那麼問題來了,若是一個logger的handler使用了HttpHandler,這個坑爹貨竟然不會在發出日誌前對日誌內容部分進行格式化,而是隻發送了前面的fmt字符串到http服務器,結果就像下面這樣:日誌
WARNING Tue Jan 27 15:27:34 2015 admin.config 192.168.100.126 POST /user/login User [%s] logged in failed.
而咱們期待的應該是:code
WARNING Fri Jan 23 11:36:45 2015 admin.config 192.168.100.126 POST /user/login User [admin] logged in failed.orm
使用logging模塊提供的Filter功能。對象
直接給出實例代碼:
# -*- coding: utf-8 -*- import logging import logging.config import logging.handlers log_config_dict = { "version": 1, "formatters": { "format_def": { "format": "%(levelname)-8s %(asctime)s %(name)s %(ip)s " "%(method)s %(path)s %(message)s", }, }, "handlers": { "handler_http": { "class": "logging.handlers.HTTPHandler", "formatter": "format_def", "level": "INFO", "host": "192.168.100.1:8888", "url": "/log/admin", "method": "POST", }, }, "loggers": { "admin.config": { "level": "INFO", "propagate": 0, "handlers": ["handler_http"], }, "admin.api": { "level": "INFO", "propagate": 0, "handlers": ["handler_http"], } }, } class RequestFilter(logging.Filter): """A filter used to add extra information to a record. Add ip, method and path information to a record for a HTTP request. Attributes: name: logger's name """ def __init__(self, name): self.name = name def filter(self, record): # 這裏調用getMessage()方法獲得格式化後的日誌內容, # HTTP服務器上只要讀取POST中的message參數便可。 record.message = record.getMessage() return True def init_log(): logging.config.dictConfig(log_config_dict) def get_logger(name): if type(name) is not str: return None log = logging.getLogger(name) log.addFilter(RequestFilter(name)) # 添加一個過濾器用來進行消息格式化 log.addHandler(logging.NullHandler()) return log def get_config_logger(): return get_logger("admin.config") def get_api_logger(): return get_logger("admin.api")
上面的中的中文註釋部分直接說明了解決方案。