jaeger客户端初始化必须显式指定服务名和udp上报地址,否则span静默丢弃;http调用需手动注入trace context;span生命周期须用defer finish;跨goroutine需传递span.context;框架集成时context key须用私有类型避免冲突。

Jaeger客户端初始化必须指定服务名和上报地址
不设 service_name 或 local_agent_host_port 会导致 tracer 初始化失败,且错误静默(无 panic,但所有 span 都丢弃)。Go 官方 jaeger-client-go 默认使用 UDP 上报,若 Jaeger Agent 未监听 localhost:6831,span 就会直接丢失,连日志都不打。
实操建议:
- 用
jaeger.New(jaeger.WithServiceName("your-service"))显式传入服务名,不能依赖环境变量或空字符串 - UDP 地址必须写成
"localhost:6831"这种 host:port 格式,"127.0.0.1:6831"在某些容器网络下可能不通 - 开发时可加
jaeger.WithSampler(jaeger.NewConstSampler(true))强制采样,避免因默认 0.001 采样率看不到 trace - 上线前务必切换为
jaeger.NewRemoteReporter并确认 Agent 可达,用net.Dial("udp", "localhost:6831")简单探测一次
HTTP 请求自动埋点需手动注入 context,不能只靠中间件
Jaeger 的 Go SDK 不提供开箱即用的 HTTP 中间件自动透传 trace context。如果你只在 handler 入口调用 opentracing.StartSpanFromContext,但没把新 span 注入到下游 HTTP 请求的 context.Context,那整个调用链就断了——下游服务收不到 uber-trace-id header。
实操建议:
- 发起 HTTP 请求前,用
tracer.Inject(span.Context(), opentracing.HTTPHeaders, opentracing.HTTPHeadersCarrier(req.Header))手动注入 - 不要依赖
req.WithContext()自动传播:span context 和 HTTP header 是两套机制,必须显式 inject + extract - 如果用
http.Client,推荐封装一个带 trace 注入的 helper 函数,避免每次重复写 4 行 inject 逻辑 - 注意
req.Header是 map 类型,inject 后 header 大小会变,别在 inject 后再修改 header 键名(比如转大写)
Span 生命周期管理容易泄漏 goroutine
常见错误是调用 span.Finish() 前忘了 defer,或者在异步 goroutine 里 finish 了本该由主 goroutine 结束的 span。后果是 span 数据错乱、parent-child 关系断裂,甚至内存持续增长(span 内部有 timer 和 channel)。
实操建议:
- 只要 span 在函数作用域内创建,就立刻写
defer span.Finish(),哪怕后续有 error return - 跨 goroutine 的 span 必须显式传递 context:
go func(ctx context.Context) { ... }(span.Context().WithSpan(span)),不能只传原始 context - 避免在 select/case 或 channel receive 后才 finish span——可能永远等不到,导致 span 悬挂
- 用
runtime.SetFinalizer(span, func(s interface{}) { /* log warning */ })做兜底检测(仅调试期),正式环境删掉
与 Gin/Echo 等框架集成时,context key 冲突很隐蔽
Gin 默认用 gin.Context 自己的 key 存值,而 Jaeger 的 opentracing.Span 也存进 context.Context。如果中间件里用了 c.Set("trace_id", ...) 这类操作,可能覆盖掉 span context,导致 downstream 调用拿不到 parent span。
实操建议:
- 所有自定义 context key 必须用私有类型做 key,例如
type traceKey struct{},而不是字符串 - Gin 中间件获取 span 应该用
opentracing.SpanFromContext(c.Request.Context()),而非从 c.Keys 读 - Echo 用户注意:Echo 的
c.Request().Context()是干净的,但c.Get("something")不影响 context,这点比 Gin 安全 - 如果必须往 context 写业务字段,统一用
context.WithValue(ctx, myKey{}, val),别碰opentracing相关的 key
真正麻烦的不是初始化 tracer,而是 span 的起始点选择是否覆盖全部关键路径,以及跨 goroutine、跨 HTTP、跨 context.WithValue 的三重传播是否漏掉任意一环。漏一处,整条链就碎成几段。
golang免费学习笔记(深入):立即使用
在学习笔记中,你将探索golang的核心概念和高级技巧!











