Gin Context 链路追踪丢失问题

303 阅读1分钟

问题点

直接使用 Gin Context 传递给下游会导致 trace context 丢失的问题。

问题复现

package main

import (
    "fmt"
    "github.com/gin-gonic/gin"
    "go.opentelemetry.io/contrib/instrumentation/github.com/gin-gonic/gin/otelgin"
    "go.opentelemetry.io/otel"
    sdktrace "go.opentelemetry.io/otel/sdk/trace"
    "go.opentelemetry.io/otel/trace"
    "net/http"
)

func main() {
    // 初始化默认的 TracerProvider
    tp := sdktrace.NewTracerProvider()
    otel.SetTracerProvider(tp)

    engine := gin.Default()
    engine.Use(otelgin.Middleware("gin-server")) // 请求进来后自动开启一个trace

    engine.GET("/", func(c *gin.Context) {
       fmt.Println(trace.SpanFromContext(c).IsRecording()) // false
       fmt.Println(trace.SpanFromContext(c.Request.Context()).IsRecording()) // true
       c.String(http.StatusOK, "ok")
    })

    engine.Run("localhost:8088")
}

可以看到在使用 gin context的时候,是获取不到span的,传递Request.Context就可以

原因

  1. 进入trace.SpanFromContext这个方法,可以看到会通过一个特殊的别名获取Span,这个别名是一个int别名,这个很关键

image.png 2. 因为我们上面传递的是 gin context,所以这里我们进入 gin context.Value()方法,研究它是如何获取值的。

可以看到这里首先会对 key 做一个判断,如果不是string类型,就会从Request.Context里获取,但是中间还有一步hasReuqestContext,所以导致链路失败肯定是这里返回了false image.png

  1. 我们进入这个关键函数后,可以看到在正常情况下,唯一导致函数返回 false 的原因就是在于这个字段,而默认情况下,这个字段就是false

image.png 4. ContextWithFallback:从代码的注释中,可以看到这个字段就是控制是否允许查询 Request.Context

// ContextWithFallback enable fallback Context.Deadline(), Context.Done(), Context.Err() and Context.Value() when Context.Request.Context() is not nil.
   ContextWithFallback bool

解决办法

engine := gin.Default()
engine.ContextWithFallback = true