log.Printf()永不自动带trace_id,因其完全不读context.Context;须用封装logger从ctx提取trace_id字段并显式注入,否则日志断链。

Go 里没有 MDC,但能用 context.Context 实现等效效果;不显式透传 trace_id,日志就断在第一个 goroutine 里——不是没打日志,是日志里根本没 trace 字段。
为什么 log.Printf() 永远不会自动带 trace_id
Go 标准库的 log.Printf() 完全不读 context.Context,也不查当前 span。你调 trace.SpanFromContext(ctx) 得到的 SpanContext,它压根不知道。
- 现象:HTTP handler 里打了带
trace_id的日志,进到 DB 层或 goroutine 后,日志突然没了trace_id - 原因:要么没把
ctx传下去,要么下游用了log.Printf()或没封装的logrus.Info() - 正确姿势:所有日志必须从封装好的 logger 实例发出,该实例内部调用
trace.SpanFromContext(ctx).SpanContext().TraceID().String()提取字段
zap 如何自动注入 trace_id 到每条日志
zap 本身不支持 context 自动提取,必须自己包一层。别用 logger.With(zap.String("trace_id", ...)) 手动塞——漏一次就断链。
- 定义安全 key:
type ctxKey string; const traceIDKey ctxKey = "trace_id" - 入口 middleware 解析
traceparent后,用context.WithValue(ctx, traceIDKey, tid)注入 - 写 wrapper:
func (l *TraceLogger) With(ctx context.Context) *zap.Logger { tid := ctx.Value(traceIDKey); return l.logger.With(zap.String("trace_id", tid.(string))) } - 使用:
logger := NewTraceLogger(zap.L()).With(r.Context()); logger.Info("db query start")
goroutine 启动时 trace_id 丢失的典型场景
写 go func() { doSomething() }() 是最常见断裂点。闭包捕获的外层 ctx 在新 goroutine 中不可靠,尤其当外层函数已返回、ctx 被 cancel 后。
立即学习“go语言免费学习笔记(深入)”;
- 错误写法:
go func() { db.QueryRow(ctx, ...) }()—— 这里ctx是外层变量,可能已失效 - 正确写法:
go func(ctx context.Context) { db.QueryRow(ctx, ...) }(req.Context()) - 更稳妥:
ctx = trace.ContextWithSpan(context.Background(), span); go worker(ctx),确保新 goroutine 有独立、有效的 span 上下文 - 注意:
context.WithValue()本身不丢,丢的是“谁往里塞了”和“谁主动去取”
gRPC 和 HTTP 日志如何共享同一个 trace_id
HTTP 入口解析出 traceparent,gRPC 出口必须透传,否则日志在服务边界就分裂成两个 trace。
- HTTP client 侧:用
otelhttp.NewTransport(http.DefaultTransport),它自动 injecttraceparent到 header - gRPC client 侧:必须注册
grpc.WithUnaryInterceptor(otelgrpc.UnaryClientInterceptor()),不能只传ctx - gRPC server 侧:拦截器中拿到
ctx后,立刻构造带 trace 的 logger:logger := traceLogger.With(ctx),后续所有日志都基于它 - 关键点:
otel.GetTextMapPropagator().Extract()和.Inject()必须配对使用,且 propagator 类型一致(默认propagation.TraceContext{})
最容易被忽略的是:日志字段名必须和 tracing 系统约定一致。Jaeger UI 识别 trace_id,OTLP Collector 期望 trace_id,但有些老日志系统认 TraceID 或 x-trace-id——字段名错一个字母,全链路就无法关联。


















