python的logging模块中,Filters
可被 Handlers
和 Loggers
用来实现比按层级提供更复杂的过滤操作。
先给一个直接使用的示例:
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
级别的日志,这是因为没有给logger
、logger_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()
等等)在将事件发送给处理程序之前被查询。 这意味着由后代日志记录器生成的事件将不会被日志记录器的过滤器设置所过滤,除非该过滤器也已被应用于后代日志记录器。
在自定义过滤器时,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()
以将它转换为字符串。
日志模块化配置通常是更好的实践方式,接下来将举例如何在配置文件中配置自定义的过滤器。
大致思路是:
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.yaml
中filters
内配置了一个ID为myfilter
的过滤器,其中()
键表明config.myfilter.MyFilter
是一个自定义的类对象,name
、param1
、param2
均是传递给该自定义过滤器类实例化时的参数。然后将该过滤器应用到了handlers
下console
处理程序。
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()
不支持.Filter
类,或使用具有filter
方法的其他类:你可以使用一个函数(或其他可调用对象)作为过滤器。 过滤逻辑将检查过滤器对象是否文化的filter
属性:如果有,就会将它当作是Filter
并调用它的filter()
方法。参考资料:
字典配置,用户定义对象
利用 dictConfig() 定义过滤器