I'm trying to log some data on my web server, so I created a loggingMiddleware
that serves the next request and then logs the data, I thought this way I would have all the necessary data inside the r *http.Request
pointer
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
// call next handler
next.ServeHTTP(o, r)
// get requestID
reqID := middleware.GetReqID(r.Context())
log.WithFields(
log.Fields{
"request_id": reqID, // drops requestID
"method": r.Method, // http method
"remote_address": r.RemoteAddr, // remote address
"url": r.URL.String(), // url used by the client to access the service
"referer": r.Referer(), // Referer header if present
"user_agent": r.UserAgent(), // User-Agent header
"content_length": r.ContentLength, // Content-Length header if present"
},
).Info()
})
However for the RequestID this is true only if the RequestID
middleware is mounted before the loggingMiddleware
Non-Working
...
// get a new chi rotuter
router = chi.NewRouter()
// MIDDLEWARES
// log
// use logrus to log requests
router.Use(LoggingMiddleware)
// requestID
// Generate a unique id for every request
// ids are grouped based on client hostname
router.Use(middleware.RequestID)
...
Working
...
// get a new chi rotuter
router = chi.NewRouter()
// MIDDLEWARES
// requestID
// Generate a unique id for every request
// ids are grouped based on client hostname
router.Use(middleware.RequestID)
// log
// use logrus to log requests
router.Use(LoggingMiddleware)
...
Is this the expected behavior? Should the r *http.Request
pointer point to the "updated" version of the request? Is there a way to get around this?
Because if I want, for example, extract a username from a JWT token and put it in the r.Context()
so I can log it later, this would require a separate middleware to be mounted before the loggingMiddleware
.
Sorry for my english, please ask if there's something not clear.
Thanks
middleware.RequestID
adds the request ID to the request context by using http.Request.WithContext
:
ctx = context.WithValue(ctx, RequestIDKey, requestID)
next.ServeHTTP(w, r.WithContext(ctx))
Per the documentation:
WithContext returns a shallow copy of r with its context changed to ctx. The provided ctx must be non-nil.
Therefore, because it "returns a shallow copy of r", and it is (a pointer to) this shallow copy which it passes to next.ServeHTTP
, if middleware.RequestID
is mounted second, then the r
in LoggingMiddleware
is pointing to a different *http.Request
than the one which contains the modified context, and so its context will not contain the request ID. If, on the other hand, middleware.RequestID
is mounted first, then LoggingMiddleware
will receive a pointer to the shallow copy that r.WithContext
returned, and everything will work as expected.