I read all the decisions about dropping the ThreadLocal and I can relate.
Seems like passing around the request.Context()
that is of type context.Context
gives the ability to manage scoped services.
I am using logrus
for logging and want to log a requestId
that is a generate udid from a midlleware in gin
I know i can do something like this
func Authentication(conf Configuration, logger ILogger, cache ICacheService) gin.HandlerFunc {
return func(c *gin.Context) {
logger := logrus.WithContext(c)
c.Set("traceId", uuid.New().String())
c.Next()
}
}
And the pass around the entry or even store the entry inside the context whatever works, but…
I also have a Redis
and MongoDb
clients that are singletons
and I am wrapping them with my own package to facilitate logging of any outgoing IO requests.
because they are singletones I cannot pas the context.Context
to the constructor but ill have to pass it to every method like GetKey
SetKet
etc..
any common patterns for using the context request id logging inside a Singleton service ?
2
Answers
The idiomatic way is to pass
context.Context
to every method. In fact, contexts are not supposed to be passed in constructors or stored as struct members. As mentioned in the documentation:Libraries with good API design provide customization of transport functionality or possibly provide the ability to add hooks for such purposes with closure context making you possible to decorate them with logging. Redis should support custom dialers and you can inject your logging activity directly with closure function that get access both to context and to your logger, so no need to make wrapping if it was the only reason.
A cleaner software design is to instantiate logger once and pass it down to your dependencies. Context.Values are request scoped-values, not scoped services. Using dynamic context api for such purposes make your software very hard to predict, it will be better to utilize conveniences of strongly-typed API with Dependency Injection instead.
Hope it helps!