新项目必须用 opentelemetry + jaegerexporter 或 otlphttp,因 jaeger-client-go 已归档、不支持现代 context 模型,导致 goroutine 和 db 调用中 span 断裂;其初始化常因 servicename 或采样器未设全而静默返回 nil,引发 panic 或链路残缺。

新项目别碰 jaeger-client-go,它已归档,链路大概率断在 goroutine 或 DB 调用里——不是不报错,而是 span 无声丢失。
为什么 jaeger-client-go 初始化总 panic 或链路残缺
根本原因就两个:配置字段改了但没调 cfg.NewTracer(),或 ServiceName 和采样器没设全。这个包是值类型,字段赋值不等于实例生成;ServiceName 空着调 NewTracer() 会静默返回 nil,后续所有 StartSpan() 都 panic。
-
Sampler.Type必须显式设为"const"(开发)或"probabilistic"(生产),Param也得配套填上,留空就崩溃 - UDP reporter 默认走
localhost:6831,Docker 网络或防火墙一拦,span 就发不出去,日志里只显示reporter: submitting X spans却不见 UI - 没
defer closer.Close(),短命命令行工具跑完还卡着 goroutine,内存缓慢泄漏
HTTP 服务埋点为什么只有一跳,不往下传
标准库 http.ServeMux 和 http.Client 完全不处理 trace 上下文,靠手写 uber-trace-id 头或直接塞 req.Header.Set() 是最常见翻车点——格式错一位、大小写不对、漏了 flags 字段,下游就解析失败。
- 服务端必须用
othttp.Middleware包装 handler,它从traceparent(W3C 标准)或uber-trace-id头提取 context,并绑定到req.Context() - 客户端必须用
othttp.Transport替换http.DefaultTransport,否则tracer.Inject()永远不执行 - 别信
opentracing.HTTPHeaders这个常量名——它只是个 key,不是自动行为,不包装 transport / middleware 就等于没做
跨 goroutine 和数据库调用怎么避免 span 断裂
Go 不继承 context,90% 的“单跳链路”问题出在这两处:异步任务启动时传了 context.Background(),或 DB 查询没被 wrapper 包住。
- 异步操作如
time.AfterFunc或go doWork(ctx),ctx必须是带 span 的req.Context(),不能是新建的 background - MySQL 用
otelmysql.Wrap(db),PostgreSQL 用otelpostgresql.Wrap(db),原生*sql.DB的Query、Exec全部不进 span - 手动
StartSpan()时,parent 必须来自req.Context(),即tracer.Start(ctx, "db.query"),不是tracer.Start(context.Background(), ...)
本地验证链路是否真正贯通的最快方式
别等 Jaeger UI 刷新,先看 stdout 输出和请求头——链路完整性的第一道关卡是 traceparent 是否透传成功。
- 初始化 tracer provider 时加
stdoutexporter.NewExporter(),直接打印 JSON,确认 span 数量、parentID、traceID 是否连续 - cURL 请求时带上
-H "traceparent: 00-123...-456...-01",服务端 middleware 能否正确提取并创建 root span - 用
otelhttp.NewClient()发请求,抓包看发出的请求头里有没有traceparent,没有就是 client 没 wrap
真正难的不是把 span 打出来,而是让每个 goroutine、每次 DB 查询、每条 gRPC 调用都共享同一个 context 实例——稍有疏忽,链路就在某次 go func() { ... }() 里彻底消失。
golang免费学习笔记(深入):立即使用
在学习笔记中,你将探索golang的核心概念和高级技巧!











