4、配置结构化日志:使用 python-json-logger 库输出 JSON 格式日志
在集成测试与微服务架构中,非结构化的文本日志难以被日志收集系统(如 ELK、Loki)高效解析。使用 python-json-logger 库可以将日志记录直接输出为 JSON 格式,使每条日志都成为结构化的键值对集合,便于后续的过滤、聚合与告警。
4.1 安装与基础配置
首先通过 pip 安装依赖:
pip install python-json-logger
在 Python 代码中,核心配置步骤包括:创建 JsonFormatter 实例、将其绑定到 StreamHandler,最后将 Handler 添加到 Logger。以下是一个可直接运行的配置示例:
import logging
from pythonjsonlogger import jsonlogger
# 1. 获取或创建 Logger
logger = logging.getLogger("integration_test")
logger.setLevel(logging.DEBUG)
# 2. 创建控制台 Handler
handler = logging.StreamHandler()
handler.setLevel(logging.DEBUG)
# 3. 定义 JSON 格式化器
formatter = jsonlogger.JsonFormatter(
fmt="%(asctime)s %(name)s %(levelname)s %(message)s %(module)s %(funcName)s",
datefmt="%Y-%m-%dT%H:%M:%S"
)
handler.setFormatter(formatter)
# 4. 将 Handler 添加到 Logger
if not logger.handlers:
logger.addHandler(handler)
# 5. 使用示例
logger.info("用户登录成功", extra={"user_id": 1001, "ip": "192.168.1.10"})
logger.error("数据库连接超时", extra={"db_host": "pg-master-01", "timeout_ms": 5000})
输出效果:
{"asctime": "2025-03-21T14:32:10", "name": "integration_test", "levelname": "INFO", "message": "用户登录成功", "module": "test_auth", "funcName": "test_login", "user_id": 1001, "ip": "192.168.1.10"}
{"asctime": "2025-03-21T14:32:11", "name": "integration_test", "levelname": "ERROR", "message": "数据库连接超时", "module": "test_db", "funcName": "test_connection", "db_host": "pg-master-01", "timeout_ms": 5000}
最佳实践: 在集成测试中,建议在
conftest.py 的 pytest_configure 钩子中一次性完成 Logger 配置,避免每个测试模块重复初始化。
4.2 关键参数与字段映射
JsonFormatter 的 fmt 参数使用标准 %(key)s 占位符,支持所有 LogRecord 属性。下表列出最常用的内置字段与自定义扩展方式:
| 字段名 | 占位符 | 说明 | 示例值 |
|---|---|---|---|
| 时间戳 | %(asctime)s |
ISO 8601 格式,通过 datefmt 控制精度 |
2025-03-21T14:32:10 |
| 日志级别 | %(levelname)s |
INFO / WARNING / ERROR 等 | ERROR |
| Logger 名称 | %(name)s |
通常使用模块名或测试套件名 | integration_test |
| 消息正文 | %(message)s |
日志描述文本 | 数据库连接超时 |
| 模块名 | %(module)s |
自动提取调用栈中的模块 | test_db |
| 函数名 | %(funcName)s |
自动提取调用栈中的函数 | test_connection |
| 自定义字段 | extra={} |
通过 extra 字典传入任意键值对 |
{"user_id": 1001} |
注意: 如果
extra 中的键与 fmt 中的内置占位符同名,extra 值会覆盖内置字段。建议自定义字段使用下划线前缀(如 _user_id)或全小写命名以避免冲突。
4.3 结构化日志在集成测试中的典型流程
下图展示了从测试执行到日志消费的完整链路,其中 JSON 格式化器是连接测试代码与日志基础设施的关键桥梁:
4.4 进阶配置:自定义字段与日志上下文
在复杂的集成测试场景中,往往需要为每条日志注入全局上下文(如测试用例 ID、环境名称、构建版本)。推荐使用 logging.Filter 或 extra 参数实现:
import logging
from pythonjsonlogger import jsonlogger
class TestContextFilter(logging.Filter):
"""为所有日志记录注入测试上下文"""
def filter(self, record):
record.test_run_id = getattr(record, 'test_run_id', 'unknown')
record.environment = getattr(record, 'environment', 'staging')
return True
# 配置 Logger
logger = logging.getLogger("integration_test")
handler = logging.StreamHandler()
formatter = jsonlogger.JsonFormatter(
fmt="%(asctime)s %(levelname)s %(message)s %(test_run_id)s %(environment)s"
)
handler.setFormatter(formatter)
logger.addHandler(handler)
logger.addFilter(TestContextFilter())
# 使用:自动携带上下文
logger.info("外部 API 调用成功", extra={"api": "/v2/orders", "latency_ms": 120})
输出:
{"asctime": "2025-03-21T14:32:12", "levelname": "INFO", "message": "外部 API 调用成功", "test_run_id": "unknown", "environment": "staging", "api": "/v2/orders", "latency_ms": 120}
关键要点:
JsonFormatter的fmt中声明的字段必须存在于 LogRecord 中,否则输出null。- 使用
Filter注入全局字段,避免在每个extra中重复传递。 - 生产环境建议将日志输出到文件或直接发送到日志收集 Agent,而非仅输出到控制台。
4.5 配置清单速查
| 步骤 | 操作 | 关键代码 |
|---|---|---|
| 1 | 安装库 | pip install python-json-logger |
| 2 | 创建 Logger | logger = logging.getLogger("name") |
| 3 | 创建 Handler | handler = logging.StreamHandler() |
| 4 | 实例化 JsonFormatter | JsonFormatter(fmt="...", datefmt="...") |
| 5 | 绑定 Formatter 与 Handler | handler.setFormatter(formatter) |
| 6 | 添加 Handler 到 Logger | logger.addHandler(handler) |
| 7 | (可选)添加 Filter | logger.addFilter(MyFilter()) |
| 8 | 记录日志 | logger.info("msg", extra={...}) |
完成以上配置后,集成测试的所有日志将自动以 JSON 格式输出,为后续的自动化分析、告警与故障排查奠定坚实基础。