必须在main()开头注册tracerprovider并调用otel.settracerprovider和otel.settextmappropagator,否则所有span为nil;http需用otelhttp.newhandler和otelhttp.transport,数据库用otelsql.injectdriver,goroutine和日志须显式传递context。

Go 里链路追踪不是“加个 SDK 就能用”,而是必须在 main() 开头注册 TracerProvider,否则所有 otel.Tracer() 返回的 span 都是 nil——日志没 trace_id、HTTP 中间件不生效、数据库调用不进链路,全程静默失败。
TracerProvider 必须在 main() 开头注册,且只注册一次
这是最常被跳过的初始化步骤。Go 的 otel.Tracer() 不会 panic,也不报错,但如果你没提前调用 otel.SetTracerProvider(tp),它就永远返回空 span。
- 必须放在
http.ListenAndServe或 Gin/Chi 启动前,且不能包在某个 handler 里临时 new - 别写在
init():单元测试并发跑时可能 panic - Resource(如
service.name)必须作为选项传给sdktrace.NewTracerProvider(),顺序写反会导致服务名丢失,后台无法聚合 - 记得同时调
otel.SetTextMapPropagator(propagation.TraceContext{}),否则跨服务 header 传播失效
HTTP 入口和出口必须用 otelhttp,不能混用原生 client
只在 server 端用 otelhttp.NewHandler,client 还用 http.DefaultClient,链路一定断在第一个出口。
- 入口:用
otelhttp.NewHandler(yourHandler, "api")包裹,不要自己解析traceparent手动 StartSpan - 出口:构造 client 时必须用
&http.Client{Transport: otelhttp.NewTransport(http.DefaultTransport)} - Gin 用户别用
gin.Use()直接包裸http.Handler,要用ginotel.Middleware,否则路由参数(如/order/:id)会退化成/order/* - chi 用户要用
chi.WrapHandler(otelhttp.NewHandler(...)),不是chi.Use(...)
数据库调用必须用 otelsql.InjectDriver,不能手动 Start/End
看到有人在 db.Query() 前后手动 tracer.Start() / span.End(),结果 duration 错、context 不透、事务信息全丢——因为 database/sql 驱动根本不读 context 里的 span。
- 必须用
otelsql.InjectDriver("mysql", &mysql.MySQLDriver{})替换原 driver - GORM 要显式调
db.WithContext(ctx).Query(...),不能只传context.Background() - 别用
context.TODO():它会彻底切断链路,生产代码禁止出现 - 高基数字段(如用户 email、订单号)别直接塞进 span attribute,会撑爆 backend 存储
goroutine 和日志必须显式传 ctx,不能靠闭包捕获
Go 的 goroutine 不继承父 context,log.Printf() 更不会自动带 trace_id——这两处是链路断裂最高发区域。
- 启动新 goroutine 时,必须显式传入带 span 的 ctx:
go func(ctx context.Context) { ... }(req.Context()) - 日志打 trace_id 要手动取:
trace.SpanFromContext(ctx).SpanContext().TraceID().String() - 推荐桥接器如
otellogrus,或封装 log.WithValues("trace_id", ...) - gRPC 客户端必须加拦截器:
grpc.WithUnaryInterceptor(otelgrpc.UnaryClientInterceptor()),光传 ctx 不够
真正难的不是埋点,而是确保每个环节都“接住”了上一跳传来的 context——从 HTTP 入口、到 goroutine、再到 DB 驱动和下游 gRPC,漏掉任意一环,trace 就断成孤岛。本地跑通不代表 K8s 里能用,exporter endpoint 写死 localhost:4317 上线后必然连不上 collector。
golang免费学习笔记(深入):立即使用
在学习笔记中,你将探索golang的核心概念和高级技巧!











