python logging模块Filters过滤器介绍,如何使用自定义的过滤器

文章目录

    • 一、在代码中定义及使用Filter
    • 二、logging.Filter 类filter(record)方法
    • 三、在配置文件中配置自定义的Filter

python的logging模块中,Filters 可被 HandlersLoggers 用来实现比按层级提供更复杂的过滤操作。

一、在代码中定义及使用Filter

先给一个直接使用的示例:

import logging

logger = logging.getLogger('dev')

hdl = logging.StreamHandler()
fmt = logging.Formatter("%(asctime)s - %(name)s - %(levelname)s - %(message)s")
hdl.setFormatter(fmt)

# 重写过滤器类
class MyFilter(logging.Filter):
    def __init__(self, name):
        super().__init__(name=name)

    def filter(self, record):
        if record.levelno <= logging.WARNING:
            return True
        return False

flt = MyFilter('dev')
# 将过滤器应用到hdl处理程序
hdl.addFilter(flt)
logger.addHandler(hdl)
# 将过滤器应用到logger日志器
# logger.addFilter(flt)

logger_test1 = logging.getLogger('dev.test1')

logger.debug("This is a debug log.")
logger.info("This is an info log.")
logger.warning("This is a warning log.")
logger.error("This is an error log.")
logger.critical("This is a critical log.")

logger_test1.debug("This is a debug log.")
logger_test1.info("This is an info log.")
logger_test1.warning("This is a warning log.")
logger_test1.error("This is an error log.")
logger_test1.critical("This is a critical log.")

运行代码,输出:

2023-08-20 22:49:40,849 - dev - WARNING - This is a warning log.
2023-08-20 22:49:40,849 - dev.test1 - WARNING - This is a warning log.

解释:

  • 定义了一个MyFilter类,根据filter(record) 返回值决定是否要记录指定的记录?返回零表示否,非零表示是。示例中表示过滤掉高于logging.WARNING级别的日志。实例化MyFilter时,传了一个dev参数,它表示将对dev名称的日志器生效,然后将过滤器应用到处理程序hdl,然后hdl应用到logger日志器。
  • 输出结果中只有WARNING级别的日志,这是因为没有给loggerlogger_test1单独设置level级别,因此它默认使用了root根日志级别WARNING,因此WARNING以下级别的日志给过滤掉了,而自定义的过滤器,把高于WARNING级别的日志给过滤掉了,所以只剩下WARNING级别的日志。
  • 另外,你会发现,虽然没有给logger_test1配置处理程序、过滤器、格式化器等,但是它最终使用的是logger相同的配置,这是因为logger_test1 = logging.getLogger('dev.test1')dev.test1使用了日志器的层级关系。基本过滤器类只允许低于日志记录器层级结构中低于特定层级的事件。 例如,一个用 ‘A.B’ 初始化的过滤器将允许 ‘A.B’, ‘A.B.C’, ‘A.B.C.D’, ‘A.B.D’ 等日志记录器所记录的事件。 但 ‘A.BB’, ‘B.A.B’ 等则不允许。

将示例中的代码做一个小的改动:把hdl.addFilter(flt)注释掉,把# logger.addFilter(flt)取消注释,然后重新运行代码。

输出结果:

2023-08-20 23:16:49,507 - dev - WARNING - This is a warning log.
2023-08-20 23:16:49,507 - dev.test1 - WARNING - This is a warning log.
2023-08-20 23:16:49,507 - dev.test1 - ERROR - This is an error log.
2023-08-20 23:16:49,507 - dev.test1 - CRITICAL - This is a critical log.

解释:

  • 会发现,自定义的过滤器,不再对logger_test1日志器生效了。

    这是因为关联到处理程序的过滤器会在事件由处理程序发出之前被查询,而关联到日志记录器的过滤器则会在有事件被记录的的任何时候(使用 debug(), info()等等)在将事件发送给处理程序之前被查询。 这意味着由后代日志记录器生成的事件将不会被日志记录器的过滤器设置所过滤,除非该过滤器也已被应用于后代日志记录器。

二、logging.Filter 类filter(record)方法

在自定义过滤器时,filter(record)方法中,record有哪些属性可用呢?

