能实现Go链路追踪,但需满足内核支持BTF、Go二进制带符号表(或用libbpf-go兼容剥离版)、采集器能解析Go运行时协议特征三个前提;否则仅获TCP连接与HTTP状态码,无法提取span核心字段如service、path、trace_id、span_id。

能,但必须满足三个硬性前提:内核支持 BTF、Go 二进制带符号表(或用 libbpf-go 兼容剥离版)、采集器能识别 Go 的运行时协议特征。 否则你看到的只是 TCP 连接和 HTTP 状态码,不是真正的 span。
为什么 sys_enter_connect 不等于链路追踪
eBPF 抓到 sys_enter_connect 只说明进程发起了一个 socket 连接,它不包含服务名、HTTP 路径、trace_id、span_id —— 这些才是链路追踪的核心字段。传统 eBPF tracepoint 只能拿到系统调用参数,而 Go 的 HTTP client 是在用户态封装的,connect() 调用前早已构造好 URL 和 headers,这些信息不会透出到内核。
- 直接监听
tcp:tcp_connect或syscalls:sys_enter_connect得到的是“连接事件”,不是“请求事件” - Go 的
net/http默认复用连接,一次connect()可能承载数十个 HTTP 请求,无法按 request 维度拆分 - 若服务用了 gRPC 或自定义协议,仅靠 TCP 层无法解析语义(比如无法区分是健康检查还是真实业务调用)
真正起作用的是 uprobe + Go 运行时函数拦截
链路追踪需要在 Go runtime 关键路径埋点:比如 net/http.(*Client).do、runtime.gopark、internal/reflectlite.Value.Call(对 MCP 工具调用有用)。这些函数调用发生在用户态,必须用 uprobe 拦截。
Go 配置库,使用 spf13/viper — 分层优先级(flag > env >file > KV > default),提供 BindPFlag/BindPFlags、SetEnvPrefix + SetEnvKeyReplace 等功能。
- 推荐 target 函数:
net/http.(*Transport).roundTrip(最稳定,所有 HTTP 请求必经) - Go 1.17+ ABI 改用寄存器传参,
uprobe触发时需读%ax获取第一个参数(*http.Request),但前提是没被编译器内联;加-gcflags="all=-l"编译可保函数可探 - 读取
Request.URL.Path和Header.Get("Traceparent")需用bpf_probe_read_user逐层解引用,不能直接bpf_probe_read_user_str读整个结构体 - libbpf-go 支持从 BTF 自动推导 Go struct 偏移,cilium/ebpf 在 5.15+ 内核上大概率读错字段导致 path 为空
采样和上下文传播必须依赖协议解析,不是靠 guess
eBPF 本身不生成 trace_id,它只负责提取已存在于网络流量或内存中的上下文。能否还原完整链路,取决于你的采集器是否能识别并解析 OpenTelemetry 标准头(traceparent)、gRPC 的 grpc-trace-bin、甚至 AI Agent 场景下的 x-llm-model 或 x-vector-db 等自定义 header。
立即学习“go语言免费学习笔记(深入)”;
- 纯 TCP 层 uprobe 无法获取 span.parent_id,必须配合 HTTP header 解析才能做 span linking
- OBI、DeepFlow、DataKit ELinker 等工具之所以能“无埋点”,是因为它们内置了针对常见协议(HTTP/1.1、HTTP/2、gRPC、Redis、PostgreSQL)的 parser,且 parser 运行在用户态 agent 中,eBPF 只负责把原始 buffer 送过去
- 如果你自己写采集器,要支持 Go 的 context.WithValue 透传,还得解析
runtime.casgstatus来关联 goroutine 生命周期,否则异步调用(如http.Post+go func())会断链
真正难的不是 attach uprobe,而是让采集器理解 Go 的内存布局、HTTP 流水线行为、以及 context 跨 goroutine 传递机制。很多团队卡在“能看到 connect,但看不到 /api/user”,问题往往出在 parser 没适配 Go 的 header 写入时机,或者忽略了 http.Transport.IdleConnTimeout 导致长连接复用干扰 span 边界判定。

















