Python structlog介绍(Structured Logging 结构化日志)(一个用于结构化日志的Python库)记录键值对数、绑定上下文绑定、contextvars、Datadog
文章目录
structlog 完全指南:Python 结构化日志框架详解
前言
日志(Logging)是每个应用程序必不可少的组成部分。
对于一个简单的 Python 程序,我们通常会这样写:
import logging
logging.basicConfig(level=logging.INFO)
logging.info("User login success")
或者:
logging.info("User %s login success", username)
这种方式对于开发阶段没有问题。
但是到了生产环境,你很快就会遇到各种问题:
- 如何统计某个用户一天登录多少次?
- 如何根据 request_id 找到一次完整请求?
- 如何把日志发送到 Elasticsearch(一个开源的、基于 Apache Lucene 构建的分布式搜索与分析引擎)?
- 如何让 Loki、Grafana、Datadog、Splunk 自动解析日志?
- 如何让 AI 或脚本自动分析日志?
如果日志只是字符串,那么这些都会变得十分困难。
因此,现代云原生系统几乎都采用 Structured Logging(结构化日志)。
而 Python 世界最流行的结构化日志库,就是 structlog。它自 2013 年以来已广泛用于生产环境,支持 JSON、logfmt、漂亮的控制台输出,并且可以与 Python 标准 logging 模块无缝集成。(structlog)
什么是 structlog?
structlog 是一个用于 结构化日志(Structured Logging) 的 Python 库。
官方网站给它的介绍非常简单:
Simple. Powerful. Fast. Pick three.
意思就是:
- 简单(Simple)
- 强大(Powerful)
- 快速(Fast)
它最大的思想只有一句话:
日志不是字符串,而是一组 Key-Value 数据。 (structlog)
例如,不再记录:
User Arnold login success from 192.168.1.1
而是记录:
{
"event": "user_login",
"username": "Arnold",
"ip": "192.168.1.1",
"status": "success"
}
这就是结构化日志。
为什么需要结构化日志?
传统日志:
2025-07-10 10:12:31 INFO User Arnold login success
机器只能把它当成一整行字符串。
如果想统计:
Arnold 登录了多少次?
只能:
- 正则表达式
- 字符串切割
- grep
- awk
非常麻烦。
而结构化日志:
{
"timestamp":"2025-07-10T10:12:31",
"level":"info",
"user":"Arnold",
"action":"login"
}
任何日志平台都可以直接:
SELECT *
WHERE user="Arnold"
甚至:
GROUP BY user
完全不需要解析字符串。
structlog 的核心思想
structlog 认为:
日志事件就是一个 Python 字典。
例如:
{
"event": "User login",
"user": "Arnold",
"ip": "127.0.0.1",
"status": "success"
}
最后再决定:
输出成:
控制台:
User login user=Arnold ip=127.0.0.1
或者:
JSON:
{
...
}
或者:
logfmt:
event="User login" user=Arnold ip=127.0.0.1
所以:
数据和展示完全分离。
安装
安装非常简单:
pip install structlog
第一个 structlog 程序
import structlog
logger = structlog.get_logger()
logger.info(
"user_login",
username="Arnold",
ip="192.168.1.100"
)
输出:
2025-07-10T10:20:11
event='user_login'
username='Arnold'
ip='192.168.1.100'
可以看到:
日志已经不是一句话。
而是一组字段。
event 是什么?
很多人第一次都会疑惑:
为什么第一个参数叫:
logger.info("user_login")
而不是 message?
实际上:
logger.info(
"user_login",
username="Arnold"
)
等价于:
{
"event": "user_login",
"username": "Arnold"
}
所以:
第一个参数就是:
event
表示:
发生了什么事情。
而不是完整句子。
推荐:
event="user_login"
event="payment_success"
event="create_order"
event="delete_user"
而不是:
"User Arnold login success."
bind():绑定上下文
这是 structlog 最强大的功能之一。
例如:
logger = structlog.get_logger()
logger = logger.bind(user="Arnold")
以后:
logger.info("login")
自动输出:
user=Arnold
event=login
再记录:
logger.info("logout")
输出:
user=Arnold
event=logout
无需每次重复写:
user="Arnold"
多次 bind()
可以不断增加上下文:
logger = logger.bind(
request_id="12345"
)
logger = logger.bind(
trace_id="abc"
)
以后:
logger.info("query")
自动包含:
user
request_id
trace_id
这对于微服务非常重要。
JSON 输出
生产环境最常见的是:
import structlog
structlog.configure(
processors=[
structlog.processors.JSONRenderer()
]
)
logger = structlog.get_logger()
logger.info(
"login",
user="Arnold"
)
输出:
{
"event":"login",
"user":"Arnold"
}
JSON 是目前日志平台最常见的格式之一,便于 Elasticsearch、Loki 等系统直接解析。(structlog)
Processor(处理器)
Processor 是 structlog 的核心。
它采用流水线(Pipeline)的方式。
日志
↓
增加时间
↓
增加等级
↓
增加异常
↓
JSON 输出
例如:
processors=[
add_log_level,
TimeStamper(),
JSONRenderer()
]
每一个 Processor:
输入:
dict
输出:
dict
最后:
Renderer:
输出字符串。
所以整个流程十分灵活。
Context(上下文)
大型系统里:
一次请求通常需要:
request_id
trace_id
tenant_id
user_id
如果每次:
logger.info(
request_id=...
)
会非常麻烦。
structlog 支持上下文绑定,让这些字段自动出现在日志中,并支持现代 Python 的 contextvars,能够在异步(asyncio)环境中安全地传播请求上下文。(structlog)
例如:
logger = logger.bind(
request_id="abc"
)
以后:
所有日志:
自动带:
request_id
与 logging 的关系
很多新人误以为:
structlog 是:
logging 的替代品。
其实不是。
structlog 可以:
structlog
↓
logging
↓
Console
File
Syslog
CloudWatch(AWS(亚马逊云科技)提供的一项监控和可观测性服务)
...
也就是说:
它更像:
logging 的增强层。
官方也支持与标准库 logging 无缝集成,因此可以继续使用现有的 Handler、Formatter 等生态。(structlog)
在 FastAPI 中使用
例如:
logger.info(
"http_request",
path="/users",
method="GET",
status=200
)
输出:
{
"event":"http_request",
"path":"/users",
"method":"GET",
"status":200
}
Grafana、Loki 可以直接:
status=500
查询:
所有错误请求。
与 OpenTelemetry 配合
现代微服务:
通常:
Application
↓
structlog
↓
OpenTelemetry
↓
OTLP
↓
Grafana Loki
↓
Grafana Dashboard
日志中记录:
trace_id
span_id
request_id
即可与 Trace 关联。
点击一次 Trace:
可以直接看到:
对应日志。
structlog 的优点
1. 真正的结构化日志
不是:
字符串
而是:
dict
机器更容易理解和处理。
2. 上下文管理优秀
通过 bind() 或 contextvars,无需重复传递公共字段,适合请求链路追踪和多租户场景。(structlog)
3. 输出格式灵活
支持:
- Console
- JSON
- logfmt
- 自定义 Renderer
4. 与 logging 完全兼容
不用推翻已有项目。
可以渐进迁移。
5. 非常适合云原生
几乎所有:
- Kubernetes
- Docker
- Grafana
- Loki
- ELK
- Datadog(一个商业化的云原生可观测性与监控平台(SaaS 服务),它把监控、日志、链路追踪、告警、安全等功能集成在一个统一的平台里。可以把它理解为一个"全家桶式"的监控解决方案)
都推荐:
JSON 日志。
structlog 的不足
当然,structlog 也并非适用于所有场景。
学习成本高于 logging
Python 自带的 logging 使用广泛,很多开发者已经熟悉。
而 structlog 需要理解:
- Processor
- Context
- Renderer
- Event Dict
这些概念。
配置较多
一个真正的生产配置:
通常几十行。
不像:
logging.basicConfig(...)
那么简单。
小项目收益有限
如果只是:
几十行脚本:
print()
或者:
logging.info()
已经足够。
没有必要引入 structlog。
什么时候应该使用 structlog?
推荐使用:
- 微服务
- FastAPI
- Django
- Flask
- AI Agent
- RAG 系统
- LangGraph
- Kubernetes
- Docker
- OpenTelemetry
- ELK / Loki
不推荐:
- 一次性脚本
- 学习 Demo
- 小工具
总结
structlog 并不是一个简单的日志输出库,它更像是一套 结构化日志解决方案。它把日志从传统的字符串提升为可查询、可分析、可关联的数据对象,再通过 Processor 管道完成增强和渲染,最终输出为控制台文本、JSON 或其他格式。
对于现代 Python 服务,尤其是部署在 Kubernetes、使用 OpenTelemetry、Grafana、Loki、ELK 等可观测性平台的应用,采用结构化日志已经成为一种主流实践。而 structlog 凭借与标准 logging 的兼容性、灵活的 Processor 管道以及优秀的上下文管理能力,成为 Python 生态中最受欢迎的结构化日志框架之一。(structlog)
如果你的项目已经进入生产环境,或者需要更好的日志检索、分析和链路追踪能力,那么学习并使用 structlog 将是一项非常值得的投入。
更多推荐



所有评论(0)