链路追踪:OpenTelemetry 集成与日志关联
面试问法:GoFrame 的链路追踪怎么做?用的什么标准?日志怎么和链路关联? 一句话结论:官方链路追踪基于 OpenTelemetry 标准:启动时用官方 contrib 初始化 OTLP 上报器(
contrib/trace/otlphttp、otlpgrpc),HTTP/gRPC 请求由框架自动生成并跨服务传播 span;进程内方法用gtrace.NewSpan手动埋点;日志组件在 ctx 带链路信息时自动输出 TraceId,可直接检索同一请求的全部日志。
核心概念
- Span:一次调用的一段记录;跨服务场景由框架拦截器自动创建 Root Span 并沿网络传递,开发者不必手工建。
- 进程内方法调用不会自动产生 span,需开发者用
gtrace.NewSpan(ctx, "方法名")创建并defer span.End()。 - 上报器:官方提供 OTLP HTTP/gRPC 封装;Jaeger 专用封装官方提示移除、建议改用 OTLP(以官方文档为准)。
- HTTP 服务开启后框架自动为请求生成 span(内部有内置追踪中间件),ORM/Redis/日志拿到带 span 的 ctx 后自动把信息挂到当前链路。
接入三步
// 1. main 初始化上报器(serviceName、采集端地址)
shutdown, err := otlphttp.Init("user-service", "http://127.0.0.1:4318", "")
if err != nil { g.Log().Fatal(ctx, err) }
defer shutdown() // 进程退出前刷新上报
// 2. 关键方法手动埋点
func GetUser(ctx context.Context, id int) (u *entity.User, err error) {
ctx, span := gtrace.NewSpan(ctx, "GetUser") // ctx 携带 span 继续传递
defer span.End()
return dao.User.Ctx(ctx).Where("id", id).One() // dao 自动把耗时挂到 span
}
// 3. 日志关联:ctx 带链路信息时自动输出 TraceId 片段
g.Log().Infof(ctx, "get user: %d", id)
排查流程:先在追踪系统按 TraceId 看调用链与各段耗时,再回日志系统用同一 TraceId 捞上下文。
注意点
- ctx 必须作为首参层层传递(中间件→service→dao),链路才不断。
- 采样策略(如只采 10%)在初始化或采集端配置,避免全量上报成本过高。
- 本地无采集后端时功能不报错,只是没有数据;可本地起一个 Jaeger/OTLP collector 看板验证。
- 上报器是全局资源,
defer shutdown()与优雅退出配合,保证退出时数据落盘。
常见追问
- 追问:TraceId 是怎么跨服务传递的?——基于 OpenTelemetry 传播协议(如 W3C traceparent)注入到出站请求头,服务端拦截器读出后接续父子 span。
- 追问:日志里为什么有时没 TraceId?——ctx 没带链路信息(比如用 context.Background() 起异步任务、或漏传 ctx),日志组件自然取不到。
记忆点
- 链路 = 启动初始化 OTLP + ctx 贯穿 + 关键方法 gtrace.NewSpan;服务间传播由框架完成。
- 日志自动带 TraceId,排障从“猜”变“看”;组件与版本细节以官方文档为准。