问题点
直接使用 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就可以
原因
- 进入
trace.SpanFromContext这个方法,可以看到会通过一个特殊的别名获取Span,这个别名是一个int别名,这个很关键
2. 因为我们上面传递的是 gin
context,所以这里我们进入 gin context.Value()方法,研究它是如何获取值的。
可以看到这里首先会对 key 做一个判断,如果不是string类型,就会从Request.Context里获取,但是中间还有一步hasReuqestContext,所以导致链路失败肯定是这里返回了false
- 我们进入这个关键函数后,可以看到在正常情况下,唯一导致函数返回 false 的原因就是在于这个字段,而默认情况下,这个字段就是
false
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