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")
上面的中的中文注释部分直接说明了解决方案。