pytest 日志不打印问题排查与修复记录
一、问题现象
在执行 pytest -s test_http_client_request.py 时,HTTPClient 发出的请求没有任何日志输出,但代码中明明写了 logger.info(...)。
二、修改前的代码(问题代码)
python
import requests
import logging
# 1、初始化日志
logging.basicConfig(level=logging.INFO) # ❌ 在 pytest 下无效
logger = logging.getLogger(__name__)
# 2. 创建格式器
formatter = logging.Formatter('%(asctime)s - %(levelname)s - %(message)s')
# 3. 创建并配置控制台处理器 (输出到屏幕)
console_handler = logging.StreamHandler()
console_handler.setFormatter(formatter)
logger.addHandler(console_handler)
# ❌ 缺少 logger.setLevel(...)
# ❌ 缺少 logger.propagate = False
看起来"什么都有",但实际运行起来一条 INFO 日志都打不出来。
三、为什么不能打印日志(三个原因)
原因 1:logging.basicConfig() 在 pytest 下是空操作
logging.basicConfig() 有一个前置条件:只有当 root logger 没有任何 handler 时,它才会生效。
而 pytest 启动时会先给 root logger 挂上自己的 LogCaptureHandler,所以当代码执行到 basicConfig(level=logging.INFO) 时------root logger 已经有 handler 了,这一行直接被跳过,root logger 的 level 没有被改成 INFO。
原因 2:logger 没有显式设置 level
python
logger = logging.getLogger(__name__) # level 默认是 NOTSET
NOTSET 意味着继承父级 logger 的 level ,一路往上找到 root logger。而 pytest 把 root logger 的 level 设成了 WARNING,所以:
logger.info("...")
↓
logger.level = NOTSET → 继承 root
↓
root.level = WARNING(pytest 设的)
↓
INFO < WARNING → 被过滤 ❌
INFO 级别的日志在 logger 这一层就被丢弃了,根本到不了 handler。
原因 3:-s 只管 stdout/stderr,不管 logging
很多人以为 pytest -s 能让所有输出都显示出来。实际上:
| 标志 | 作用 |
|---|---|
-s |
禁用 pytest 对 stdout/stderr 的捕获 |
| logging 插件 | pytest 独立 的日志处理系统,-s 不影响它 |
即使 handler 收到了日志,pytest 的 logging 插件也可能把它拦截,不会直接打到控制台。
四、做了哪些修改
修改后的代码
python
import requests
import logging
logger = logging.getLogger(__name__)
logger.setLevel(logging.INFO) # ✅ 新增:显式设置 level
logger.propagate = False # ✅ 新增:禁止冒泡到 root logger
# 2. 创建格式器
formatter = logging.Formatter('%(asctime)s - %(levelname)s - %(message)s')
# 3. 创建并配置控制台处理器 (输出到屏幕)
console_handler = logging.StreamHandler()
console_handler.setFormatter(formatter)
logger.addHandler(console_handler)
# ✅ 删除了无效的 logging.basicConfig(level=logging.INFO)
改动汇总
| 改动 | 操作 | 原因 |
|---|---|---|
删除 logging.basicConfig(level=logging.INFO) |
删除 | pytest 下是空操作,留着会误导 |
新增 logger.setLevel(logging.INFO) |
新增 | 显式设置 level,不再依赖 root logger,确保 INFO 能通过第一道关卡 |
新增 logger.propagate = False |
新增 | 阻止日志冒泡到 root logger,避免被 pytest 的日志插件拦截或重复处理 |
五、为什么这么修改
5.1 为什么 setLevel 是关键
Python logging 的 level 检查是两级过滤:
logger.info("msg")
↓
【第一级】logger.level 检查 ← 大多数人不了解这一级
↓ 通过
【第二级】handler.level 检查
↓ 通过
输出
之前代码两级都没显式设置,全靠继承,而继承到的是 pytest 设的 WARNING。现在第一级显式设为 INFO,日志就能通过:
logger.info("msg")
↓
logger.level = INFO → 放行 ✅
↓
handler.level = NOTSET(默认全放行) → 放行 ✅
↓
输出到控制台 ✅
5.2 为什么 propagate = False 是必要的
默认情况下 propagate = True,日志记录在走完自己 logger 的 handler 后,还会继续向上传递 给 root logger 的 handler。而 pytest 给 root logger 挂的 handler 是用来捕获日志的(用于测试报告中的日志展示),会导致:
- 日志被重复打印(自己的 handler 打一次,root 的 handler 再打一次)
- 或者被 pytest 的日志系统拦截,看不到实时输出
设为 False 后,日志只走自己的 handler,干净、可控。
六、修改后的效果
执行 pytest -s test_http_client_request.py,日志正常输出:
test_http_client_request.py
2026-09-16 10:30:15,123 - INFO - [POST] http://xxx/api/v1/auth/login
2026-09-16 10:30:15,456 - INFO - Status: 200
.
2026-09-16 10:30:15,789 - INFO - [POST] http://xxx/api/v1/auth/login
2026-09-16 10:30:16,012 - INFO - Status: 200
.
2026-09-16 10:30:16,345 - INFO - [POST] http://xxx/api/v1/auth/login
2026-09-16 10:30:16,678 - INFO - Status: 200
.
======================================================= 3 passed in 1.56s =======================================================
七、核心知识点总结
一句话记住:Python logging 的 level 检查发生在 logger 上,不在 handler 上。
basicConfig()只在 root logger 无 handler 时生效,pytest 下基本没用- 给 logger 显式
setLevel()是确保日志能输出的第一道关卡 propagate = False让日志只走自己的 handler,绕开 pytest 的日志拦截pytest -s只管 stdout/stderr,不管 logging 插件