1 // Copyright (C) The Arvados Authors. All rights reserved.
3 // SPDX-License-Identifier: AGPL-3.0
12 "git.curoverse.com/arvados.git/sdk/go/stats"
13 "github.com/sirupsen/logrus"
16 type contextKey struct {
21 requestTimeContextKey = contextKey{"requestTime"}
22 loggerContextKey = contextKey{"logger"}
25 // LogRequests wraps an http.Handler, logging each request and
26 // response via logger.
27 func LogRequests(logger logrus.FieldLogger, h http.Handler) http.Handler {
29 logger = logrus.StandardLogger()
31 return http.HandlerFunc(func(wrapped http.ResponseWriter, req *http.Request) {
32 w := &responseTimer{ResponseWriter: WrapResponseWriter(wrapped)}
33 lgr := logger.WithFields(logrus.Fields{
34 "RequestID": req.Header.Get("X-Request-Id"),
35 "remoteAddr": req.RemoteAddr,
36 "reqForwardedFor": req.Header.Get("X-Forwarded-For"),
37 "reqMethod": req.Method,
39 "reqPath": req.URL.Path[1:],
40 "reqQuery": req.URL.RawQuery,
41 "reqBytes": req.ContentLength,
44 ctx = context.WithValue(ctx, &requestTimeContextKey, time.Now())
45 ctx = context.WithValue(ctx, &loggerContextKey, lgr)
46 req = req.WithContext(ctx)
48 logRequest(w, req, lgr)
49 defer logResponse(w, req, lgr)
54 func Logger(req *http.Request) logrus.FieldLogger {
55 if lgr, ok := req.Context().Value(&loggerContextKey).(logrus.FieldLogger); ok {
58 return logrus.StandardLogger()
62 func logRequest(w *responseTimer, req *http.Request, lgr *logrus.Entry) {
66 func logResponse(w *responseTimer, req *http.Request, lgr *logrus.Entry) {
67 if tStart, ok := req.Context().Value(&requestTimeContextKey).(time.Time); ok {
69 lgr = lgr.WithFields(logrus.Fields{
70 "timeTotal": stats.Duration(tDone.Sub(tStart)),
71 "timeToStatus": stats.Duration(w.writeTime.Sub(tStart)),
72 "timeWriteBody": stats.Duration(tDone.Sub(w.writeTime)),
75 respCode := w.WroteStatus()
77 respCode = http.StatusOK
79 lgr.WithFields(logrus.Fields{
80 "respStatusCode": respCode,
81 "respStatus": http.StatusText(respCode),
82 "respBytes": w.WroteBodyBytes(),
86 type responseTimer struct {
92 func (rt *responseTimer) CloseNotify() <-chan bool {
93 if cn, ok := rt.ResponseWriter.(http.CloseNotifier); ok {
94 return cn.CloseNotify()
99 func (rt *responseTimer) WriteHeader(code int) {
102 rt.writeTime = time.Now()
104 rt.ResponseWriter.WriteHeader(code)
107 func (rt *responseTimer) Write(p []byte) (int, error) {
110 rt.writeTime = time.Now()
112 return rt.ResponseWriter.Write(p)