4. 值班机器人Trace接入实战:在机器人核心流程中插入Span
本章将指导你在值班机器人的三个核心流程——接收告警、决策、执行动作——中手动插入 OpenTelemetry Span,构建端到端追踪链路。通过 Trace,你可以精确追踪每一条告警从入站到动作执行的完整生命周期,快速定位延迟瓶颈或逻辑错误。
4.1 核心流程Span设计
在接入 Trace 前,需要先定义 Span 的层级结构与属性。下表列出了三个核心阶段对应的 Span 名称、职责及关键属性:
| 阶段 | Span 名称 | 职责 | 关键属性(Attributes) |
|---|---|---|---|
| 接收告警 | alert.receive |
解析入站告警源(Prometheus、Grafana、自研监控) | alert.source, alert.severity, alert.name |
| 决策 | alert.decision |
匹配规则引擎、查询历史、判断是否需要执行动作 | decision.rule_id, decision.match_result, decision.duration_ms |
| 执行动作 | alert.action.execute |
调用外部系统(Jira、PagerDuty、Webhook、自愈脚本) | action.type, action.target, action.status |
设计原则:每个 Span 的
name 使用 层级.子模块 命名规范,便于在 Jaeger 或 Grafana Tempo 中按前缀搜索。属性中必须包含 alert.id 作为关联键,确保同一告警的所有 Span 可被串联。
4.2 端到端追踪架构图
下图展示了值班机器人内部 Span 的嵌套关系与上下文传播路径:
4.3 Python 代码实战:插入 Span
以下代码演示如何在 Python 值班机器人中,使用 OpenTelemetry SDK 手动创建嵌套 Span。假设你已经配置了全局 TracerProvider 并导出到 Jaeger 或 Tempo。
from opentelemetry import trace
from opentelemetry.trace import Status, StatusCode
import time
tracer = trace.get_tracer("alert-bot", "1.0.0")
def process_alert(alert_dict: dict):
"""值班机器人核心流程:接收 -> 决策 -> 执行"""
# 创建根 Span:接收告警
with tracer.start_as_current_span("alert.receive") as receive_span:
receive_span.set_attribute("alert.id", alert_dict.get("id"))
receive_span.set_attribute("alert.source", alert_dict.get("source", "unknown"))
receive_span.set_attribute("alert.severity", alert_dict.get("severity", "info"))
# 模拟告警解析耗时
time.sleep(0.05)
# 创建子 Span:决策阶段
with tracer.start_as_current_span("alert.decision") as decision_span:
decision_span.set_attribute("decision.rule_id", alert_dict.get("rule_id", "default"))
decision_span.set_attribute("decision.match_result", "true")
# 决策内部子 Span:查询历史缓存
with tracer.start_as_current_span("alert.decision.cache") as cache_span:
cache_span.set_attribute("cache.hit", "true")
time.sleep(0.02)
# 决策内部子 Span:规则匹配
with tracer.start_as_current_span("alert.decision.rule") as rule_span:
rule_span.set_attribute("rule.evaluation", "pass")
time.sleep(0.03)
decision_span.set_attribute("decision.duration_ms", 50)
# 创建子 Span:执行动作
with tracer.start_as_current_span("alert.action.execute") as action_span:
action_span.set_attribute("action.type", "webhook")
action_span.set_attribute("action.target", "https://hooks.example.com/alert")
# 模拟 HTTP 调用
try:
# 实际调用外部系统
time.sleep(0.1)
action_span.set_attribute("action.status", "success")
action_span.set_status(Status(StatusCode.OK))
except Exception as e:
action_span.set_attribute("action.status", "failed")
action_span.set_attribute("action.error", str(e))
action_span.set_status(Status(StatusCode.ERROR, str(e)))
# 根 Span 记录总耗时
receive_span.set_attribute("alert.total_duration_ms", 200)
# 模拟一条告警
alert = {
"id": "alert-001",
"source": "prometheus",
"severity": "critical",
"rule_id": "cpu_high_95"
}
process_alert(alert)
关键点:
- 使用
tracer.start_as_current_span上下文管理器自动处理 Span 的开始与结束。 - 子 Span 自动继承父 Span 的 Trace ID,无需手动传递 Context。
- 通过
set_attribute添加业务维度信息,便于后续检索与过滤。 - 异常时调用
set_status(StatusCode.ERROR)标记 Span 为失败状态。
4.4 Go 代码实战:插入 Span
Go 版本的实现同样遵循相同的 Span 层级结构。以下代码使用 OpenTelemetry Go SDK:
package main
import (
"context"
"fmt"
"time"
"go.opentelemetry.io/otel"
"go.opentelemetry.io/otel/attribute"
"go.opentelemetry.io/otel/trace"
)
var tracer = otel.Tracer("alert-bot", trace.WithInstrumentationVersion("1.0.0"))
type Alert struct {
ID string
Source string
Severity string
RuleID string
}
func ProcessAlert(ctx context.Context, alert Alert) {
// 根 Span:接收告警
ctx, receiveSpan := tracer.Start(ctx, "alert.receive",
trace.WithAttributes(
attribute.String("alert.id", alert.ID),
attribute.String("alert.source", alert.Source),
attribute.String("alert.severity", alert.Severity),
),
)
defer receiveSpan.End()
time.Sleep(50 * time.Millisecond)
// 子 Span:决策阶段
ctx, decisionSpan := tracer.Start(ctx, "alert.decision",
trace.WithAttributes(
attribute.String("decision.rule_id", alert.RuleID),
attribute.String("decision.match_result", "true"),
),
)
// 决策内部子 Span:查询缓存
_, cacheSpan := tracer.Start(ctx, "alert.decision.cache",
trace.WithAttributes(attribute.Bool("cache.hit", true)),
)
time.Sleep(20 * time.Millisecond)
cacheSpan.End()
// 决策内部子 Span:规则匹配
_, ruleSpan := tracer.Start(ctx, "alert.decision.rule",
trace.WithAttributes(attribute.String("rule.evaluation", "pass")),
)
time.Sleep(30 * time.Millisecond)
ruleSpan.End()
decisionSpan.SetAttributes(attribute.Int64("decision.duration_ms", 50))
decisionSpan.End()
// 子 Span:执行动作
_, actionSpan := tracer.Start(ctx, "alert.action.execute",
trace.WithAttributes(
attribute.String("action.type", "webhook"),
attribute.String("action.target", "https://hooks.example.com/alert"),
),
)
// 模拟 HTTP 调用
time.Sleep(100 * time.Millisecond)
actionSpan.SetAttributes(attribute.String("action.status", "success"))
actionSpan.End()
receiveSpan.SetAttributes(attribute.Int64("alert.total_duration_ms", 200))
fmt.Printf("Processed alert %s\n", alert.ID)
}
func main() {
// 假设已初始化全局 TracerProvider
ctx := context.Background()
alert := Alert{
ID: "alert-002",
Source: "grafana",
Severity: "warning",
RuleID: "mem_high_80",
}
ProcessAlert(ctx, alert)
}
避坑指南:
- Go 中必须显式传递
context.Context,否则子 Span 无法继承父 Span 的 Trace ID。 - 不要忘记调用
defer span.End()或手动span.End(),否则 Span 会一直处于打开状态,导致追踪数据不完整。 - 属性值尽量使用强类型方法(如
attribute.String、attribute.Int64),避免运行时类型转换开销。
4.5 验证 Trace 接入效果
完成代码接入后,通过以下步骤验证 Trace 是否正确生成:
- 启动导出器:确保 OTLP gRPC 或 HTTP 导出器已配置,并指向 Jaeger Collector 或 Grafana Tempo。
- 触发告警:向值班机器人发送一条测试告警(例如通过 curl 模拟 Prometheus Webhook)。
- 查询 Trace:在 Jaeger UI 或 Grafana Explore 中搜索
alert.receive,按 Trace ID 查看完整链路。 - 检查 Span 属性:确认每个 Span 都携带了预期的属性(alert.id、decision.rule_id、action.status 等)。
- 验证嵌套关系:展开 Trace 视图,确认
alert.decision.cache和alert.decision.rule是alert.decision的子 Span。
快速验证命令:使用
otel-cli 或 grpcurl 直接向 OTLP 端点发送测试 Span,无需启动完整机器人。例如:otel-cli span --service alert-bot --name "alert.receive" --attrs "alert.id=test-001"
4.6 常见问题与排查
| 问题现象 | 可能原因 | 解决方案 |
|---|---|---|
| Trace 中只有根 Span,没有子 Span | Context 未正确传递,或子 Span 使用了不同的 Tracer | 检查是否使用同一个 tracer 实例,并确保 ctx 在父子 Span 间传递 |
| Span 属性缺失或为空 | 属性设置时机在 Span 结束后,或属性键拼写错误 | 在 span.End() 之前设置属性,并核对属性名是否与查询条件一致 |
| Trace 未出现在 Jaeger/Tempo 中 | 导出器配置错误、网络不通、采样率过低 | 检查 OTLP 端点地址、防火墙规则,并确认采样率 >= 1(调试时可设为 1.0) |
| Span 耗时显示为 0 | 未正确调用 span.End(),或使用了 Start 但不带 WithTimestamp |
确保每个 Span 都调用了 End(),并检查 SDK 版本是否支持自动计时 |
通过本章实战,你已成功在值班机器人核心流程中插入 Span,实现了从告警接收到动作执行的端到端追踪。下一阶段将基于这些 Trace 数据构建故障定位 Dashboard,并配置基于 Span 延迟的告警规则。