下面写了一段代码,来探究一下(当然更推荐你去看logging模块的源码):

import logging
import time

class MyFilter(logging.Filter):
    def filter(self, record):
        time.sleep(1)
        for i in dir(record):
            if not i.startswith("__"):
                print(i,'=',eval('record.'+i))
        print('getMessage()','=',record.getMessage())
        return True

def mylogger():
    logger = logging.getLogger()
    fmt = logging.Formatter(fmt="%(asctime)s - %(message)s",datefmt="%Y-%m-%d %H:%M:%S")
    hld = logging.StreamHandler()
    hld.setFormatter(fmt)

    logger.addFilter(MyFilter())
    logger.addHandler(hld)
    return logger

if __name__ == '__main__':
    logger = mylogger()

    def myfunc():
        try:
            ttt = 1/0
        except Exception as err:
            logger.exception('The args: %s,%s','arg1','arg2',exc_info=True,stack_info=True)

    time.sleep(1)
    myfunc()

运行代码,输出:

2023-08-20 23:32:11 - The args: arg1,arg2
Traceback (most recent call last):
  File "E:/Chen/python3/ExciseA/config/test.py", line 49, in myfunc
    ttt = 1 / 0
ZeroDivisionError: division by zero
Stack (most recent call last):
  File "E:/Chen/python3/ExciseA/config/test.py", line 55, in 
    myfunc()
  File "E:/Chen/python3/ExciseA/config/test.py", line 51, in myfunc
    logger.exception('The args: %s,%s', 'arg1', 'arg2', exc_info=True, stack_info=True)
args = ('arg1', 'arg2')
created = 1692554731.9882045
exc_info = (, ZeroDivisionError('division by zero'), )
exc_text = None
filename = test.py
funcName = myfunc
getMessage = >
levelname = ERROR
levelno = 40
lineno = 51
module = test
msecs = 988.2044792175293
msg = The args: %s,%s
name = root
pathname = E:/Chen/python3/ExciseA/config/test.py
process = 13384
processName = MainProcess
relativeCreated = 1015.1534080505371
stack_info = Stack (most recent call last):
  File "E:/Chen/python3/ExciseA/config/test.py", line 55, in 
    myfunc()
  File "E:/Chen/python3/ExciseA/config/test.py", line 51, in myfunc
    logger.exception('The args: %s,%s', 'arg1', 'arg2', exc_info=True, stack_info=True)
thread = 10072
threadName = MainThread
getMessage() = The args: arg1,arg2
属性名称 描述
args 合并到 msg 以产生 message 的包含参数的元组,或是其中的值将被用于合并的字典(当只有一个参数且其类型为字典时)。
created LogRecord 被创建的时间(即time.time()的返回值)。
exc_info 异常元组 (例如 sys.exc_info) 或者如未发生异常则为 None
exc_text 用来缓存traceback文本内容的,好像写死为了None
filename 发生日志记录调用所在的文件的名称,pathname 的文件名部分
funcName 日志记录调用所在的函数
levelname 消息文本记录级别 ('DEBUG', 'INFO', 'WARNING', 'ERROR', 'CRITICAL').
levelno 消息数字记录级别 (DEBUG, INFO, WARNING, ERROR, CRITICAL).
lineno 发出日志记录调用所在的源行号(如果可用)。
module 模块 (filename 的名称部分)。
msecs 日志被创建的时间的毫秒部分。
msg 在原始日志记录调用中传入的格式字符串。 与 args 合并以产生 message(注意:record属性中没有直接提供message,可以通过record.getMessage()间接获取)
name 用于记录调用的日志记录器名称。
pathname 发出日志记录调用的源文件的完整路径名(如果可用)。
process 进程ID(如果可用)
processName 进程名(如果可用)
relativeCreated 以毫秒数表示的LogRecord被创建的时间,即相对于 logging 模块被加载时间的差值。
stack_info 当前线程中从堆栈底部起向上直到包括日志记录调用并导致创建此记录的堆栈帧的堆栈帧信息(如果可用)。
thread 线程ID(如果可用)
threadName 线程名(如果可用)

