Echo中间件中直接用otel.Tracer会丢span,因req.Context()默认不携带trace context,须手动Extract后用ContextWithSpanContext注入并替换req;HTTPErrorHandler需在活跃span中RecordError;Group前缀不影响span name,但路径变量需用HTTPRouteKey规范;DB span需透传req.Context()至查询调用点。

为什么 echo.MiddlewareFunc 里直接用 otel.Tracer 会丢 span?
因为 Echo 的中间件执行顺序和 OpenTelemetry 的 context 传递机制不自动对齐:默认情况下,req.Context() 并不携带 trace context,即使你调用了 otel.GetTextMapPropagator().Extract(),也得手动把解析出的 SpanContext 注入到新 context 中,再传给后续 handler。
常见错误现象是:所有 HTTP span 都显示为 root span(parent span ID 为空),或 trace ID 每次请求都变,无法串联 DB、RPC 等下游调用。
- 必须在中间件开头调用
propagator.Extract()从req.Header提取traceparent - 用
trace.ContextWithSpanContext()构造带 span 的新 context,并替换req.Request = req.WithContext(newCtx) - 确保下游 handler(比如你的业务路由)使用的是这个新 context,而不是原始
req.Context()
如何让 echo.HTTPErrorHandler 也上报错误 span?
默认的 error handler 在 span 结束后才触发,所以直接在里面调用 span.RecordError() 无效——span 已 close。正确做法是把 error 记录提前到 handler 执行阶段,或复用当前 span 的 context。
实操建议:
立即学习“go语言免费学习笔记(深入)”;
使用 uber-go/dig 在 Golang 中实现依赖注入——基于反射容器,提供 Provide/Invoke、dig.In/dig.Out 参数与结果对象,支持命名依赖等
- 在自定义中间件中 defer 调用
span.End()前,检查echo.HTTPErrorHandler是否被触发(可通过c.Response().Status()或捕获 panic 判断) - 更稳妥的方式:用
echo.HTTPErrorHandler回调函数内,通过c.Request().Context()获取当前 span,再调用span.RecordError(err)和span.SetStatus(codes.Error, err.Error()) - 注意:只有当该 context 确实绑定了活跃 span 时才安全调用,否则加一层
span := trace.SpanFromContext(ctx); if span.IsRecording() { ... }防 panic
echo.Group 路由前缀是否影响 span name?
不影响。OpenTelemetry 的 HTTP 框架插件(如 otelhttp)默认用 req.URL.Path 生成 span name,而 Echo 的 Group 只是路由注册时的路径前缀,最终匹配到的 c.Request().URL.Path 是完整路径(如 /api/v1/users),不是 /v1/users。
但如果你手动设置 span name(比如用 trace.WithSpanName("user-get")),那就完全由你控制;若依赖自动命名,要注意:
- 路径中带变量(如
/users/:id)会导致每个 ID 生成不同 span name,增加 cardinality,建议用semconv.HTTPRouteKey手动设为/users/{id} - 避免在 span name 中拼接用户数据(如
"GET /users/" + id),防止高基数或敏感信息泄露 - 如果用了
echo.New().Router().Add()手动注册,要确认req.URL.Path未被中间件意外修改(比如重写中间件没同步更新 context)
DB 查询 span 怎么和 HTTP span 关联上?
关键在于 DB 层是否接收并使用了上游传下来的 context。Echo 中间件注入的 context 必须一路透传到 DB 调用点,否则 DB span 会新建 trace。
以 sql.DB 为例:
- 不要用
db.Query(...),改用db.QueryContext(c.Request().Context(), ...) - 如果封装了 DAO 层,确保每个方法第一个参数是
context.Context,且调用时传入c.Request().Context(),而不是context.Background() - 用
go.opentelemetry.io/contrib/instrumentation/database/sql包时,需在sql.Open()后调用RegisterDBStatsMetrics()并传入 tracer,否则只记录 SQL,不关联 trace - 验证方式:查 Jaeger/Tempo,点开一个 HTTP span,看是否有子 span 标记为
db.query且 trace ID 相同
最常被忽略的是 ORM(如 GORM)默认不读 context,必须显式启用:GORM v2 要调用 db.WithContext(ctx).First(&u),v1 则基本不支持,得升级或换方案。

















