runtime/trace不能用于链路追踪,它仅记录单进程内调度、gc等运行时事件,不生成trace-id/span-id,也不透传context;opentelemetry才是分布式链路追踪的正确选择。

不能直接用 runtime/trace 写链路追踪数据到文件——它生成的 trace.out 是 Go 运行时调度/GC 事件,不是分布式链路追踪数据,和 OpenTelemetry 的 trace 完全不兼容。
runtime/trace 生成的文件不是链路追踪数据
很多人误以为 runtime/trace.Start() 输出的 trace.out 能当链路追踪日志用,结果发现:导入 Jaeger 报 unknown magic number、查不到 HTTP 请求路径、span 之间没父子关系。这是因为 runtime/trace 只记录单进程内 goroutine 状态切换、系统调用、GC STW 等底层事件,不带 trace-id、span-id、parent-id,也不透传 context,压根不具备分布式追踪语义。
你调 http.ServeMux 或 db.QueryContext(ctx, ...),只要没走 OpenTelemetry 的 instrumented 包,这些调用就完全不会出现在 trace.out 里。
OpenTelemetry 的 trace 数据怎么落盘
OpenTelemetry Go SDK 默认不写文件,它通过 Exporter 发送数据到后端(如 Jaeger、OTLP HTTP/gRPC)。要持久化到本地文件,必须自己实现一个 trace.SpanExporter,把 span 批量序列化后写入文件:
- 每次
ExportSpans()被调用时,拿到[]sdktrace.ReadOnlySpan切片 - 用
json.Encoder或gob.Encoder编码(推荐 JSON,便于调试和跨工具读取) - 写入前先打开文件,模式用
os.O_WRONLY | os.O_CREATE | os.O_APPEND,避免覆盖 - 每条 span 单独一行(JSON Lines 格式),方便后续用
jq或流式解析 - 务必在
file.Sync()后再返回,否则断电或 kill -9 会丢数据
示例关键逻辑:
func (e *fileExporter) ExportSpans(ctx context.Context, spans []sdktrace.ReadOnlySpan) error {
f, err := os.OpenFile("traces.jsonl", os.O_WRONLY|os.O_CREATE|os.O_APPEND, 0644)
if err != nil {
return err
}
defer f.Close()
enc := json.NewEncoder(f)
for _, s := range spans {
if err := enc.Encode(s); err != nil {
return err
}
}
return f.Sync() // ← 这行不能少
}
为什么不用 ioutil.WriteFile 或 os.WriteFile
因为链路追踪是高频小数据写入场景,ioutil.WriteFile(已弃用)和 os.WriteFile 每次都覆盖整个文件,会导致:
- 并发写入时数据被截断或错乱
- 无法追加,旧 trace 全丢
- 没
Sync(),数据卡在 page cache,断电即失
必须用 os.OpenFile 配合 O_APPEND,且每次写完调 file.Sync()。如果担心频繁 Sync() 影响性能,可改用缓冲写入(bufio.Writer),但缓冲区满或显式 Flush() 后仍需 Sync()。
真正难的不是写文件,而是读和解析
写进去容易,但后续想按 trace-id 查请求、统计 P99 延迟、画调用图,就得自己解析 JSON Lines 文件。没有索引、没有压缩、没有 TTL,几小时跑下来文件就几个 GB。生产环境建议只在调试阶段用文件导出,长期留存必须上专用后端(如 Jaeger + Cassandra、OTLP + Loki)。文件方式唯一适合的场景是:离线复现、CI 测试抓 trace、无网络环境临时诊断。
golang免费学习笔记(深入):立即使用
在学习笔记中,你将探索golang的核心概念和高级技巧!











