OpenTelemetry是Go微服务观测基建默认起点,HTTP/gRPC调用不集成即丧失请求因果链;需确保中间件包裹Handler、gRPC客户端服务端拦截器成对配置、OTLP导出器正确指向Collector、所有异步及下游调用显式传递带trace的context。

OpenTelemetry 在 Go 微服务中不是“可选插件”,而是观测基建的默认起点。只要服务间存在 HTTP 或 gRPC 调用,不集成 OpenTelemetry 就等于主动放弃请求因果链——你看到的慢接口,大概率是上游某个没传 traceparent 的服务拖垮的。
HTTP 请求进不来 trace?检查中间件是否真生效
很多项目写了 TraceMiddleware,但实际请求里压根没生成 Span。常见原因不是代码写错,而是中间件没被注册到路由链路里。
- 确认你的
http.Handler确实包裹了该中间件,比如:http.ListenAndServe(":8080", TraceMiddleware(myMux)),而不是只对某个子路由生效 - 检查是否在中间件里漏掉了
span.End()—— 没这句,Span 就永远不会上报,Jaeger 里永远空着 - 验证请求头是否携带
traceparent:用curl -H "traceparent: 00-4bf92f3577b34da6a3ce929d0e0e4736-00f067aa0ba902b7-01" http://localhost:8080/api测试上下文能否透传
gRPC 客户端/服务端 Span 断开?别只配一边
gRPC 场景下,otelgrpc.UnaryClientInterceptor() 和 otelgrpc.UnaryServerInterceptor() 必须成对出现。只配客户端,服务端收不到上下文;只配服务端,调用方无法关联出入口 Span。
- 客户端 Dial 时必须带
grpc.WithUnaryInterceptor(otelgrpc.UnaryClientInterceptor()) - 服务端
grpc.NewServer()初始化时,要传入grpc.UnaryInterceptor(otelgrpc.UnaryServerInterceptor()) - 注意:gRPC 默认不传播
context.Context中的 Span 数据,otelgrpc插件就是干这个的——它把traceparent编码进metadata,再在远端解码还原
Span 上报失败?先盯住 OTLP 导出器配置
otlptracehttp.New() 默认连 localhost:4318,但生产环境几乎从不这么用。导出失败不会 panic,只会静默丢数据。
- 检查
OTEL_EXPORTER_OTLP_ENDPOINT环境变量是否设置正确,比如http://otel-collector:4318 - 确认 Collector 是否监听 HTTP(不是 gRPC)端口,且启用了
otlphttpreceiver - 加一行日志验证导出器初始化结果:
log.Printf("OTLP exporter ready: %+v", exporter),避免err != nil被忽略 - 本地调试时,可用
otelcol-contrib启一个简易 Collector:otelcol-contrib --config ./config.yaml,配置里至少包含otlphttp和loggingexporter
Trace ID 不跨服务?根源常在 context 传递漏点
即使中间件和拦截器都配全了,Trace ID 还是断掉,问题往往藏在业务代码里——尤其是异步逻辑或第三方库调用路径。
立即学习“go语言免费学习笔记(深入)”;
- 所有跨 goroutine 的操作(如
go func() { ... }())必须显式传递ctx,不能用context.Background()新建 - 数据库查询、Redis 调用、HTTP client 请求,都要用带
ctx的方法,比如db.QueryContext(ctx, ...)、redis.Client.Do(ctx, ...) - 如果你用了
sqlx或gorm,它们默认不支持 context 传播,得手动 wrap 或启用对应插件(如gorm.io/plugin/opentelemetry)
context.Context 走完全程。漏掉任意一环,链路就断在那儿,而你很难一眼发现断点在哪。


















