如何在Python logging模块中为pytest测试添加自定义日志级别
问题场景
我正在使用pytest框架,需要记录测试失败时可被捕获并展示的日志输出。为实现该目标,我需要为默认pytest日志器新增一个自定义日志级别:由于应用本身会输出INFO级别的业务日志,测试过程中需要避免捕获这类日志,因此无法直接将日志捕获级别设置为INFO。
我参考Python logging工具添加自定义日志级别的相关方案编写了实现代码,但运行失败,抛出的错误在对应方案的评论区已有用户提及,但无对应解决方案,错误信息如下:
AttributeError: module 'logging' has no attribute 'test_info'
需要明确正确添加自定义日志级别的实现方式。
复现代码
基于pytest fixture与测试用例的复现
import logging import pytest @pytest.fixture(scope='session', autouse=True) def logging_format(level_name: str='TEST_INFO', level_number: int=31): def log_test_info(self, message, *args, **kwargs): if self.isEnabledFor(level_number): self._log(level_number, message, args, **kwargs) logging.addLevelName(level_number, level_name) setattr(logging, level_name, level_number) setattr(logging.getLoggerClass(), level_name.lower(), log_test_info) def test(): logging.test_info('Some log data.') assert True
不依赖pytest的纯Python复现场景
为确认问题并非pytest导致,我在无pytest的纯Python环境下也复现了相同问题,代码如下:
import logging level_name = 'TEST_INFO' level_number = 31 def log_test_info(self, message, *args, **kwargs): if self.isEnabledFor(level_number): self._log(level_number, message, args, **kwargs) logging.addLevelName(level_number, level_name) setattr(logging, level_name, level_number) setattr(logging.getLoggerClass(), level_name.lower(), log_test_info) logging.test_info('Some log data.') assert True
问题原因
代码报错的核心原因有两个:
- 属性名大小写不匹配:代码中给
logging模块设置的是大写的TEST_INFO属性(对应日志级别数值常量,和内置的logging.INFO常量命名规则一致),但调用时写的是小写开头的logging.test_info,模块下不存在这个属性,自然抛出AttributeError。 - 缺少模块级调用方法的绑定:
logging.xxx()这种直接通过模块调用日志的写法,是logging模块单独封装的便捷方法,不是logger类的实例方法,仅给logger类绑定实例方法无法支持模块级调用。
正确实现方案
纯Python环境下的自定义日志级别实现
import logging # 自定义日志级别配置,数值设为31高于WARNING(30)和INFO(20),刚好满足过滤INFO业务日志的需求 LEVEL_NAME = "TEST_INFO" LEVEL_NUMBER = 31 def logger_instance_log(self, message, *args, **kwargs): """支持logger实例调用,比如logger.test_info()""" if self.isEnabledFor(LEVEL_NUMBER): self._log(LEVEL_NUMBER, message, args, **kwargs) def module_level_log(message, *args, **kwargs): """支持logging模块直接调用,比如logging.test_info()""" root_logger = logging.getLogger() if root_logger.isEnabledFor(LEVEL_NUMBER): root_logger._log(LEVEL_NUMBER, message, args, **kwargs) # 1. 注册日志级别数值和名称的映射关系 logging.addLevelName(LEVEL_NUMBER, LEVEL_NAME) # 2. 给logging模块绑定大写的级别数值常量,和内置logging.INFO这类常量的规则保持一致 setattr(logging, LEVEL_NAME, LEVEL_NUMBER) # 3. 给logger类绑定小写的实例日志方法,和内置logger.info()这类调用规则保持一致 setattr(logging.getLoggerClass(), LEVEL_NAME.lower(), logger_instance_log) # 4. 给logging模块绑定小写的模块级便捷调用方法,和内置logging.info()这类调用规则保持一致 setattr(logging, LEVEL_NAME.lower(), module_level_log) # 测试验证 logging.basicConfig(level=LEVEL_NUMBER) logging.test_info("模块级自定义日志输出正常") custom_logger = logging.getLogger("test") custom_logger.test_info("logger实例自定义日志输出正常")
pytest场景适配
把上述自定义日志级别的初始化逻辑放到session级、autouse=True的fixture中执行,同时在pytest配置文件中指定日志捕获级别为自定义的TEST_INFO,即可实现仅捕获测试专用日志、过滤INFO级别业务日志的需求,pytest.ini配置示例:
[pytest] log_level = TEST_INFO log_cli_level = TEST_INFO
内容的提问来源于stack exchange,提问作者David Gard
相关产品推荐
相关产品推荐

