Fiber项目必须用OpenTelemetry+otelhttp.NewHandler包裹app.Handler(),因无官方otelfiber中间件;TracerProvider须在main()开头初始化,否则span全为noop;HTTP透传依赖otelhttp自动处理traceparent,手动解析易断链。

别用 jaeger-client-go,它已归档,不支持 Go 原生 context 透传,Fiber 中 goroutine 分支极易断链;必须用 OpenTelemetry + otelhttp 或专用 Fiber 中间件,且 TracerProvider 初始化时机错不得。
为什么 Fiber 项目不能照搬 Gin 的 otelgin.Middleware
Fiber 没有官方维护的 otel-fiber 中间件(截至 2026 年底仍无 go.opentelemetry.io/contrib/instrumentation/github.com/gofiber/fiber/v2/otelfiber),社区方案多为手动封装或复用 otelhttp。直接套用 otelgin 会编译失败——类型不匹配、fiber.Ctx 无法转 *http.Request。
可行路径只有两条:
- 用
otelhttp.NewHandler()包裹 Fiber 的底层http.Handler(推荐,侵入小、生命周期对齐) - 手写中间件,调用
otel.GetTextMapPropagator().Extract()+otel.Tracer().Start(),但必须显式绑定ctx.Context()到fiber.Ctx.Locals或自定义字段,否则子 span 无法继承
第一种方式更稳:Fiber v2.49+ 默认基于 net/http,app.Handler() 返回标准 http.Handler,可直接喂给 otelhttp.NewHandler()。
立即学习“go语言免费学习笔记(深入)”;
正确初始化 TracerProvider 必须在 main() 最开头
otel.SetTracerProvider(tp) 晚于第一个 HTTP 请求,所有 span 都是 noop —— UI 空白、日志无报错、极难定位。
正确顺序:
使用 uber-go/dig 在 Golang 中实现依赖注入——基于反射容器,提供 Provide/Invoke、dig.In/dig.Out 参数与结果对象,支持命名依赖等
- 加载配置(如
viper.ReadInConfig()或环境变量) - 立即调用
initTracer(),内含sdktrace.NewTracerProvider()和otel.SetTracerProvider() - 再创建
fiber.App,再注册路由
验证是否生效:log.Printf("tracer: %v", otel.GetTracerProvider()) 输出不能是 <nil></nil>。ServiceName 必须非空、合法(小写、无空格、无下划线),例如 "user-service",填错 Collector 会静默拒收。
HTTP 层 traceparent 透传必须靠 otelhttp.NewHandler
Fiber 不自动解析 traceparent(W3C 标准),也不自动注入。手动读写 ctx.Request().Header.Get("traceparent") 容易漏掉 tracestate 或格式错位,Jaeger 直接丢弃整个 span。
正确做法:
- 用
otelhttp.NewHandler(app.Handler(), "fiber-app")包裹原始 handler - 启动 server 时传入该包装 handler:
http.ListenAndServe(":3000", wrappedHandler) - 下游 HTTP 调用必须用
otelhttp.Client(不是http.DefaultClient),否则出站请求无traceparent
注意:otelhttp.NewHandler 会自动完成 Extract → StartSpan → 注入 context.Context 到 http.Request → 绑定到生命周期;你业务代码里 ctx.Context() 就自带 parent span,可直接用于 DB/gRPC 子调用。
DB、gRPC、goroutine 中 context 透传最容易漏
Fiber handler 内启 goroutine 或调 DB 时,若只传 ctx(fiber.Ctx)不传其 .Context(),span 就断了。你在 Jaeger 里看到的“孤立 root span”,90% 是这里漏的。
关键检查点:
- DB 连接:MySQL 用
otelmysql.Wrap(sql.Open()),PostgreSQL 用otelpostgresql.Wrap(pgsql.Open()),否则 SQL 不计入 span 时间 - gRPC 调用:客户端必须用
otelgrpc.UnaryClientInterceptor(),服务端用otelgrpc.UnaryServerInterceptor();裸grpc.Dial()必断链 - goroutine:写成
go doWork(ctx.Context()),而非go doWork()或go doWork(ctx) - 定时回调(如
time.AfterFunc):原ctx.Context()可能已 cancel,需用context.WithTimeout显式派生
UDP reporter(jaeger.WithAgentEndpoint)在 Docker/K8s 下默认失效:容器内 "localhost" 指自己,得换成 "jaeger-agent:6831" 或 "host.docker.internal:6831";开发阶段建议先切 HTTP reporter(jaeger.NewHTTPReporter("http://jaeger-collector:14268/api/traces")),失败会明确报错。

















