structlog:Python 结构化日志终极解决方案

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_idip 搜索和过滤。


三、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"}
)

所有日志自动带上 serviceversionenv 字段。

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_iduser_idpathstatus_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_idtask_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 StackGrafana LokiDatadog 等收集分析
  • 可以按 request_iduser_idtask_id 快速搜索和关联日志
  • 可以做统计分析:接口响应时间、错误率、任务成功率

九、核心优势总结

  1. 结构化 JSON 输出:机器易解析,完美适配现代日志系统
  2. 上下文自动绑定:一个请求 / 任务的所有日志共享同一个 ID,排查问题神器
  3. 可扩展处理器链:自由定制日志格式、过滤、转换
  4. 和标准 logging 兼容:可以无缝替换现有 logging 代码
  5. 性能更好:比标准 logging 快 2-3 倍
  6. 配置简单:比 logging 的配置简单一个数量级

十、一句话终极总结

structlog 是 Python 后端开发的日志标配,所有企业级项目都应该用它代替标准 logging。

相关推荐
幂律智能1 小时前
Prompt不是提问,而是任务定义
大数据·人工智能·prompt
闵孚龙1 小时前
AI Agent 长上下文压缩全解析:自动压缩、记忆治理、Prompt Cache、上下文工程,让长会话不跑偏
人工智能·架构·prompt·claude
子午1 小时前
基于YOLO的玫瑰叶片检测系统~Python+深度学习+人工智能+目标检测+YOLOV8算法
人工智能·python·yolo
爱看科技1 小时前
Meta Connect开发者大会定档在即,苹果/微美全息加速抢跑AI+XR消费级赛道
人工智能·xr
kcuwu.1 小时前
博客转抖音视频(文件上传版)Coze工作流实现文档(第一版)
人工智能·音视频·coze
TigerOne1 小时前
第5章 模块化设计
人工智能
eastyuxiao1 小时前
数字孪生在智慧建筑中的应用案例
大数据·人工智能·智慧城市·数字孪生
CypressTel1 小时前
赛柏特安全观察:黑客利用人工智能开发针对网站管理工具的零日漏洞
人工智能·安全
阳明山水1 小时前
基于静态属性的聚类预测新商品销量
人工智能·机器学习·微信·微信公众平台·微信开放平台