Python Logging庫HTTPHandler的消息格式化的方法

問題

Python的logging庫是標準庫中用來實現日誌的庫,功能強大,並且使用起來也算是方便。該庫提供了不少個不一樣的Handler,用來對日誌進行不一樣的處理。例如FileHandler用來將日誌記錄到文件,RotateFileHandler用來將日誌記錄到文件並且支持日誌文件滾動備份,還有本文中所說的HttpHandler,能夠將日誌經過HTTP請求發送到服務器上。api

使用Python的logging模塊的過程大約有以下幾個步驟:服務器

  1. 根據配置文件、配置字典或者調用方法的方式初始化日誌配置,並獲取一個logger。函數

  2. 調用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")

上面的中的中文註釋部分直接說明了解決方案。

相關文章
相關標籤/搜索