Fiber 默认不兼容 otelgin/otelchi,因其使用 fiber.Ctx 而非 gin.Context,且中间件签名无 context.Context 参数;需手动实现 OTel 中间件,从 header 提取 trace context、创建 span 并注入 c.Locals,跨服务调用时须用 http.NewRequestWithContext() 并显式注入 header。

Fiber 是 Go 生态中轻量、高性能的 Web 框架,但官方不提供 OpenTelemetry 自动埋点支持,必须手动集成中间件 + 正确传播 context。直接套用 Gin 或 Echo 的 OTel 中间件会失败,因为 Fiber 的中间件签名、上下文模型和请求生命周期与它们完全不同。
为什么 Fiber 默认不兼容 otelgin / otelchi
OpenTelemetry 官方 contrib 包中没有 github.com/gofiber/fiber/v2 的 instrumenter;otelgin.Middleware 依赖 *gin.Context,而 Fiber 使用 *fiber.Ctx,二者无类型兼容性。强行 import 并 cast 会导致 panic 或 span 丢失。Fiber 的中间件函数签名是 func(*fiber.Ctx) error,不接收 context.Context 入参,因此必须从 *fiber.Ctx 中显式提取并注入 trace context。
手动实现 Fiber 的 OTel 中间件(推荐)
核心逻辑:在请求进入时从 HTTP header(如 traceparent)解析 trace context,创建 root 或 child span;在响应返回前结束 span;全程将 span context 注入 fiber.Ctx.Locals 供下游 handler 使用。
- 确保已初始化全局
TracerProvider(参考你知识库中的initTracer()函数) - 使用
otel.GetTextMapPropagator().Extract()从c.Request().Header解析传入的 trace context - 调用
tracer.Start()时传入提取出的 context,而非context.Background() - 结束 span 必须放在
defer中,且在c.Next()之后,否则可能因 panic 而跳过
func OtelMiddleware(serviceName string) fiber.Handler {
tracer := otel.Tracer(serviceName)
return func(c *fiber.Ctx) error {
ctx := c.Context()
// 从 header 提取父 context
propCtx := otel.GetTextMapPropagator().Extract(ctx, propagation.HeaderCarrier(c.Request().Header))
// 创建 span,parent 来自 propagator
spanCtx, span := tracer.Start(propCtx, c.Path(), trace.WithSpanKind(trace.SpanKindServer))
defer span.End()
// 将 span context 注入 fiber.Ctx,供后续 handler 获取
c.Locals("span_ctx", spanCtx)
c.Locals("span", span)
err := c.Next()
if err != nil {
span.RecordError(err)
span.SetStatus(codes.Error, err.Error())
}
return err
}
}
跨服务调用时手动注入 trace context
Fiber 自身不拦截 http.Client 请求,所以对外发起的 HTTP 调用(如调用其他微服务)必须显式注入 traceparent header。不能依赖自动 instrumentation —— 目前没有稳定的 opentelemetry-go-contrib/instrumentation/net/http for Fiber 的适配层。
- 从
c.Locals("span_ctx")取出当前 span context - 用
otel.GetTextMapPropagator().Inject()写入http.Header - 务必在
http.NewRequestWithContext(spanCtx, ...)中传入该 context,否则 Inject 无效
func callDownstream(c *fiber.Ctx) error {
spanCtx, ok := c.Locals("span_ctx").(context.Context)
if !ok {
spanCtx = context.Background()
}
req, _ := http.NewRequestWithContext(spanCtx, "GET", "http://other-service/api", nil)
// 注入 trace context 到 header
otel.GetTextMapPropagator().Inject(spanCtx, propagation.HeaderCarrier(req.Header))
resp, err := http.DefaultClient.Do(req)
// ...
}
常见错误:Span 显示为孤立节点或 TraceID 不连续
这是 Fiber 集成中最常踩的坑,根本原因在于 context 传递断裂:
-
span.End()被提前调用(比如放在c.Next()前),导致子 span 无法关联 parent - 跨服务调用时用了
http.NewRequest()而非http.NewRequestWithContext(),propagator 无 context 可 inject - 忘记在
fiber.Ctx.Locals中存 span context,下游 handler 只能 fallback 到context.Background(),生成新 trace - OTLP 导出器配置未启用 TLS 或 endpoint 错误(如写成
localhost:4318但 collector 实际监听:4317),数据发不出去,Jaeger 看不到任何链路
真正关键的不是加了多少 span,而是每个 HTTP 跳转之间是否严格保持 traceparent header 的读-传-写闭环。Fiber 不帮你做这件事,得一行行写清楚。


















