Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How can I pass a context.Context via Gin's 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.

like image 203
Andrew DiNunzio Avatar asked Jan 19 '26 07:01

Andrew DiNunzio


1 Answers

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.

like image 135
Woody1193 Avatar answered Jan 20 '26 21:01

Woody1193