gin 默认不传递 trace context 是因为 http 无状态,其自身不处理上下文传播;otelgin.middleware 仅在当前服务创建 span,下游请求需用 otelhttp.newtransport 包装 client 才能自动注入 traceparent header,否则链路中断。

为什么 Gin 默认不传递 trace context?
因为 HTTP 是无状态协议,Gin 本身不处理上下文传播——otelgin.Middleware 只负责在当前服务内创建 Span,但不会自动把 traceparent header 带出去。如果你用 http.Client 发起下游请求却没封装,链路就断在第一个服务出口。
怎么让 Gin 请求带出 traceparent header?
必须用 otelhttp.NewTransport 替换默认传输层,否则手动加 header 容易漏掉或格式错。常见错误是只调用 otelhttp.Do 却忽略 transport 初始化。
- 初始化 client 时必须传入包装后的 transport:
http.Client{Transport: otelhttp.NewTransport(http.DefaultTransport)} - 不要直接用
http.Get或http.Post,它们绕过 transport,trace context 丢失 - 如果用了第三方 HTTP 库(比如 resty),需确认它支持自定义 transport;否则得改用原生
http.Client
跨服务调用时 parent span 总是 root,怎么办?
这是 propagator 没生效的典型表现。OpenTelemetry 默认使用 W3C tracecontext 标准,但若服务间 header 名不一致(比如一方用 b3,另一方用 traceparent),context 就无法提取。
- 所有服务必须统一 propagator:
otel.SetTextMapPropagator(propagation.TraceContext{}) - 检查下游服务是否也初始化了相同 propagator;只有一方设没用
- 用 curl 或 Postman 手动发请求测试 header 是否真实存在:
curl -H "traceparent: 00-123...-456...-01" http://service-b/
Gin 中间件和手动 span 创建能混用吗?
能,但要注意 context 继承关系。直接用 tracer.Start(ctx, "xxx") 会创建新 trace,除非你显式把上游 context 传进去。
- 在 handler 里获取当前 span:
span := trace.SpanFromContext(c.Request.Context()) - 需要子 span 时,用
tracer.Start(c.Request.Context(), "db.query"),不是context.Background() - 别在 goroutine 里丢掉原始 context:异步任务必须用
context.WithValue或context.WithCancel携带 span context
inject/extract 流程——少一个 header、漏一次 transport 包装、差一个 propagator 配置,链就断了。











