python3 自定义logging.Handler, Formatter, Filter模块

在日常使用logging模块中,我们常会使用到官方提供的FileHandler,StreamHandler,RotatingFileHander等,详细参考官方文档, 这些模块都是继承来自与logging.Handler这个父类,而Handler主要用来自定义日志对象的规则(比如:将日志输出到什么地方,哪些日志进行输出、以及日志输出的格式等)。虽然官方提供了很多实现好的Handler,但总有一些特殊情况需要自定义输出日志。比如下面的示例中要将日志输出到kafka集群中,此时我们需要自定义Handler对象。
由于本示例会涉及继承并自定义Handler, Formatter, Filter三个类,所以先描述下本示例的需求。
1)通过Filter类对日志信息进行过滤
2)通过Formatter类对日志信息进行格式化,这里以输出json格式为例
3)通过Handler类将日志输出到kafka


0 常见format参数说明

%(levelno)s: 打印日志级别的数值
%(levelname)s: 打印日志级别名称
%(pathname)s: 打印当前执行程序的路径,其实就是sys.argv[0]
%(filename)s: 打印当前执行程序名
%(funcName)s: 打印日志的当前函数
%(lineno)d: 打印日志的当前行号
%(asctime)s: 打印日志的时间
%(thread)d: 打印线程ID
%(threadName)s: 打印线程名称
%(process)d: 打印进程ID
%(message)s: 打印日志信息

1 实现自定义Handler

首先贴出Handler官方文档,下面继承logging.Handler方法进行自定义输出,主要是去实现emit方法。由于需要连接kafka,所以需要安装kafka-python包。

import json
from kafka import KafkaProducer
from kafka.errors import KafkaError
import logging
import datetime


class KafkaLoggingHandler(logging.Handler):
    """
    自定义logging.Handler模块,自定义将日志输出到指定位置(这里是输出到kafka)
    """
    def __init__(self, config=None, topic=None, name=""):
        super(KafkaLoggingHandler, self).__init__()

        if isinstance(config, dict) is False:
            raise ValueError("lack of kafka config parameters...")
        if isinstance(topic, str) is False:
            raise ValueError("lack of kafka topic parameters...")
        self.name = name
        self.config = config
        self.producer = KafkaProducer(**self.config)
        self.topic = topic

        # 实例化自定义的日志过滤器
        filter = KafkaLogFilter()
        self.addFilter(filter)

        # 实例化自定义的日志格式化对象
        json_format = JsonForMatter()
        self.setFormatter(json_format)

    @staticmethod
    def on_send_success(record_metadata):
        # 如果消息成功写入Kafka,broker将返回RecordMetadata对象(包含topic,partition和offset
        print("Success: [{}] send success".format(record_metadata))

    @staticmethod
    def on_send_error(excp):
        # 如果失败broker将返回error。这时producer收到error会尝试重试发送消息几次,直到producer返回error
        print("INFO " + "send info failed, cause: {}".format(excp))

    def emit(self, record):
        """
        重写logging.Handler的emit方法
        :param record: 传入的日志信息
        :return:
        """
        # 对日志信息进行格式化
        value = self.format(record)
        # 转成json格式,注意ensure_ascii参数设置为False,否则中文乱码
        value = json.dumps(value, ensure_ascii=False).encode("utf-8")
        future = self.producer.send(topic=self.topic, value=value)
        try:
            record_metadata = future.get(timeout=10)
            self.on_send_success(record_metadata)
        except KafkaError as e:
            self.on_send_error(e)

首先在类的初始化函数中,传入了两个变量,config和topic(name暂不用管),其中config是一个字典类,主要传入连接kafka的集群ip以及端口号,格式如下:

config = {
    "bootstrap_servers": ["192.168.10.2:9092", 
                          "192.168.10.2:9092"]
}

topic就是写入kafka的topic字段。接着在emit方法中将传入的日志信息写入kafka。其中self.format就是后面我们会自定义的Formatter模块(后面会讲),对我们的日志信息进行格式化(转成字典类型,方便后续json化)。


2 自定义Formatter

自定义Formatter类主要是为了自定义格式化输出,在下面代码中,主要是重写format方法,将传入的record LogRecord变量类型转为dict类型,在LogRecord变量中存储有很多属性值,这里我们就简单使用其中的filenamelinenomodule以及msg这四个属性,然后我们自己在加个时间tm属性。

