github.com/m3db/m3@v1.5.0/src/query/api/v1/middleware/logging.go (about) 1 // Copyright (c) 2021 Uber Technologies, Inc. 2 // 3 // Permission is hereby granted, free of charge, to any person obtaining a copy 4 // of this software and associated documentation files (the "Software"), to deal 5 // in the Software without restriction, including without limitation the rights 6 // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 // copies of the Software, and to permit persons to whom the Software is 8 // furnished to do so, subject to the following conditions: 9 // 10 // The above copyright notice and this permission notice shall be included in 11 // all copies or substantial portions of the Software. 12 // 13 // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 19 // THE SOFTWARE. 20 21 package middleware 22 23 import ( 24 "net/http" 25 "strings" 26 "time" 27 28 "github.com/gorilla/mux" 29 "github.com/opentracing/opentracing-go" 30 "go.uber.org/zap" 31 32 "github.com/m3db/m3/src/cmd/services/m3query/config" 33 "github.com/m3db/m3/src/query/util/logging" 34 "github.com/m3db/m3/src/x/headers" 35 xhttp "github.com/m3db/m3/src/x/http" 36 "github.com/m3db/m3/src/x/instrument" 37 ) 38 39 // RequestID populates the request scoped logger with a unique request ID. 40 func RequestID(iOpts instrument.Options) mux.MiddlewareFunc { 41 return func(base http.Handler) http.Handler { 42 return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { 43 rqCtx := logging.NewContextWithGeneratedID(r.Context(), iOpts) 44 45 sp := opentracing.SpanFromContext(rqCtx) 46 if sp != nil { 47 rqID := logging.ReadContextID(rqCtx) 48 sp.SetTag("rqID", rqID) 49 } 50 51 // Propagate the context with the reqId 52 base.ServeHTTP(w, r.WithContext(rqCtx)) 53 }) 54 } 55 } 56 57 // LoggingOptions are the options for the logging middleware. 58 type LoggingOptions struct { 59 Threshold time.Duration 60 Disabled bool 61 Fields Fields 62 } 63 64 // NewLoggingOptions returns new options from the config. 65 func NewLoggingOptions(c config.LoggingMiddlewareConfiguration) LoggingOptions { 66 if c.Threshold == 0 { 67 c.Threshold = time.Second 68 } 69 return LoggingOptions{ 70 Threshold: c.Threshold, 71 Disabled: c.Disabled, 72 } 73 } 74 75 // Append the provided Fields to these Fields. 76 func (f Fields) Append(other Fields) Fields { 77 return func(r *http.Request, start time.Time) []zap.Field { 78 var fields []zap.Field 79 if f != nil { 80 fields = f(r, start) 81 } 82 if other != nil { 83 fields = append(fields, other(r, start)...) 84 } 85 return fields 86 } 87 } 88 89 // WithNoResponseLogging disables response logging for a route. 90 var WithNoResponseLogging = func(opts Options) Options { 91 opts.Logging.Disabled = true 92 return opts 93 } 94 95 // Fields returns additional logging fields to add to the response log message. 96 type Fields func(r *http.Request, start time.Time) []zap.Field 97 98 // ResponseLogging logs the response time if the request took longer than the configured threshold. 99 func ResponseLogging(opts Options) mux.MiddlewareFunc { 100 return func(base http.Handler) http.Handler { 101 return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { 102 start := opts.Clock.Now() 103 statusCodeTracking := &xhttp.StatusCodeTracker{ResponseWriter: w, TrackError: true} 104 w = statusCodeTracking.WrappedResponseWriter() 105 base.ServeHTTP(w, r) 106 d := opts.Clock.Now().Sub(start) 107 if !opts.Logging.Disabled && d >= opts.Logging.Threshold { 108 logger := logging.WithContext(r.Context(), opts.InstrumentOpts) 109 fields := []zap.Field{ 110 zap.Duration("duration", d), 111 zap.String("url", r.URL.RequestURI()), 112 zap.Int("status", statusCodeTracking.Status), 113 } 114 if statusCodeTracking.ErrMsg != "" { 115 fields = append(fields, zap.String("error", statusCodeTracking.ErrMsg)) 116 } 117 fields = appendM3Headers(r.Header, fields) 118 fields = appendM3Headers(w.Header(), fields) 119 if opts.Logging.Fields != nil { 120 fields = append(fields, opts.Logging.Fields(r, start)...) 121 } 122 logger.Info("finished handling request", fields...) 123 } 124 }) 125 } 126 } 127 128 func appendM3Headers(h http.Header, fields []zap.Field) []zap.Field { 129 for k, v := range h { 130 if strings.HasPrefix(k, headers.M3HeaderPrefix) { 131 if len(v) == 1 { 132 fields = append(fields, zap.String(k, v[0])) 133 } else { 134 fields = append(fields, zap.Strings(k, v)) 135 } 136 } 137 } 138 return fields 139 }