github.com/thanos-io/thanos@v0.32.5/pkg/logging/http.go (about) 1 // Copyright (c) The Thanos Authors. 2 // Licensed under the Apache License 2.0. 3 4 package logging 5 6 import ( 7 "fmt" 8 "net" 9 "sort" 10 "strings" 11 12 "net/http" 13 "time" 14 15 "github.com/go-kit/log" 16 "github.com/go-kit/log/level" 17 18 httputil "github.com/thanos-io/thanos/pkg/server/http" 19 ) 20 21 type HTTPServerMiddleware struct { 22 opts *options 23 logger log.Logger 24 } 25 26 func (m *HTTPServerMiddleware) preCall(name string, start time.Time, r *http.Request) { 27 logger := m.opts.filterLog(m.logger) 28 level.Debug(logger).Log("http.start_time", start.String(), "http.method", fmt.Sprintf("%s %s", r.Method, r.URL), "http.request_id", r.Header.Get("X-Request-ID"), "thanos.method_name", name, "msg", "started call") 29 } 30 31 func (m *HTTPServerMiddleware) postCall(name string, start time.Time, wrapped *httputil.ResponseWriterWithStatus, r *http.Request) { 32 status := wrapped.Status() 33 logger := log.With(m.logger, "http.method", fmt.Sprintf("%s %s", r.Method, r.URL), "http.request_id", r.Header.Get("X-Request-ID"), "http.status_code", fmt.Sprintf("%d", status), 34 "http.time_ms", fmt.Sprintf("%v", durationToMilliseconds(time.Since(start))), "http.remote_addr", r.RemoteAddr, "thanos.method_name", name) 35 36 logger = m.opts.filterLog(logger) 37 m.opts.levelFunc(logger, status).Log("msg", "finished call") 38 } 39 40 func (m *HTTPServerMiddleware) HTTPMiddleware(name string, next http.Handler) http.HandlerFunc { 41 return func(w http.ResponseWriter, r *http.Request) { 42 wrapped := httputil.WrapResponseWriterWithStatus(w) 43 start := time.Now() 44 hostPort := r.Host 45 if hostPort == "" { 46 hostPort = r.URL.Host 47 } 48 49 var port string 50 var err error 51 // Try to extract port if there is ':' as part of 'hostPort'. 52 if strings.Contains(hostPort, ":") { 53 _, port, err = net.SplitHostPort(hostPort) 54 if err != nil { 55 level.Error(m.logger).Log("msg", "failed to parse host port for http log decision", "err", err) 56 next.ServeHTTP(w, r) 57 return 58 } 59 } 60 61 deciderURL := r.URL.String() 62 if len(port) > 0 { 63 deciderURL = net.JoinHostPort(deciderURL, port) 64 } 65 decision := m.opts.shouldLog(deciderURL, nil) 66 67 switch decision { 68 case NoLogCall: 69 next.ServeHTTP(w, r) 70 71 case LogStartAndFinishCall: 72 m.preCall(name, start, r) 73 next.ServeHTTP(wrapped, r) 74 m.postCall(name, start, wrapped, r) 75 76 case LogFinishCall: 77 next.ServeHTTP(wrapped, r) 78 m.postCall(name, start, wrapped, r) 79 } 80 } 81 } 82 83 // NewHTTPServerMiddleware returns an http middleware. 84 func NewHTTPServerMiddleware(logger log.Logger, opts ...Option) *HTTPServerMiddleware { 85 o := evaluateOpt(opts) 86 return &HTTPServerMiddleware{ 87 logger: log.With(logger, "protocol", "http", "http.component", "server"), 88 opts: o, 89 } 90 } 91 92 // getHTTPLoggingOption returns the logging ENUM based on logStart and logEnd values. 93 func getHTTPLoggingOption(logStart, logEnd bool) (Decision, error) { 94 if !logStart && !logEnd { 95 return NoLogCall, nil 96 } 97 if !logStart && logEnd { 98 return LogFinishCall, nil 99 } 100 if logStart && logEnd { 101 return LogStartAndFinishCall, nil 102 } 103 return -1, fmt.Errorf("log start call is not supported") 104 } 105 106 // getLevel returns the level based logger. 107 func getLevel(lvl string) level.Option { 108 switch lvl { 109 case "INFO": 110 return level.AllowInfo() 111 case "DEBUG": 112 return level.AllowDebug() 113 case "WARN": 114 return level.AllowWarn() 115 case "ERROR": 116 return level.AllowError() 117 default: 118 return level.AllowAll() 119 } 120 } 121 122 // NewHTTPOption returns a http config option. 123 func NewHTTPOption(configYAML []byte) ([]Option, error) { 124 // Define a black config option. 125 logOpts := []Option{ 126 WithDecider(func(_ string, err error) Decision { 127 return NoLogCall 128 }), 129 } 130 131 // If req logging is disabled. 132 if len(configYAML) == 0 { 133 return logOpts, nil 134 } 135 136 reqLogConfig, err := NewRequestConfig(configYAML) 137 // If unmarshalling is an issue. 138 if err != nil { 139 return logOpts, err 140 } 141 142 globalLevel, globalStart, globalEnd, err := fillGlobalOptionConfig(reqLogConfig, false) 143 144 // If global options have invalid entries. 145 if err != nil { 146 return logOpts, err 147 } 148 // If the level entry does not matches our entries. 149 if err := validateLevel(globalLevel); err != nil { 150 // fmt.Printf("HTTP") 151 return logOpts, err 152 } 153 154 // If the combination is valid, use them, otherwise return error. 155 reqLogDecision, err := getHTTPLoggingOption(globalStart, globalEnd) 156 if err != nil { 157 return logOpts, err 158 } 159 160 logOpts = []Option{ 161 WithFilter(func(logger log.Logger) log.Logger { 162 return level.NewFilter(logger, getLevel(globalLevel)) 163 }), 164 WithLevels(DefaultCodeToLevel), 165 } 166 167 if len(reqLogConfig.HTTP.Config) == 0 { 168 logOpts = append(logOpts, []Option{WithDecider(func(_ string, err error) Decision { 169 return reqLogDecision 170 }), 171 }...) 172 return logOpts, nil 173 } 174 175 methodNameSlice := []string{} 176 177 for _, eachConfig := range reqLogConfig.HTTP.Config { 178 eachConfigName := fmt.Sprintf("%v:%v", eachConfig.Path, eachConfig.Port) 179 methodNameSlice = append(methodNameSlice, eachConfigName) 180 } 181 182 sort.Strings(methodNameSlice) 183 184 logOpts = append(logOpts, []Option{ 185 WithDecider(func(runtimeMethodName string, err error) Decision { 186 idx := sort.SearchStrings(methodNameSlice, runtimeMethodName) 187 if idx < len(methodNameSlice) && methodNameSlice[idx] == runtimeMethodName { 188 return reqLogDecision 189 } 190 return NoLogCall 191 }), 192 }...) 193 return logOpts, nil 194 195 }