DEBUG.WIKI
ZgoCloud:多地区部署,按需选节点;日本、美国、香港、德国
PanstarCloud:部署应用,接入 AI,云服务器与 AI 网关
OnlineSIM:在线接收短信,虚拟号码租用

pytest caplog 一条日志出现两次:测试中切换 propagate 的影响

logger 在 pytest 建立捕获时不向根传播,测试内再将 propagate 改为 True,同一个捕获 handler 可能在本级与根各处理一次。稳定传播策略可避开这条重复路径。

业务只调用一次 logger.warning(),caplog.messages 却得到两条相同文本。如果 logger 在 pytest 开始捕获时设置了 propagate=False,测试中又改为 True,先检查这次传播切换,而不要直接认定业务调用了两次。

下面的测试保留了这个顺序:模块导入时关闭传播,测试内重新打开。

import logging

logger = logging.getLogger("example")
logger.propagate = False

def test_log_is_captured_once(caplog):
    logger.propagate = True
    logger.warning("only once")
    assert caplog.messages == ["only once"]

这条路径在 pytest 9.2.0.dev0 出现过重复捕获。正式 9.2.0 的捕获建立过程仍将同一 handler 挂到根 logger 和当时不传播的 logger 上,不能假定动态切换已经被特殊处理。

一个记录经过同一 handler 两次

不传播的 logger 无法把记录送到根,pytest 因此需要在它本身挂捕获 handler。后来打开传播,该 handler 仍留在本级;标准日志分派先调用本级 handler,再向根传播,又调用根上的同一对象。

类似现象也可能出现在失败报告、终端日志或日志文件。应用自己重复挂了 handler 也会造成相同文本,应检查实际 logger 链与 handler 对象,不能仅凭“两行相同”确定就是本题。

能固定传播策略时,在捕获前完成初始化

如果应用设计本来要求向根传播,将它作为稳定初始化策略,在 pytest 建立本次捕获之前完成,测试执行期间保持不变。若业务确实要求不传播,也应在该阶段保持不传播,让捕获使用对应的本级路径。修改一个已开始的 caplog 测试里的开头几行,并不等于改在捕获之前。

这项调整以业务允许固定传播策略为前提。如果测试本来就要验证 False 到 True 的变化,不能删除切换来宣称原测试通过;应保留那项行为测试,并单独记录当前捕获机制的限制,区分业务真实发出次数与 pytest 的处理次数。

不要全局按消息文本去重。两次合法调用可能拥有完全相同的文本,去重会吞掉应被检测的重复业务事件。也不应在测试中随意移除 pytest 的私有 handler,这会影响其他捕获输出与清理责任。

相反方向的切换也要留意:一个原本传播的 logger 在捕获后关闭传播,可能不在初始化时的本级挂载列表里,日志因此漏捕。固定策略解决的是这一组动态路由条件,并非任何日志配置问题。

验收应保留正常日志插件,分别覆盖稳定传播、稳定不传播与业务实际需要的切换场景,同时确认结束后配置恢复。只把断言改为两条、禁用日志插件或降低日志级别,都不能证明原来的“只发一次且捕获一次”要求已经满足。