如何在pytest捕获的日志中显示logging extra字典中的动态键值对?
如何在pytest捕获的日志中显示logging extra字典中的动态键值对?
我完全理解你的痛点——因为structlog生成的extra字典里的键是动态变化的,没法提前硬编码到log_cli_format里,默认的pytest日志格式化器根本没法把这些动态上下文内容显示出来。下面给你一个精准解决这个问题的方案:
核心思路
我们需要自定义一个日志格式化器,它能自动遍历日志记录中所有来自extra的动态键值对,把它们转换成key=value的格式附加到日志消息末尾,不需要提前知道键名。然后让pytest使用这个自定义格式化器来处理捕获的日志。
具体实现步骤
1. 编写自定义格式化器(通过conftest.py复用)
在你的测试目录下创建conftest.py文件,添加以下代码:
import logging class DynamicExtraFormatter(logging.Formatter): def format(self, record): # 先获取默认格式化后的日志消息 original_message = super().format(record) # 筛选出所有来自extra的动态键值对(排除logging自带的默认属性) extra_props = {} for key, value in record.__dict__.items(): if key not in logging.LogRecord.__dict__: extra_props[key] = value # 如果有动态属性,就拼接成key=value的字符串附加到消息后 if extra_props: kv_string = ' '.join(f"{key}={repr(value)}" for key, value in extra_props.items()) return f"{original_message} {kv_string}" return original_message def pytest_configure(config): # 给pytest的CLI日志处理器设置自定义格式化器 log_plugin = config.pluginmanager.get_plugin("logging") log_plugin.log_cli_handler.setFormatter( DynamicExtraFormatter(config.getini("log_cli_format")) ) # 给测试捕获日志的处理器也设置自定义格式化器 for handler in logging.getLogger().handlers: # 识别pytest的捕获日志文件处理器 if hasattr(handler, "baseFilename") and "pytest" in handler.baseFilename: handler.setFormatter( DynamicExtraFormatter(config.getini("log_format")) )
2. 配置pytest日志格式(可选,推荐)
在项目根目录创建pytest.ini,设置默认的日志格式(也可以通过命令行参数传递):
[pytest] log_cli_level = INFO log_cli_format = %(levelname)s %(name)s:%(filename)s:%(lineno)s %(message)s
3. 验证你的测试代码
用你提供的 reproducer 测试:
import pytest import logging logger = logging.getLogger() def test_logging(): logger.info('message here', extra={"foo": "bar", "baz": "qux"}) assert False, 'Trigger failure'
运行命令:
pytest --log-cli-level INFO
此时输出的捕获日志就会包含动态的extra内容:
----------------------------------------- Captured log call ----------------------------------------- INFO root:test_me.py:8 message here foo='bar' baz='qux' ====================================== short test summary info ====================================== FAILED test_me.py::test_logging - AssertionError: Trigger failure assert False ========================================= 1 failed in 0.08s =========================================
为什么这个方法有效?
当你调用logger.info(..., extra=dict)时,Python的logging模块会把extra字典里的所有键值对直接注入到日志记录对象(LogRecord)的属性中。我们的自定义格式化器通过对比LogRecord的默认属性,筛选出这些动态注入的键值,然后自动格式化成可读的字符串。
这个方案完美适配structlog的场景——不管structlog生成的extra里有什么动态键名,都会被自动捕获并显示。
备注:内容来源于stack exchange,提问作者Charles Duffy
相关产品推荐
相关产品推荐

