Echo的span名总显示为GET /是因为otelhttp.NewHandler未获取路由匹配结果,正确做法是使用echootel.Middleware,它在路由解析后用echo.Route().Path设span名,并需最先注册且确保中间件透传context。

为什么 Echo 的 span 名总显示为 GET /,不是真实路由名?
因为默认用 otelhttp.NewHandler 包裹整个 echo.Echo 实例时,它只看到原始 HTTP 方法和路径,不知道 Echo 内部的路由匹配结果。最终 span name 固定为 GET / 或 POST /,丢失了 /users/:id 这类语义信息。
正确做法是使用官方维护的 echootel.Middleware,它会在 Echo 的路由解析完成后才创建 span,并自动把 echo.Route().Path 作为 span name。
- 必须在
echo.New()之后、Start()之前注册中间件:e.Use(echootel.Middleware("my-service")) - 不要手动调用
otelhttp.NewHandler,否则会绕过 Echo 的路由上下文 - 若需排除健康检查路径,传参
echootel.WithFilter(func(c echo.Context) bool { return !strings.HasPrefix(c.Request().URL.Path, "/health") })
如何让下游 HTTP 请求自动带上 traceparent?
仅靠 echootel.Middleware 只能处理入向请求,出向请求(比如用 http.Client 调其他服务)默认不携带 trace context,链路会在第一个 outbound call 断掉。
必须启用 httptrace 插件并确保 client 使用 instrumented 版本:
- 初始化时调用
otelhttp.NewTransport(&http.Transport{})替换默认 transport - 所有 outbound 请求必须用这个 instrumented client 发起,不能混用原生
http.DefaultClient - 若用
echo.HTTPClient,需提前赋值:e.HTTPClient = &http.Client{Transport: otelhttp.NewTransport(http.DefaultTransport)} - 验证方式:在 handler 中打印
req.Header.Get("traceparent"),非空即生效
中间件顺序错乱导致 span 断链的典型表现
常见现象是 Jaeger 里看到多个 root span,或子 span 的 parent_id 为空。根本原因是自定义中间件(如 auth、cors)没有透传 context,导致后续 span 创建时找不到 parent。
开箱即用的技能链路由引擎。13 条预定义链覆盖搜索、开发、审查、MLOps、法律、创意等场景,三层路由架构(触发词→SAD反馈→DAG编排),recall@10=96.97%。配置驱动(chains.yaml),零代码扩展。pip install skill-weave-chains 一键安装。
Echo 的中间件链是线性执行的,echootel.Middleware 必须处于最外层(即最先注册),且所有下游中间件必须显式使用 c.Request().WithContext() 更新 request context:
- 错误写法:
func(c echo.Context) error { /* 未调用 c.SetRequest() */ return nil } - 正确写法:
func(c echo.Context) error { ctx := c.Request().Context(); /* do sth */; c.SetRequest(c.Request().WithContext(ctx)); return nil } - 特别注意:
echo.MiddlewareFunc返回的 error 不会中断 context 传递,但 panic 会——务必 recover
OTLP 导出失败但没报错?检查这三点
ExportFailed 错误常被静默吞掉,尤其在服务启动初期导出器未就绪时。Span 看似生成了,实则全丢弃。
- 确认
otlphttp.NewClient或otlptrace.New的 endpoint 地址可连通(如curl -v http://localhost:4318/v1/traces) - 检查是否漏设
sdktrace.WithResource(resource.NewWithAttributes(...)),部分 collector 拒绝无 service.name 的数据 - 本地开发建议先切到
stdoutexporter验证 span 内容,再切回 OTLP
真正难调试的是跨 goroutine 场景:Echo 的 c.Async() 或 go func() 里直接用 tracer.Start(),会丢失父 context —— 此时必须用 trace.ContextWithSpan 显式绑定。










