大家好!在最新的分布式系统环境中,跟踪服务行为并解决问题是一项至关重要的任务。此时,OpenTelemetry (OTel) 就是一个强大的工具,可以帮助我们完成这项任务。
OpenTelemetry 能够帮助我们可视化和分析在众多微服务中发生的请求流 (Trace)。构成这个流的最小工作单元被称为 Span。🗺️
今天,我们将深入探讨表示 Span “状态”的 SpanStatus。它不仅仅是简单地分为“成功”和“失败”,其中还隐藏着更详细的故事。通过清晰地理解这三种状态码之间的差异,您将能够更精确地监控系统。

🧐 整体概览:Span 及其状态为何重要?
当应用程序中执行特定功能(例如:“处理商品订单”)时,此过程将记录为一个巨大的 Trace。而这个 Trace 则由多个小的作业单元——Span 组成。
- Span 1: 接收 API 请求
- Span 2: 验证用户身份
- Span 3: 从数据库查询商品信息
- Span 4: 调用支付服务 💳
- Span 5: 发送订单完成响应
在这里,了解每个 Span 是成功完成还是中途出现问题,对于准确找出整个“商品订单处理”流程中的瓶颈或错误点至关重要。此时,SpanStatus 就扮演了核心角色。
SpanStatus 就像一个交通信号灯,告知每个工作单元的结果。现在,我们来逐一了解信号灯的三种颜色吧!
1️⃣ Unset: 默认状态 (🤔 灰色信号灯)
Unset 是 Span 创建时的默认值。这意味着没有明确设置任何状态。
- 含义: “暂时没有发生其他错误。但我不能保证它成功。”
- 特点: 大多数 OpenTelemetry 后端(如 Jaeger、Zipkin 等)会将 Unset 状态视为成功处理。也就是说,如果没有错误,就认为是成功。
- 何时使用?: 当 Span 启动,并在没有任何错误的情况下正常结束,但开发人员没有特意设置 Ok 状态时,它将保持此状态。
它适用于简单的信息查询,即使失败也无伤大雅,或者不需要明确记录成功与否的任务。
2️⃣ Ok: 明确的成功 (✅ 绿色信号灯)
Ok 表示操作已明确成功。这是与 Unset 最大的区别。
- 含义: “此操作已按我预期完美成功!”
- 特点: 开发人员必须在代码中明确设置,例如 span.set_status(StatusCode.OK)。
- 为何重要?: 对于支付处理、用户注册、重要数据存储等必须确认成功与否的业务逻辑非常有用。在后续分析 Trace 时,可以将“没有错误”的 Span 和“明确成功”的 Span 区分开来查看,从而提高数据的可靠性。
例如,要准确回答“过去一小时的支付成功率是多少?”这样的问题,就需要聚合记录为 Ok 状态的 Span。
3️⃣ Error: 明确的错误 (❌ 红色信号灯)
Error,顾名思义,表示在操作处理过程中发生了错误。
- 含义: “发生问题了!请检查!”
- 特点: 通常在 try-except 语句中发生异常时设置。除了简单地将状态更改为 Error,通常还会将错误消息或堆栈跟踪与 Span 一起记录 (span.record_exception(e)),以帮助调试。
- 何时使用?: 用于所有意外的问题情况,例如网络超时、数据库连接失败、由于参数错误导致的异常等。
💻 通过代码理解 SpanStatus 的流程
光靠文字说明可能不太容易理解。让我们通过使用 Python OpenTelemetry SDK 的简单示例代码来理解整个过程。
from opentelemetry import trace
from opentelemetry.trace import StatusCode
# OTel Tracer 设置 (假设这部分已预先配置)
tracer = trace.get_tracer(__name__)
def process_payment(amount: int, card_number: str):
# 以 'process_payment' 为名启动一个新的 Span。
# Span 在此开始时的默认状态是 'Unset'。
with tracer.start_as_current_span("process_payment") as span:
try:
span.set_attribute("payment.amount", amount)
span.add_event("Payment processing started")
if not card_number.startswith("4"):
# 如果卡号无效,则抛出异常
raise ValueError("Invalid card number")
# --- 实际支付逻辑在此处 ---
print(f"Processing payment of {amount}...")
# --- 支付成功 ---
# ⭐️ 明确记录操作已成功完成。
# 当此代码执行时,Span 状态将从 'Unset' 变为 'Ok'。
span.set_status(StatusCode.OK)
span.add_event("Payment successful!")
return {"status": "success"}
except Exception as e:
# 🚨 如果发生异常,则将状态设置为 Error。
# Span 状态将从 'Unset' 变为 'Error'。
error_message = f"Failed to process payment: {e}"
span.set_status(StatusCode.ERROR, error_message)
# 在 Span 中记录发生了什么异常,以便后续分析。
span.record_exception(e)
return {"status": "failed", "error": error_message}
# 函数调用示例
process_payment(100, "4123-...") # 成功案例
process_payment(50, "5432-...") # 失败案例
📊 结果 Span 原始数据比较
上述代码执行后,收集到的 Span 数据将呈现以下形式(简化为 JSON 格式):
1. 成功案例 (Ok)
{
"name": "process_payment",
"context": { ... },
"attributes": {
"payment.amount": 100
},
"events": [
{"name": "Payment processing started", ...},
{"name": "Payment successful!", ...}
],
"status": {
"status_code": "OK"
},
...
}
2. 失败案例 (Error)
{
"name": "process_payment",
"context": { ... },
"attributes": {
"payment.amount": 50
},
"events": [
{"name": "Payment processing started", ...},
{
"name": "exception",
"attributes": {
"exception.type": "ValueError",
"exception.message": "Invalid card number",
...
}
}
],
"status": {
"status_code": "ERROR",
"description": "Failed to process payment: Invalid card number"
},
...
}
从数据中可以看出 status_code 如何明确地分为 OK 和 ERROR,以及在发生错误时如何添加详细说明 (description) 和异常信息 (exception 事件)。
✨ 结论:正确使用信号灯!
总结一下 SpanStatus 的三种状态:
- Unset 🤔: 默认值。如果没有错误则视为成功,但不是保证的成功。
- Ok ✅: 明确的成功。“此操作确实成功了”的强烈信号。
- Error ❌: 明确的失败。表示发生问题并包含调试所需信息的状态。
不仅仅是捕获错误,养成将重要操作的成功明确记录为 Ok 的习惯是很好的。这样可以更清晰、更可靠地了解系统行为,并且在发生故障时,它将成为快速准确地找出问题根源的强大武器。
发表回复