structlog:Python 结构化日志终极解决方案
一、structlog 是什么?
structlog 是 Python 生态中最流行的结构化日志库 ,专门解决标准 logging 模块的所有痛点。
核心特点:
- 输出结构化 JSON 格式日志(不是纯文本)
- 支持上下文自动绑定(自动带上请求 ID、用户 ID、接口路径)
- 可扩展的处理器链(自由定制日志格式、过滤、输出)
- 100% 兼容 Python 标准
logging模块 - 性能比标准 logging 更快、配置更简单
大白话:标准 logging 输出的是人能看懂但机器看不懂 的纯文本;structlog 输出的是人和机器都能看懂的 JSON 格式,完美适配 ELK、Grafana Loki 等日志分析系统。
二、为什么不用标准 logging?(痛点对比)
标准 logging 的致命问题
python
运行
ini
# 标准 logging 输出
import logging
logging.warning("用户登录失败", extra={"user_id": 123, "ip": "192.168.1.1"})
输出:
plaintext
css
WARNING:root:用户登录失败
user_id 和 ip 直接丢了! 就算配置了格式,输出也是:
plaintext
ini
2024-05-14 10:00:00 WARNING 用户登录失败 user_id=123 ip=192.168.1.1
这是非结构化纯文本,日志系统很难解析和搜索。
structlog 的输出
python
运行
ini
import structlog
logger = structlog.get_logger()
logger.warning("用户登录失败", user_id=123, ip="192.168.1.1")
输出(JSON 格式):
json
json
{
"event": "用户登录失败",
"level": "warning",
"timestamp": "2024-05-14T10:00:00Z",
"user_id": 123,
"ip": "192.168.1.1"
}
所有字段都保留 ,日志系统可以直接按 user_id、ip 搜索和过滤。
三、structlog 核心概念
1. 结构化日志
日志不再是一行字符串,而是键值对组成的字典,最终序列化为 JSON。每个日志条目都有固定字段:
event:日志消息level:日志级别(debug/info/warning/error/critical)timestamp:时间戳- 自定义字段:user_id、request_id、path、status_code 等
2. 处理器链(Processor Chain)
structlog 的核心设计:日志会经过一系列处理器处理,最后输出。
plaintext
日志事件 → 处理器1 → 处理器2 → ... → 输出处理器
常用处理器:
- 添加时间戳
- 添加日志级别
- 格式化 JSON
- 过滤敏感字段
- 异常堆栈格式化
3. 绑定上下文(Binding Context)
可以给 logger 绑定全局或局部上下文,后续所有日志自动带上这些字段。
python
运行
ini
# 绑定全局上下文
logger = logger.bind(service="contract-ai", version="1.0.0")
# 绑定请求上下文
request_logger = logger.bind(request_id="abc123", user_id=123)
# 后续所有日志自动带上这些字段
request_logger.info("接口调用成功", path="/api/user", status_code=200)
输出:
json
json
{
"event": "接口调用成功",
"level": "info",
"service": "contract-ai",
"version": "1.0.0",
"request_id": "abc123",
"user_id": 123,
"path": "/api/user",
"status_code": 200
}
四、快速上手:5 分钟跑通
1. 安装
bash
运行
pip install structlog python-json-logger
2. 最简单的配置
python
运行
ini
import structlog
# 基础配置(开发环境用)
structlog.configure(
processors=[
structlog.processors.TimeStamper(fmt="iso"), # 添加ISO格式时间戳
structlog.processors.add_log_level, # 添加日志级别
structlog.processors.JSONRenderer() # 输出JSON
]
)
# 获取logger
logger = structlog.get_logger()
# 打日志
logger.debug("调试信息", param1="value1")
logger.info("普通信息", user_id=123)
logger.warning("警告信息", ip="192.168.1.1")
logger.error("错误信息", error="数据库连接失败")
logger.critical("致命错误", service="db")
3. 开发环境友好配置(彩色输出)
开发环境用人类可读的彩色格式,生产环境用 JSON:
python
运行
ini
import structlog
import sys
# 自动判断环境
is_dev = sys.stderr.isatty()
structlog.configure(
processors=[
structlog.processors.TimeStamper(fmt="iso"),
structlog.processors.add_log_level,
# 开发环境彩色输出,生产环境JSON
structlog.dev.ConsoleRenderer() if is_dev else structlog.processors.JSONRenderer()
]
)
五、进阶用法
1. 异常追踪(自动打印堆栈)
python
运行
python
try:
1 / 0
except ZeroDivisionError:
logger.error("计算错误", exc_info=True)
输出会自动包含完整的异常堆栈信息,JSON 格式下也能正确解析。
2. 全局上下文绑定
python
运行
ini
# 应用启动时绑定全局上下文
structlog.configure(
processors=[
structlog.processors.TimeStamper(fmt="iso"),
structlog.processors.add_log_level,
structlog.processors.JSONRenderer()
],
initial_values={"service": "contract-ai", "version": "1.0.0", "env": "production"}
)
所有日志自动带上 service、version、env 字段。
3. 敏感字段过滤
自动过滤密码、token 等敏感字段:
python
运行
ini
def filter_sensitive(_, __, event_dict):
sensitive_fields = ["password", "token", "secret"]
for field in sensitive_fields:
if field in event_dict:
event_dict[field] = "***"
return event_dict
structlog.configure(
processors=[
structlog.processors.TimeStamper(fmt="iso"),
structlog.processors.add_log_level,
filter_sensitive, # 先过滤敏感字段
structlog.processors.JSONRenderer()
]
)
logger.info("用户登录", username="zhangsan", password="123456")
输出:
json
json
{"event":"用户登录","level":"info","timestamp":"...","username":"zhangsan","password":"***"}
六、和 FastAPI 集成(重点)
这是你最需要的!给每个请求自动绑定 request_id、user_id、path、status_code。
完整 FastAPI + structlog 中间件
python
运行
ini
from fastapi import FastAPI, Request
import structlog
import uuid
import time
import sys
# 配置 structlog
is_dev = sys.stderr.isatty()
structlog.configure(
processors=[
structlog.processors.TimeStamper(fmt="iso"),
structlog.processors.add_log_level,
structlog.processors.StackInfoRenderer(),
structlog.processors.format_exc_info,
structlog.dev.ConsoleRenderer() if is_dev else structlog.processors.JSONRenderer()
],
initial_values={"service": "contract-ai", "version": "1.0.0"}
)
logger = structlog.get_logger()
app = FastAPI()
# 全局日志中间件
@app.middleware("http")
async def log_requests(request: Request, call_next):
# 生成唯一请求ID
request_id = str(uuid.uuid4())
# 绑定请求上下文
request_logger = logger.bind(
request_id=request_id,
method=request.method,
path=request.url.path,
client_ip=request.client.host
)
# 记录请求开始
start_time = time.time()
request_logger.info("请求开始")
try:
# 处理请求
response = await call_next(request)
# 记录请求成功
process_time = round((time.time() - start_time) * 1000, 2)
request_logger.info(
"请求成功",
status_code=response.status_code,
process_time_ms=process_time
)
# 把请求ID加到响应头,方便前端排查
response.headers["X-Request-ID"] = request_id
return response
except Exception as e:
# 记录请求异常
process_time = round((time.time() - start_time) * 1000, 2)
request_logger.error(
"请求失败",
status_code=500,
process_time_ms=process_time,
exc_info=True
)
raise
# 测试接口
@app.get("/api/user/{user_id}")
def get_user(user_id: int):
# 在接口内部使用日志,自动带上请求上下文
logger.info("查询用户信息", user_id=user_id)
return {"user_id": user_id, "username": "zhangsan"}
效果:每个请求都会生成唯一的 request_id,所有相关日志都带上这个 ID,排查问题时可以一键搜索整个请求的所有日志。
七、和 Celery 集成
给每个 Celery 任务自动绑定 task_id、task_name:
python
运行
ini
from celery.signals import task_prerun, task_postrun, task_failure
import structlog
logger = structlog.get_logger()
@task_prerun.connect
def before_task(task_id, task, args, kwargs, **_):
# 任务开始前绑定上下文
task_logger = logger.bind(
task_id=task_id,
task_name=task.name,
args=args,
kwargs=kwargs
)
task_logger.info("任务开始执行")
@task_postrun.connect
def after_task(task_id, task, retval, state, **_):
# 任务结束后记录
logger.info("任务执行完成", task_id=task_id, state=state)
@task_failure.connect
def task_failed(task_id, task, exception, traceback, **_):
# 任务失败记录异常
logger.error(
"任务执行失败",
task_id=task_id,
exception=str(exception),
exc_info=True
)
八、生产环境最佳实践
1. 标准生产配置模板
python
运行
ini
import structlog
import sys
import os
ENV = os.getenv("ENV", "dev")
is_dev = ENV == "dev"
structlog.configure(
processors=[
# 1. 添加时间戳
structlog.processors.TimeStamper(fmt="iso", utc=True),
# 2. 添加日志级别
structlog.processors.add_log_level,
# 3. 过滤敏感字段
lambda _, __, ed: {k: "***" if k in ["password", "token"] else v for k, v in ed.items()},
# 4. 异常堆栈格式化
structlog.processors.StackInfoRenderer(),
structlog.processors.format_exc_info,
# 5. 输出格式
structlog.dev.ConsoleRenderer() if is_dev else structlog.processors.JSONRenderer()
],
# 全局上下文
initial_values={
"service": "contract-ai",
"version": "1.0.0",
"env": ENV
},
# 日志级别控制
wrapper_class=structlog.make_filtering_bound_logger(
structlog.DEBUG if is_dev else structlog.INFO
)
)
logger = structlog.get_logger()
2. 日志级别控制
- 开发环境:
DEBUG级别,输出所有日志 - 生产环境:
INFO级别,只输出 info 及以上日志 - 排查问题时临时改成
DEBUG
3. 和日志系统集成
- 输出的 JSON 日志可以直接被 ELK Stack 、Grafana Loki 、Datadog 等收集分析
- 可以按
request_id、user_id、task_id快速搜索和关联日志 - 可以做统计分析:接口响应时间、错误率、任务成功率
九、核心优势总结
- 结构化 JSON 输出:机器易解析,完美适配现代日志系统
- 上下文自动绑定:一个请求 / 任务的所有日志共享同一个 ID,排查问题神器
- 可扩展处理器链:自由定制日志格式、过滤、转换
- 和标准 logging 兼容:可以无缝替换现有 logging 代码
- 性能更好:比标准 logging 快 2-3 倍
- 配置简单:比 logging 的配置简单一个数量级
十、一句话终极总结
structlog 是 Python 后端开发的日志标配,所有企业级项目都应该用它代替标准 logging。