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。

相关推荐
IT_陈寒5 小时前
Vue这个坑我跳了两次,原来问题出在这
前端·人工智能·后端
新新技术迷5 小时前
Node给AI接口做SSE代理与鉴权
人工智能
redreamSo6 小时前
大模型是不是到顶了?瓶颈到底在哪
人工智能·openai
Oo9206 小时前
Tool Use 背后的技术逻辑
人工智能
姗姗来迟了6 小时前
Vue3封装AI流式对话组件踩坑实录
人工智能
码上天下6 小时前
用Pinia管理AI多会话状态
人工智能
用户054324329707 小时前
Next.js接大模型流式SSE实操踩坑
人工智能
Assby7 小时前
从 Function Calling 到 MCP:理解 Agent 工具调用的底层通信机制
人工智能·后端
小星AI8 小时前
Claude Code 从入门到精通,一步到位
人工智能
后端小肥肠8 小时前
Codex + Obsidian 做人生副本视频:输入主题文案,直通剪映草稿
人工智能·aigc·agent