必须用fiberotel.Middleware,不能套otelhttp.NewHandler;因后者返回http.Handler,与Fiber的func(*fiber.Ctx) error中间件签名不兼容,硬塞会导致ctx.Params为空、span name退化为HTTP GET、traceparent无法回写。

必须用 fiberotel.Middleware,不能套 otelhttp.NewHandler;Fiber 的 app.Use() 不接受标准 http.Handler,硬塞会导致路由参数丢失、ctx.Params 为空、span name 退化为 HTTP GET。
为什么 fiber.Use(otelhttp.NewHandler(...)) 会失效
Fiber 的中间件签名是 func(*fiber.Ctx) error,而 otelhttp.NewHandler 返回的是 http.Handler(即 func(http.ResponseWriter, *http.Request))。两者类型不兼容,强行转换或包装会绕过 Fiber 的上下文生命周期管理。
- 路由参数(如
:id)无法注入fiber.Ctx,ctx.Params("id")永远为空 -
ctx.Next()和ctx.SendStatus()等方法行为异常,可能 panic 或静默丢 span - span name 固定为
HTTP GET,不是真实路径(如GET /api/users/:id),导致 Jaeger 聚合失效 - response header 写入被 Fiber 拦截,
traceparent无法回写,下游服务收不到上下文
正确初始化 TracerProvider 并注册 fiberotel
fiberotel.Middleware 依赖全局 otel.TracerProvider 和 otel.TextMapPropagator,漏掉任一环节都会让所有 span 为 nil 且静默无日志。
- 在
main()第一行调用sdktrace.NewTracerProvider(),传入resource.WithAttributes(semconv.ServiceNameKey.String(os.Getenv("SERVICE_NAME"))) - 紧跟着执行
otel.SetTracerProvider(tp)和otel.SetTextMapPropagator(propagation.TraceContext{}) - 采样器必须显式配置:
sdktrace.WithSampler(sdktrace.ParentBased(sdktrace.TraceIDRatioBased(0.01))),否则默认sdktrace.NeverSample()导致零 span - 导出器(如
jaeger.NewExporter或otlphttp.NewExporter)需通过sdktrace.WithBatcher()注入,且进程退出前调用tp.Shutdown(ctx)
fiberotel.Middleware 的关键参数与陷阱
fiberotel.Middleware 默认不提取 traceparent,也不记录 HTTP 语义字段(如 http.status_code, http.route),必须手动开启。
使用 uber-go/dig 在 Golang 中实现依赖注入——基于反射容器,提供 Provide/Invoke、dig.In/dig.Out 参数与结果对象,支持命名依赖等
- 第二个参数是 span 名前缀,建议设为服务名(如
"user-service"),不是空字符串——空字符串会被 Jaeger 静默丢弃 - 必须传
fiberotel.WithSpanOptions(trace.WithAttributes(semconv.HTTPRouteKey.String(ctx.Route().Path)))才能记录真实路由 - 若要记录请求体大小或响应状态码,需额外加
trace.WithAttributes(...),fiberotel不自动补全这些字段 - 健康检查路径(如
/health)建议用fiber.Skip排除,避免低价值 span 淹没采样配额
下游 HTTP 调用和数据库怎么透传 context
Fiber 的 ctx 不等于 context.Context,必须显式转换;DB 驱动也不会读 fiber.Ctx 里的值。
- 发起 HTTP 请求前:用
req = req.WithContext(ctx.Context())把 Fiber 上下文转成标准context.Context,再交给otelhttp.NewClient(http.DefaultClient).Do(req) - 数据库查询必须用
db.QueryContext(ctx.Context(), ...),不能用db.Query();GORM 用户调db.WithContext(ctx.Context()).Find(&u) - 启动 goroutine 时,传
ctx.Context()而非ctx:例如go doWork(ctx.Context()),并在函数内用trace.SpanFromContext(ctx)获取 parent - 别用
ctx.Locals("trace_id")存 span ID——它不跨服务,且无法被propagator.Inject()识别
最易忽略的一点:Fiber 中间件顺序决定 span 生命周期。如果自定义 auth 中间件里提前 return 或 panic,defer span.End() 就不会执行,整个 trace 会静默截断——务必在每个中间件末尾加 ctx.Context().Done() 监听或统一 recover 处理。
golang免费学习笔记(深入):立即使用
在学习笔记中,你将探索golang的核心概念和高级技巧!










