你可以通过Gin的上下文(context)传递一个context.Context。

huangapple go评论76阅读模式
英文:

How can I pass a context.Context via Gin's context?

问题

我正在尝试找出在使用Gin时,使用OpenTelemetry进行跟踪时传播context.Context的正确方法。

我目前有一个gin处理程序,它调用一个函数并传递一个*gin.Context,如下所示:

func (m Handler) doSomething(ginCtx *gin.Context, name string) {
  ctx, span := otel.Tracer("mytracer").Start(ginCtx.Request.Context(), "doSomething")
  defer span.End()
  // ...
}

实际上,这会导致不正确的跟踪范围,因为在调用doSomething()之前,我在调用函数中创建了一个新的context.Context,其中包含父跟踪范围的信息(类似于上面的代码片段)。

在我的大部分代码中,我都在传递context.Context,但我想我可以利用gin的Context。否则,我必须同时传递这两种类型的上下文:

func (m Handler) doSomething(ctx context.Context, ginCtx *gin.Context, name string) {
  ctx, span := otel.Tracer("mytracer").Start(ctx, "doSomething")
  defer span.End()
  // ...
}

这种做法感觉不对,因为存储在*gin.Context中的Request与我通过context.Context作为参数传递的内容不同步。然而,我不敢使用具有更新的Context的新Request设置*gin.Context上的Request,因为:

  1. 它只适用于此函数,我必须取消设置它(也许通过defer()实现?)
  2. 它似乎不是线程安全的(尽管我假设在这种情况下,我需要在gin上下文中进行Copy()

处理这个问题的正确方法是仅Copy *gin.Context并使用新的context.Context修改Request,然后传递*gin.Context而不是context.Context吗?

我不知道从文本中Copy一个gin.Context的影响是什么:
> Copy返回一个当前上下文的副本,可以在请求范围之外安全使用。当上下文必须传递给goroutine时,必须使用此方法。

我是否仍然可以通过复制的*gin.Context进行Abort()操作,复制后的旧上下文是否仍然可用?我只需要像传递context.Context一样,使其行为类似于堆栈,即从函数中简单返回即可将上下文弹出,然后我就可以使用旧的上下文。

英文:

I'm trying to figure out the proper way to propagate a context.Context for the purposes of tracing with OpenTelemetry when using Gin.

I currently have a gin handler that calls a function and passes a *gin.Context, like so:

func (m Handler) doSomething(ginCtx *gin.Context, name string) {
  ctx, span := otel.Tracer("mytracer").Start(ginCtx.Request.Context(), "doSomething")
  defer span.End()
  // ...
}

This actually results in incorrect spans, since before doSomething() is called, I create a new context.Context in the calling function with the parent span information (similar to the snippet above).

In most of my code, I'm passing around a context.Context, but figured I could make use of gin's Context. Otherwise I have to pass both types of contexts:

func (m Handler) doSomething(ctx context.Context, ginCtx *gin.Context, name string) {
  ctx, span := otel.Tracer("mytracer").Start(ctx, "doSomething")
  defer span.End()
  // ...
}

This feels wrong since the Request stored in the *gin.Context is out of sync with what I'm passing around as a parameter via context.Context. However, I'm afraid to set the Request on the *gin.Context with a new Request that has the updated Context because

  1. It's only for this function and I'd have to un-set it (maybe through a defer()?)
  2. It doesn't seem like it'd be thread-safe (though I assume I'd need to Copy() the gin Context in this scenario anyway)

Is the proper way to handle this just Copying the *gin.Context and modifying the Request with the new context.Context, then passing the *gin.Context around instead of context.Context?

I don't know what the implications are of Copying a gin.Context from the text:
> Copy returns a copy of the current context that can be safely used outside the request's scope. This has to be used when the context has to be passed to a goroutine.

Can I still Abort() through a copied *gin.Context, and is the old one still usable after copying? I just need something that behaves like a stack in the same way that passing context.Contexts around does, where simply returning from the function "pops" the context off and I'm left with the old one.

答案1

得分: 1

我对gin包不是特别熟悉,但是我有一些类似的要求需要在labstack/echo中解决,我通过以下方式将所需内容注入到函数中来解决:

type Router struct {
    Inner *gin.Gin
}

func (router *Router) Handle(ctx context.Context, method string, route string, handler func(context.Context, *gin.Context) gin.IRoutes) {
    router.Inner.Handle(method, route, func(gCtx *gin.Context) error {
        return handler(ctx, gCtx)
    })
}

这样可以在声明路由时将context.Context注入进去。所以,不再需要这样做:

g := gin.Default()
g.POST("/some/route", myHandler) // myHandler接受*gin.Context

而是可以这样做:

r := Router{Inner: gin.Default()}
r.Handle("POST", "/some/route", myHandlerV2) // myHandlerV2接受*gin.Context和context.Context

这种模式还可以允许你注入其他依赖,比如数据库连接、日志记录器等。

英文:

I'm not overly familiar with the gin package, but I had some similar requirements for labstack/echo and I solved it by injecting what I needed into the function this way:

type Router struct {
    Inner *gin.Gin
}

func (router *Router) Handle(ctx context.Context, method string, route string, handler(context.Context, *gin.Context) gin.IRoutes {
    return router.Inner.Handle(method, route, func(gCtx *gin.Context) error {
        return handler(ctx, gCtx)
    })
}

This works by allowing you to inject the context.Context in when you declare the route. So, instead of doing this:

g := gin.Default()
g.POST("/some/route", myHandler) // myHandler accepts *gin.Context

you can now do:

r := Router{Inner: gin.Default()}
r.Handle("POST", "/some/route", myHandlerV2) // myHandlerV2 accepts *gin.Context and context.Context

This pattern would also allow you to inject other dependencies such as database connections, loggers, etc.

huangapple
  • 本文由 发表于 2022年6月22日 06:28:00
  • 转载请务必保留本文链接:https://go.coder-hub.com/72707748.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定