根本原因是未成功初始化tracer就调用startspan;必须显式调用cfg.newtracer()并检查err和tracer非nil,服务名须合法且大小写敏感,udp上报需适配docker网络,http链路需通过opentracing.httpheaderscarrier正确注入/提取context。

Jaeger 客户端初始化失败导致 tracer 为 nil
直接调用 opentracing.GlobalTracer().StartSpan() 报 nil pointer dereference,根本原因是初始化没成功,但代码继续执行了。Go 的 opentracing.GlobalTracer() 默认返回空实现,不 panic,只在真正调用时崩。
- 必须显式调用
cfg.NewTracer()并检查err,不能忽略返回值 - 初始化后加一行
log.Printf("global tracer: %v", opentracing.GlobalTracer())确认非<nil></nil> - 服务名必须合法:只含字母、数字、连字符,且大小写敏感;
SERVICE_NAME环境变量是最稳妥的来源 - UDP 上报默认走
localhost:6831,Docker 或 WSL 下常不通——改用ReporterConfig.AgentHost指向宿主机 IP,或直接切 HTTP reporter(WithCollectorEndpoint("http://jaeger-collector:14268/api/traces"))
HTTP 请求链路断裂,Span 显示为孤立节点
UI 里看到一堆无 Parent Span ID 的 root span,TraceID 在服务间不一致,不是 Jaeger 没收到数据,是上下文根本没传下去。
-
http.Request.Context()是唯一可信载体,手动r.Header.Set("X-Trace-ID", ...)只传 ID,丢掉了采样决策和 Span 状态 - 入口 handler 必须用
tracer.Extract(opentracing.HTTPHeaders, opentracing.HTTPHeadersCarrier(r.Header))解析父 context - 下游 HTTP 请求前,必须用
tracer.Inject(span.Context(), opentracing.HTTPHeaders, opentracing.HTTPHeadersCarrier(req.Header))注入 - 启新 goroutine 处理异步逻辑(如发 MQ、写日志)时,要显式
ctx = context.WithValue(parentCtx, key, val)传 span,否则它会新建 trace
Span 埋点粒度失真,瓶颈定位偏差
火焰图里只看到 “http.handle” 耗时 800ms,但不知道是 DB 慢、Redis 卡还是下游 gRPC 超时——Span 太粗;反之,for 循环里每个 db.QueryRow 都起 Span,Jaeger 存储暴涨、查询卡顿——Span 太细。
- 入口层用
opentracing.StartSpanFromContext(ctx, "http.handle"),类型设为ext.SpanKindRPCServer - 关键依赖调用单独建 client span:
tracer.StartSpan("db.query", opentracing.ChildOf(parentSpan.Context())) - 避免在循环内 StartSpan;改用一个 Span + 标签记录迭代数、失败次数等聚合指标
- 生产环境别用
SamplerTypeConst参数1,改用SamplerTypeProbabilistic配0.01,或接jaeger.RemoteSampler动态调控
链路数据“有上报但查不到”,排查方向错了
本地跑 docker run -d --name jaeger -p 16686:16686 jaegertracing/all-in-one,UI 打开却搜不到自己服务的 trace,不是代码没跑,是数据根本没到 collector。
- 先确认 Agent 是否监听 UDP 6831:容器内
netstat -uln | grep 6831,宿主机用nc -u localhost 6831测试连通性 - Jaeger UI 默认只查最近 1 小时数据,时间范围选错就看不到——右上角时间选择器调宽
- 服务名拼写/大小写/空格不一致,会导致 UI 分组过滤失效,直接搜
service.name: "deardai-shop"(带引号)验证 - 第三方库(
database/sql、redis-go)需额外接入otel/instrumentation包,否则 DB/Redis 调用不会自动产生子 Span
真正卡住的地方往往不是 Span 创建本身,而是 context 传递中断、goroutine 分支漏传、或第三方库未插桩——这些点不补上,再细的埋点也只是一堆孤岛。
golang免费学习笔记(深入):立即使用
在学习笔记中,你将探索golang的核心概念和高级技巧!











