必须换用opentelemetry,因jaeger-client-go已于2024年底归档,不支持go原生context透传,易致span断裂,且无法对接jaeger collector的otlp接口;新项目应直接使用go.opentelemetry.io/otel等标准包,并在main()开头初始化tracerprovider。

jaeger-client-go 已归档,必须换 OpenTelemetry
新项目直接用 go.opentelemetry.io/otel,别碰 jaeger-client-go。它早在 2024 年底就归档了,不支持 Go 原生 context 透传模型,goroutine 里 span 容易断裂,且无法对接现代 Jaeger Collector 的 OTLP 接口。
关键替换点:
-
opentracing.Tracer→otel.Tracer -
cfg.NewTracer()→sdktrace.NewTracerProvider()+otel.SetTracerProvider() -
uber-trace-idheader → 标准traceparent(W3C Trace Context)
依赖只装这三个就够了:go.opentelemetry.io/otel、go.opentelemetry.io/otel/exporters/jaeger、go.opentelemetry.io/contrib/instrumentation/github.com/gin-gonic/gin/otelgin(Gin)或 go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp(原生 net/http)。
初始化 TracerProvider 必须在 main() 开头执行
所有 Tracer().Start() 调用都依赖全局 provider。如果 otel.SetTracerProvider(tp) 晚于第一个 span 创建,那些 span 全是 noop —— 看不到数据,日志也不报错,极难排查。
正确写法:
func main() {
// ① 必须最先做
initTracer()
// ② 再启动 HTTP server 或 gRPC server
r := gin.Default()
r.Use(otelgin.Middleware("my-service"))
...
}
func initTracer() {
exporter, _ := jaeger.New(
jaeger.WithAgentEndpoint(
jaeger.WithAgentHost("localhost"),
jaeger.WithAgentPort("6831"),
),
)
tp := sdktrace.NewTracerProvider(
sdktrace.WithBatcher(exporter),
sdktrace.WithResource(resource.NewWithAttributes(
semconv.SchemaURL,
semconv.ServiceNameKey.String("my-service"),
)),
)
otel.SetTracerProvider(tp)
otel.SetTextMapPropagator(propagation.TraceContext{})
}
注意:otel.SetTextMapPropagator() 必须显式设置,否则 traceparent 头不会自动注入/提取。
HTTP 中间件必须包裹 handler,不能只包装路由路径
常见错误:把 otelhttp.NewHandler 或 otelgin.Middleware 只加在某条路由上,比如 r.GET("/api/user", otelhttp.NewHandler(...)) —— 这样只会对那个 handler 生效,中间件链断开,上下文传不下去。
在 Golang 中使用 samber/hot 进行内存缓存,支持 LRU、LFU、TinyLFU、W‑TinyLFU、S3FIFO、ARC、TwoQueue、SIEVE、FIFO 等淘汰算法,提供 TTL、缓存加载器及分片功能。
正确做法:
- Gin:用
r.Use(otelgin.Middleware("my-service"))全局注册,所有 handler 都能拿到带 span 的ctx - net/http:用
http.Handle("/api/", otelhttp.NewHandler(http.HandlerFunc(...), "api")),不是http.HandlerFunc(...)本身 - 出站请求:必须用
otelhttp.NewClient(http.DefaultClient),不能直接用http.DefaultClient
否则上游 span context 不会注入到下游请求头,链路在第一个 HTTP 调用就断掉。
跨 goroutine 和异步调用必须手动传 ctx
90% 的“单节点 trace”问题出在这里:goroutine 启动时没传 req.Context(),导致新协程里 otel.Tracer().Start(ctx, ...) 的 ctx 是 context.Background(),和父 span 完全无关。
错误写法:go doWork() 或 go doWork(r.Context())(r.Context() 在 handler 返回后可能已 cancel)
正确写法:
- 同步业务逻辑:始终用
req.Context()作为起点,如ctx, span := tracer.Start(r.Context(), "db.query") - 异步任务(如
time.AfterFunc):在回调内重新从当前 span 获取 context:spanCtx := trace.SpanContextFromContext(ctx),再用trace.ContextWithSpanContext(context.Background(), spanCtx) - 数据库调用:MySQL 用
otelmysql.Wrap,PostgreSQL 用otelpostgresql.Wrap,否则 SQL 不计入 span
手动创建子 span 时,parent 必须来自 req.Context(),禁止用 context.Background();defer span.End() 前要确保 span 不会因 panic 被跳过 —— 建议用 span.End(span.WithStackTrace(true)) 显式兜底。
golang免费学习笔记(深入):立即使用
在学习笔记中,你将探索golang的核心概念和高级技巧!










