OpenTelemetry 与 Go 可观测性接入¶
30 秒版(开场)¶
OpenTelemetry(OTel) 统一 Traces、Metrics、Logs 的采集与导出;Go 用
otelSDK + OTLP 推到 Collector/Jaeger/Prometheus。生产关键词:W3C traceparent 传播、采样率、高基数 label 控制、与 slog 关联 trace_id。
3 分钟版(精讲深度)¶
- 是什么:OTel 是 CNCF 可观测性标准;Go 通过
go.opentelemetry.io/otel埋点,Exporter 发到后端。 - 为什么:微服务下 跨服务排障 靠 Trace;5 年+ 需讲清 三支柱如何关联(同一 trace_id 串日志)。
- 怎么做:HTTP/gRPC instrumentation 创建 span;
otel.SetTracerProvider+ batch processor;采样策略按流量、错误预算与后端成本设计,常用 ParentBased + 比例头采样,或把完整候选 trace 送到 Collector 做尾采样;metrics 避免user_id等高基数属性。
10 分钟版(原理 + 图示)¶
flowchart LR
App[Go Service] -->|OTLP gRPC| Col[OTel Collector]
Col --> Jaeger[Jaeger/Tempo]
Col --> Prom[Prometheus/Mimir]
Col --> Loki[Loki 日志]
App -->|slog trace_id| Loki
最小 Tracer 初始化
import (
"go.opentelemetry.io/otel"
"go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc"
"go.opentelemetry.io/otel/sdk/resource"
sdktrace "go.opentelemetry.io/otel/sdk/trace"
semconv "go.opentelemetry.io/otel/semconv/v1.24.0"
)
func initTracer(ctx context.Context, endpoint string) (*sdktrace.TracerProvider, error) {
exp, err := otlptracegrpc.New(ctx, otlptracegrpc.WithEndpoint(endpoint), tlsOption)
if err != nil {
return nil, err
}
tp := sdktrace.NewTracerProvider(
sdktrace.WithBatcher(exp),
sdktrace.WithResource(resource.NewWithAttributes(
semconv.SchemaURL,
semconv.ServiceName("order-api"),
)),
sdktrace.WithSampler(sdktrace.ParentBased(sdktrace.TraceIDRatioBased(0.1))),
)
otel.SetTracerProvider(tp)
return tp, nil
}
其中 tlsOption 在生产应使用 TLS credentials;WithInsecure() 只适合明确受控的本地/受信链路。Gin 可用 otelgin.Middleware("service-name") 创建 span 并传播 context。
生产场景¶
- 支付链路 5 跳:缺 Trace → 无法定位慢在 DB 还是下游 RPC
- 高 QPS:全量保留成本过高。若 SDK 已在头采样阶段丢弃 trace,Collector 的尾采样无法“找回”错误 trace;要做错误优先尾采样,应让候选 trace 到达 Collector,并配置 memory limiter、队列和容量保护
- 日志:slog 注入
trace_id、span_id便于 Loki 查询
排查与工具¶
- Jaeger/Tempo UI 看 critical path
go.opentelemetry.io/contrib各框架 instrumentation- Collector pipeline:receiver → processor(batch/memory_limiter)→ exporter
- 告警:基于 RED(Rate、Errors、Duration)指标
架构取舍¶
| 方案 | 适用 |
|---|---|
| Agent 侧车 Collector | K8s 标准,解耦 SDK 配置 |
| SDK 直推 SaaS | 小团队快速接入 |
| 仅 Metrics | 成本低,排障能力弱 |
何时不全量 OTel:边缘 IoT、极低延迟路径可只 metrics + 结构化日志。
深挖问答¶
- Trace 和 Log 怎么关联? → context 中 trace_id 写入 slog 字段;同一 request 共享。
- 采样策略? → 头采样便宜但看不到最终错误;尾采样可按错误/延迟决策但需要缓冲完整 trace。所谓“错误全留”是策略目标,不是无成本保证。
- context 传播跨 goroutine? → 直接把包含 span 的
context.Context显式传入 goroutine/下游调用;不要无理由改用context.Background()或手工复制 span。 - 与 Prometheus 关系? → OTel metrics 可 export 为 Prometheus 格式;长期可统一 OTLP。
反模式与事故¶
- 每个函数都 span → 开销大、Collector 爆
- span 名高基数
/user/123→ 存储爆炸,应模板化/user/:id - 未 shutdown TracerProvider → 丢尾部 span
- Metrics label 带 order_id → Prometheus cardinality 灾难
代码示例¶
ctx, span := otel.Tracer("order").Start(ctx, "CreateOrder")
defer span.End()
logger.InfoContext(ctx, "creating order", "order_id", id) // logger 应提取 trace_id