链路追踪与日志关联

微服务排查慢请求时,最缺的就是“这次请求到底经过了哪些服务、每步花了多久”。GoFrame v2 的链路追踪基于 OpenTelemetry 标准实现,官方文档“服务链路跟踪”系列章节给出了完整实践:服务间由拦截器自动创建 span,进程内方法间由开发者手动埋点。

基本概念

  • Span:一次调用的一段记录(名称、起止时间、属性);首个 Span 为 Root Span。
  • 分布式请求场景下,客户端/服务端拦截器会自动创建 Root Span 并沿网络传递,开发者无需手工创建。
  • 官方提供社区模块封装了上报器初始化:contrib/trace/otlphttpcontrib/trace/otlpgrpc(Jaeger 封装官方提示即将于 v2.6 移除,建议改用 OTLP 上报),以及框架内置的 gtrace 工具。
  • HTTP 服务开启追踪后,框架会自动为每个请求生成 span(路由表里可见 internalMiddlewareServerTracing 这类内置中间件)。

初始化并启动上报

package main

import (
    "github.com/gogf/gf/v2/frame/g"
    "github.com/gogf/gf/v2/os/gctx"
    "github.com/gogf/gf/contrib/trace/otlphttp/v2" // 官方 OTLP HTTP 封装
)

func main() {
    var (
        ctx = gctx.New()
        serviceName = "user-service"
        endpoint    = "http://127.0.0.1:4318" // OTLP HTTP 采集端
    )
    shutdown, err := otlphttp.Init(serviceName, endpoint, "")
    if err != nil {
        g.Log().Fatal(ctx, err)
    }
    defer shutdown() // 进程退出前刷新并关闭上报器

    s := g.Server()
    s.Group("/api", func(group *ghttp.RouterGroup) {
        group.Bind(new(v1.Controller))
    })
    s.Run()
}

官方对具体初始化函数签名(otlphttp.Init(serviceName, endpoint, path))与不同版本可能略有出入,接入时以对应版本官方文档为准。

方法间手动埋点

进程内方法调用不会自动产生 span,官方示范的固定写法是用 gtrace.NewSpan 创建并 defer span.End()

import "github.com/gogf/gf/v2/net/gtrace"

// 方法入口:创建子 Span,名称为方法名,便于在链路中识别
func GetUser(ctx context.Context, id int) (m g.Map, err error) {
    ctx, span := gtrace.NewSpan(ctx, "GetUser")
    defer span.End()

    info, err := GetInfo(ctx, id) // ctx 携带 span 一路传递
    if err != nil {
        return nil, err
    }
    return info, nil
}

关键点只有一个:ctx 作为第一个参数层层传递,span 的父子关系与上下文传播就自然成立。ORM/Redis/日志等组件在拿到带 span 的 ctx 后会自动把自己的耗时与信息挂到当前链路。

日志关联

GoFrame 的日志组件会自动从 ctx 中识别链路信息:带上 trace ctx 打印日志时,输出会附带链路标识(日志中形如 {189cwi9ngk...} 的 TraceId 片段),这样在日志系统里按 TraceId 搜索,就能捞出同一请求的全部日志:

g.Log().Infof(ctx, "get user: %d", id) // ctx 来自请求或上层方法

排查时先到追踪系统(如 Jaeger/云厂商 APM)按 TraceId 看调用链耗时,再回日志系统用同一个 TraceId 捞上下文,效率远高于大海捞针。

注意点

  • ctx 必须贯穿始终:中间件、service、dao 一路传 ctx,链路才不断。
  • 上报器是全局资源,defer shutdown() 保证退出时数据落盘,配合进程优雅退出使用。
  • 采样策略(如只采 10%)在采集端或初始化参数中配置,避免全量采样成本过高(具体能力以官方文档为准)。
  • 本地没有采集后端时链路功能不报错,只是没有数据可看;可用官方文档推荐的 Jaeger all-in-one 容器快速起一个本地看板。

小结

链路追踪的接入成本其实很低:启动时初始化 OTLP 上报器,代码里保证 ctx 贯穿 + 关键方法用 gtrace.NewSpan 埋点,服务间的传播由框架拦截器完成。配上 TraceId 日志,分布式排障从“猜”变成“看”。组件支持范围以官方文档 goframe.org 为准。

笔记加载中…