SAVE_ATTR = ["filename", "lineno", "module", "msg"]


class JsonForMatter(logging.Formatter):
    """
    对日志信息进行自定义格式化
    """

    def format(self, record):
        """
        重写logging.Formatter的format方法
        :param record: 传入的日志信息
        :return:
        """
        msg = self.translate(record)
        self.set_format_time(msg)
        return msg
    
    # translate LogRecord to dict
    @staticmethod
    def translate(record):
        # 只保留SAVE_ATTR列表中的属性
        d = {attr_name: record.__dict__[attr_name]
             for attr_name in record.__dict__
             if attr_name in SAVE_ATTR}
        return d

    @staticmethod
    def set_format_time(msg):
        now = datetime.datetime.utcnow()
        format_time = now.strftime("%Y-%m-%d %H:%M:%S" + ".%03d" % (now.microsecond / 1000))
        msg["tm"] = format_time

3 自定义Filter

通过自定义Filter类,能够实现哪些日志进行输出,哪些日志直接丢弃。主要是重写filter方法,如果返回True则保留该日志,如果返回False则丢弃该日志。

class KafkaLogFilter(logging.Filter):
    """
    自定义logging过滤器,决定哪些日志(kafka相关)进行输出
    只有通过该过滤器的日志才会触发logging.Handler的emit方法
    """

    def __init__(self, name="werkzeug"):  # werkzeug是WSGI的工具包,其日志中包含着访问端口的记录
        super(KafkaLogFilter, self).__init__()
        self.name = name

    def filter(self, record):
        """
        重写logging.Filter的filter方法
        只将warning等级和error等级的日志进行输出
        :param record: 传入的日志信息
        :return: 如果返回true则保留该日志,如果为false则丢弃
        """
        if record.__dict__["levelname"] in ["WARNING", "ERROR"]:
            return True
        # elif record.name == self.name:  # 当log的信息来自于werkzeug时进行简单的print
        #     if "args" in record.__dict__:
        #         print(record.__dict__["msg"] % (record.__dict__["args"]))
        #     else:
        #         print(record.__dict__["msg"])
        #     return False
        else:
            return False

使用测试

log = logging.getLogger()
kafka = KafkaLoggingHandler()
log.setLevel(logging.INFO)
log.addHandler(kafka)

log.info("test1")     # 会被屏蔽
log.warning("test2")  # 写入kafka
log.error("test3")    # 写入kafka
  • 5
    点赞
  • 15
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
`logging.Filter` 是 Python 日志模块中的一个类,用于过滤日志记录。通过继承 `logging.Filter` 类并实现 `filter` 方法,我们可以自定义过滤器来控制哪些日志记录应该被处理。 以下是一个示例,演示如何使用 `logging.Filter` 过滤掉级别为 `INFO` 的日志记录: ```python import logging class InfoFilter(logging.Filter): def filter(self, record): return record.levelno != logging.INFO logger = logging.getLogger() logger.setLevel(logging.DEBUG) handler = logging.StreamHandler() handler.setLevel(logging.DEBUG) handler.addFilter(InfoFilter()) formatter = logging.Formatter('%(asctime)s - %(name)s - %(levelname)s - %(message)s') handler.setFormatter(formatter) logger.addHandler(handler) logger.debug('debug message') logger.info('info message') logger.warning('warning message') logger.error('error message') logger.critical('critical message') ``` 在上面的示例中,我们创建了一个名为 `InfoFilter` 的自定义过滤器,并实现了其 `filter` 方法。在这个方法中,我们检查日志记录的级别是否为 `INFO`,如果是,就返回 False,表示这个日志记录不应该被处理。 然后,我们创建了一个 `StreamHandler` 处理器,并将其级别设置为 `DEBUG`。我们还将 `InfoFilter` 添加到处理器中,以便过滤掉级别为 `INFO` 的日志记录。最后,我们设置了日志记录的格式,并将处理器添加到日志记录器中。 如果您运行上面的代码,您将看到只有级别高于 `INFO` 的日志记录被处理,级别为 `INFO` 的日志记录被过滤掉了。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

太阳花的小绿豆

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值