OpenTelemetry 和 Flask
内容提要
本文介绍在 Python Flask 应用中接入 OpenTelemetry 的两种方法:一是通过自动插桩运行 dice-backend 服务,采集 Flask、sqlite3、logging 的 Traces、Metrics、Logs 并上报;二是在 dice-app 中手动初始化各 Exporter,结合 httpx、jinja2 插桩实现跨服务链路追踪,使同一 trace_id 贯穿两个服务,便于排查问题。
延伸解读
两种接入方式的选择依据
文章展示了自动插桩与手动初始化两种接入 OpenTelemetry 的方法。自动插桩适合快速为现有 Flask 应用添加可观测性,无需修改业务代码,通过环境变量和 opentelemetry-instrument 命令即可运行。手动初始化则适合需要精细控制 Exporter、资源属性或自定义指标的场景,但代码量较大。读者可根据项目阶段和团队对可观测性的控制需求选择合适方式。
跨服务链路追踪的实现关键
dice-app 与 dice-backend 能共享同一 trace_id,关键在于为 httpx 安装了 OpenTelemetry 插桩库。当 dice-app 通过 httpx 调用 dice-backend 时,插桩会自动在请求头中注入 trace 上下文,使后端服务能延续同一链路。这提示我们,在微服务架构中,对 HTTP 客户端库进行插桩是实现分布式追踪的必要步骤,否则链路会在服务边界处断开。
日志与 Trace 的关联价值
文章指出,在 span 上下文中记录的日志,通过 OTLP 上报时会自动附加 trace_id 等字段。这意味着在排查问题时,可以将日志中的 trace_id 与链路追踪系统中的 trace_id 对应起来,实现日志与链路的联动分析。这种关联能显著提升定位效率,尤其是在跨多个服务的复杂调用中,避免在不同系统间手动拼接信息。
示例代码中的潜在缺陷
dice-backend 的摇骰子逻辑存在一个明显缺陷:对随机数取模 7 后,结果有 1/7 的概率为 0,此时会抛出 ValueError 导致接口返回 500 错误。文章在测试中也观察到约 1/7 的请求失败。这提醒读者,在接入可观测性时,业务代码本身的健壮性同样重要,否则采集到的错误数据可能掩盖真正的问题。
Q&A
在 Flask 应用中接入 OpenTelemetry 有哪两种主要方式?
一是使用 OpenTelemetry 的自动插桩(auto-instrumentation)运行服务,例如通过 opentelemetry-instrument 命令启动 dice-backend,自动采集 Flask、sqlite3、logging 的 Traces、Metrics、Logs 并上报;二是在代码中手动初始化各 Exporter,例如在 dice-app 中手动设置 TracerProvider、LoggerProvider、MeterProvider,并结合 httpx、jinja2 等插桩库实现跨服务链路追踪。
如何让 dice-app 和 dice-backend 两个服务的 trace_id 保持一致?
在 dice-app 中引入 httpx 的 OpenTelemetry 插桩(HTTPXClientInstrumentor),当 dice-app 使用 httpx 向 dice-backend 发起请求时,httpx 会自动在请求头中携带 trace_id 字段,从而使得两个服务共享同一个 trace_id,实现跨服务链路追踪。
在 Flask 中手动创建 span 并添加属性的代码示例?
可以使用 with tracer.start_as_current_span("roll") as roll_span: 创建名为 roll 的 span,然后通过 roll_span.set_attribute("origin_random_number", number) 为 span 添加属性,记录执行过程中的信息。
OpenTelemetry 自动插桩支持哪些 Python 库?
OpenTelemetry 提供了对常见 Python 库的自动插桩支持,例如 Flask、sqlite3、logging、httpx、jinja2 等。官方支持列表可参考 OpenTelemetry Instrumentation 文档。
在 span 上下文中记录的日志有什么好处?
在 span 上下文中记录的日志,通过 OTLP 协议上报时会自动附加 trace_id 等字段。这样在问题排查时,可以将日志中的 trace_id 与链路追踪系统中的 trace_id 对应起来,进行综合排查。
dice-backend 服务中骰子点数为什么有时会返回 500 错误?
因为骰子点数是通过 select random() 取模 7 得到的,结果有 1/7 的概率为 0,而代码要求点数必须在 1 到 6 之间,否则抛出 ValueError 异常,导致 Flask 返回 500 INTERNAL SERVER ERROR。