context.Context 是链路追踪的起点,因 Go 无内置调用链 ID 机制,所有追踪依赖其手动传递;必须在 HTTP handler、DB 查询、HTTP client、goroutine 等各跳转点显式透传带 span 的 ctx,否则链路断裂。

为什么 context.Context 是链路追踪的起点
Go 没有内置的“调用链 ID”或“跨 goroutine 透传追踪上下文”的机制,所有链路追踪都必须基于 context.Context 手动注入和传递。不从 context.WithValue 或更规范的 context.WithSpan(配合 OpenTelemetry)开始,后续任何日志、HTTP header 注入、RPC 透传都会断掉。
常见错误是只在 HTTP handler 入口解析 trace-id,但忘记把带追踪信息的 ctx 传给下游函数——比如调用数据库或另一个 HTTP client 时仍用 context.Background(),导致子 span 完全脱离父链路。
- 必须在每个关键跳转点显式传递
ctx:DB 查询、HTTP client 调用、消息队列 publish、goroutine 启动前 - 避免在中间层重新生成
context.Background();若需 timeout,用context.WithTimeout(parentCtx, ...)而非丢弃 parent - OpenTelemetry Go SDK 中,
otel.Tracer("name").Start(ctx, "op")会自动从ctx提取父 span 并关联,前提是ctx确实携带了有效的span
HTTP 请求中如何透传 trace-id 和 parent-span-id
Go 的 http.RoundTripper 和 http.Handler 都不自动处理 W3C Trace Context(traceparent header),必须手动读写。依赖 otelhttp 中间件是最稳妥的方式,它会在 handler 入口解析 traceparent,并在 outbound request 中自动注入。
如果不用 otelhttp,自己实现时容易漏掉:tracestate 头(用于 vendor 扩展)、采样决策透传(traceflags 字段)、大小写敏感问题(标准要求小写 traceparent,但某些旧服务发大写)。
立即学习“go语言免费学习笔记(深入)”;
Go 配置库,使用 spf13/viper — 分层优先级(flag > env >file > KV > default),提供 BindPFlag/BindPFlags、SetEnvPrefix + SetEnvKeyReplace 等功能。
- 入向解析:用
otel.GetTextMapPropagator().Extract(r.Context(), propagation.HeaderCarrier(r.Header)) - 出向注入:用
otel.GetTextMapPropagator().Inject(req.Context(), propagation.HeaderCarrier(req.Header)) - 别直接操作
r.Header.Set("Traceparent", ...)—— propagator 会处理格式校验、tracestate 同步、采样标志保留
goroutine 启动时如何安全继承 span 上下文
Go 的 go fn() 不会自动继承 caller 的 context.Context,更不会继承当前 span。直接启动 goroutine 会导致新协程的 span 被当作 root,链路断裂。
正确做法是把带 span 的 ctx 显式传入 goroutine 函数参数,或用 trace.SpanFromContext(ctx) 在 goroutine 内部重新绑定。OpenTelemetry 提供 otel.WithSpan 作为 context key,但必须确保传入的是同一个 ctx 实例。
- 错误写法:
go doWork() // ctx 未传入,span 丢失 - 正确写法:
go doWork(ctx),且doWork内部用span := trace.SpanFromContext(ctx)获取 - 若使用
sync.WaitGroup,注意不要在 defer 中 finish span —— 可能因 panic 或提前 return 导致 span 未结束;建议用span.End()放在函数末尾明确位置
数据库查询如何挂载到当前 trace
标准 database/sql 不支持 context 透传 span,也不触发 OpenTelemetry 自动 instrumentation。除非使用 otelsql 这类封装驱动,否则所有 db.QueryContext(ctx, ...) 调用只是传递 cancel/timeout,对链路追踪无贡献。
otelsql 通过包装 sql.Driver,在 Conn.BeginTx、Stmt.QueryContext 等关键方法中自动创建 child span,并将 ctx 中的 span 作为 parent。但前提是:你用的是 otelsql.Open,且所有 db 实例都来自它。
- 别混用原生
sql.Open和 otelsql —— 即使传了带 span 的ctx,也不会产生 span - pgx/v5 用户需用
otelpgx,而不是简单 wrappgxpool.Pool - 注意 otelsql 默认开启慢查询检测(>100ms),可能影响性能观察;可通过
otelsql.WithSlowQueryThreshold(0)关闭
ctx 是否真实携带 span,以及目标库是否真正消费了它。最容易被忽略的是异步逻辑和第三方驱动兼容性。

















