opentelemetry是go微服务全链路监控唯一可落地的标准化方案,因其原生支持、多后端兼容、严格遵循w3c规范,且需手动对齐span生命周期、context透传与exporter初始化,漏一步即断链。

Go 服务做全链路性能监测,不能靠“自动埋点”幻想——没有 Java Agent 那种字节码注入能力,所有 span 生命周期、context 透传、exporter 初始化都得手动对齐,漏一步就断链。
OpenTelemetry 是当前唯一可落地的标准化方案
别再找 jaeger-client-go 或硬套 SkyWalking 的 Go SDK:前者已归档,后者要求显式调用 sw.Init() 且严重受限于版本兼容(比如 grpc-go v1.9+ 会直接触发 context deadline exceeded)。OpenTelemetry Go SDK 是事实标准,它不绑定后端,但导出器支持 Jaeger、Zipkin、OTLP(对接 Tempo/Grafana),且 propagation 机制严格遵循 W3C Trace Context 规范。
关键动作只有三步:
- 调用
otel.SetTracerProvider(tp)—— 漏掉这句,所有Tracer().Start()返回空span,日志里完全没链路,还不报错 - HTTP 入口用
otelhttp.NewHandler包裹 handler,不是自己解析traceparentheader - gRPC 服务端/客户端必须用
otelgrpc.UnaryServerInterceptor和otelgrpc.UnaryClientInterceptor,否则 unary 调用不会生成子 span
Context 透传是链路不断的核心,不是“加了埋点”就完事
Span 断裂最常见原因不是没埋点,而是 context 没传下去。Go 的 context 不自动跨 goroutine,go doWork() 这种写法必然生成 root span。
必须显式传递带 span 的 context:
Go 配置库,使用 spf13/viper — 分层优先级(flag > env >file > KV > default),提供 BindPFlag/BindPFlags、SetEnvPrefix + SetEnvKeyReplace 等功能。
- 异步任务:写成
go doWork(ctx),不是go doWork(context.Background()) - 定时执行:避免
time.AfterFunc(d, fn)直接用ctx,改用func() { span := trace.SpanFromContext(ctx); ... }或otel.WithSpanFromContext(ctx, span) - 下游 HTTP 调用:用
otelhttp.NewClient()实例发请求,且必须req = req.WithContext(ctx),否则otelhttp.Transport拿不到 parent - 数据库操作:不要用原生
sql.Open,要 wrap 成otelmysql.Wrap(db)或otelpostgresql.Wrap(db)
Gin / gRPC / DB 埋点必须分层适配,不能只靠中间件
中间件只能覆盖入口,DB、下游 HTTP、异步逻辑这些“内部调用”必须单独 instrument,否则链路在第一个外部调用后就终止。
典型组合:
- Gin 入口:
otelgin.Middleware(它基于otelhttp,自动提取traceparent) - 下游 HTTP:
otelhttp.NewClient()+req.WithContext(ctx),别用http.DefaultClient - gRPC 客户端:
grpc.Dial(..., grpc.WithUnaryInterceptor(otelgrpc.UnaryClientInterceptor())) - MySQL:
db, _ := sql.Open("mysql", dsn); db = otelmysql.Wrap(db) - 手动创建子 span:
ctx, span := tracer.Start(req.Context(), "heavy-calc"),且span.End()必须在同 goroutine 内调用
本地验证和线上采样必须分开配置
开发阶段用 stdouttrace.NewExporter 直接打印 span,比连 OTLP collector 更快定位问题;生产环境必须调优 exporter 参数,否则高并发下丢 trace。
两个关键配置:
-
WithSyncer(otlphttp.NewExporter(...))→ 改为WithSyncer(otlphttp.NewExporter(...))并启用 batch(默认 512 条/次),避免单条发送超时静默丢弃 - 采样器别用
AlwaysSample(),改用ParentBased(TraceIDRatioBased(0.01)),否则 trace 数据量爆炸,collector 吃不消 - OTLP exporter 默认超时 5s、失败静默——连不上 collector 就丢数据,不 panic 也不 log,排查时先 telnet 看端口通不通
最易被忽略的一点:所有 instrumented client(otelhttp.Client、otelgrpc、otelmysql)都依赖全局 TracerProvider 和 TextMapPropagator,初始化顺序错了,或者 otel.SetTextMapPropagator(propagation.TraceContext{}) 没设,跨服务 header 就传不下去——这时候看到的不是“没数据”,而是“每个服务都是独立 trace”。
golang免费学习笔记(深入):立即使用
在学习笔记中,你将探索golang的核心概念和高级技巧!










