runtime/trace 不是链路追踪工具,仅记录单进程内调度、gc等运行时事件,不支持分布式trace、trace-id/span-id、w3c标准及跨服务上下文传递,线上链路追踪必须使用opentelemetry。

runtime/trace 不是链路追踪工具,它不能启动分布式 trace,也不能串联 HTTP → DB → RPC 请求路径。你调 trace.Start() 只会记录单进程内 goroutine 调度、GC、系统调用等运行时事件,和 trace-id、span-id、W3C traceparent 完全无关。
真要做线上链路追踪,必须用 go.opentelemetry.io/otel,不是 runtime/trace。
trace.Start() 为什么不能用于线上链路追踪
-
trace.Start()生成的trace.out文件只含灰色 Proc 条、Goroutine 状态切换、STW 时间点,没有服务名、HTTP 路径、DB 查询语句、trace-id 字段 - 它不解析
traceparentheader,也不往 context 里塞 span,下游服务根本拿不到父 span 上下文 - 你把它丢进 Jaeger 或 Zipkin UI,会报
unknown magic number—— 格式根本不兼容 - 在
init()函数里调trace.Start()会静默失败,因为 runtime 还没初始化完
otel.Tracer().Start() 才是线上链路追踪的起点
真正启动可串联的 trace,得靠 OpenTelemetry 的 span 生命周期绑定 context.Context:
- 必须在 HTTP handler 入口就从
req.Context()提取父上下文,再调tracer.Start(ctx, "http.handler") - 返回的新
ctx和span必须一起传给下游:DB 查询、gRPC 调用、goroutine 启动前都要显式传入 -
span不是全局变量,也不是靠context.WithValue()随便塞进去就能自动读取的 —— 下游必须调trace.SpanFromContext(ctx)主动提取
常见错误:
- 用
db.QueryContext(context.Background(), ...),导致 DB 这一跳变成 root span -
go func() { ... }()没传ctx,新 goroutine 的 span 和上游完全断开 - 日志里想打
trace_id却直接log.Printf("req"),忘了从trace.SpanFromContext(ctx).SpanContext().TraceID().String()取
otelhttp.NewHandler() 是 HTTP 自动透传的关键
手动解析 traceparent 极易出错:漏 tracestate、大小写不一致(标准要求小写)、采样标志(traceflags)没透传。
otelhttp.NewHandler() 封装了全部逻辑:
- 入向自动调
propagation.TraceContext{}.Extract(),支持 W3C 标准 - 出向自动
Inject(),确保下游能继续链路 - 无需自己
req.Header.Set("traceparent", ...),也别用opentracing.HTTPHeadersCarrier(那是旧方案)
但注意:
- 它必须包裹最终业务 handler,不能套在其他中间件之后(比如 Gin 的
c.Next()之前就结束了) - 若下游是 Java Spring Cloud,默认用 B3 格式,得换
propagation.B3{}并配X-B3-TraceIdheader
otelsql.RegisterDriver() 是 DB 链路不断的核心
只用 otelsql.Open() 包一层 sql.DB 不够:
- 它覆盖不了
sql.Tx内部的Stmt.Exec、预编译语句、Rows.Scan等底层调用 - 结果就是 span duration 不准、DB 查询消失在链路图里
正确做法是注册带 tracing 的 driver:
- PostgreSQL 推荐
github.com/jackc/pgx/v5/pg+otelsql.RegisterDriver("pgx", pgxDriver) - MySQL 用
github.com/go-sql-driver/mysql+otelsql.RegisterDriver("mysql", mysqlDriver)
别给 span 加高基数字段(如用户 email、原始 request body),会导致 trace 后端 OOM;动态值改用 span.AddEvent()。
最易忽略的是:所有异步操作都必须从上游 ctx 派生,而不是在 goroutine 里 new 一个 context.Background()。父子关系一丢,整条链就散了。
golang免费学习笔记(深入):立即使用
在学习笔记中,你将探索golang的核心概念和高级技巧!











