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
defer()?)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.
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.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With