getMessage()在将LogRecord实例与任何用户提供的参数合并之后,返回此实例的消息。 如果用户提供给日志记录调用的消息参数不是字符串,则会在其上调用str()以将它转换为字符串。

三、在配置文件中配置自定义的Filter

日志模块化配置通常是更好的实践方式,接下来将举例如何在配置文件中配置自定义的过滤器。

大致思路是:

1)在配置文件中配置过滤器(这里选yaml格式配置文件);

2)将自定义的过滤器,单独放到一个模块中供调用;

3)通过logging.config.dictConfig()加载日志配置文件;

4)使用添加了过滤器的日志器

示例项目目录结构:

.
│ main.py
└─config
    │ myfilter.py	
    │ logger.py
    └─logconf
        log.yaml

log.yaml

version: 1
disable_existing_loggers: false
formatters:
  simple:
    format: '[%(asctime)s - %(name)s - %(levelname)-8s] %(message)s'
handlers:
  console:
    class: logging.StreamHandler
    formatter: simple
    level: DEBUG
    stream: ext://sys.stdout
    filters:
      - myfilter
loggers:
  mylogger:
    level: DEBUG
    handlers:
      - console
    propagate: no
#    filters: [myfilter]
filters:
  myfilter:
    (): config.myfilter.MyFilter
    name: mylogger
    param1: False
    param2: True

log.yamlfilters内配置了一个ID为myfilter的过滤器,其中()键表明config.myfilter.MyFilter是一个自定义的类对象,nameparam1param2均是传递给该自定义过滤器类实例化时的参数。然后将该过滤器应用到了handlersconsole处理程序。

myfilter.py

import logging

class MyFilter(logging.Filter):

    def __init__(self, name, param1=True, param2=True):
        super().__init__(name=name)
        self.param1 = param1
        self.param2 = param2

    def filter(self, record):
        allow = False
        if self.param1 == False:
            allow = allow or record.levelno < logging.ERROR
        if self.param2 == True and record.levelname == 'WARNING':
            record.msg = 'This WARNING messages modified by my filter.'+record.msg
        return allow

myfilter.py 自定义一个简单的过滤器,该过滤器功能:当param1=False时,仅返回低于logging.ERROR级别的日志记录;当param2=True且日志级别为WARNING时,修改msg信息。

logger.py

import logging.config
import yaml
import os

class Logger():

    def __init__(self,logger_name=''):
        self.logger_name = logger_name
        cur_dir = os.path.dirname(__file__)
        log_conf = os.path.join(cur_dir, 'logconf', 'flog.yaml')
        with open(log_conf, 'r') as f:
            config = yaml.safe_load(f.read())
            logging.config.dictConfig(config)

    def logger(self):
        logger = logging.getLogger(self.logger_name)
        return logger

logger.py 通过config=yaml.safe_load(f.read())读取log.yaml日志配置文件,logging.config.dictConfig(config)加载日志配置文件,完成初始化日志配置。logger()方法返回一个应用了配置的logger日志器。

main.py

from config.logger import Logger

logger = Logger('mylogger').logger()

if __name__ == '__main__':
    logger.debug("dddebug")
    logger.info("iiinfo")
    logger.warning("wwwarning")
    logger.error("eeerror")
    logger.critical("cccritical")

main.py 使用logger.py中封装的方法,实例化了一个logger日志器,供日志记录使用。

运行main.py,输出:

[2023-08-21 00:32:43,810 - mylogger - DEBUG   ] dddebug
[2023-08-21 00:32:43,810 - mylogger - INFO    ] iiinfo
[2023-08-21 00:32:43,810 - mylogger - WARNING ] This WARNING messages modified by my filter.wwwarning

从运行结果看,过滤器配置已生效。

最后:

  • logging.config.dicConfig() 可以支持Filters过滤器配置,logging.config.fileConfig() 不支持.
  • 在3.2版更改: 不需要创建专门的Filter类,或使用具有filter方法的其他类:你可以使用一个函数(或其他可调用对象)作为过滤器。 过滤逻辑将检查过滤器对象是否文化的filter属性:如果有,就会将它当作是Filter并调用它的filter()方法。


参考资料:

字典配置,用户定义对象

利用 dictConfig() 定义过滤器

你可能感兴趣的:(Python,python)