storj.io/minio@v0.0.0-20230509071714-0cbc90f649b1/cmd/http-tracer.go (about) 1 /* 2 * MinIO Cloud Storage, (C) 2017 MinIO, Inc. 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17 package cmd 18 19 import ( 20 "bytes" 21 "io" 22 "io/ioutil" 23 "net" 24 "net/http" 25 "reflect" 26 "regexp" 27 "runtime" 28 "strconv" 29 "strings" 30 "time" 31 32 "github.com/gorilla/mux" 33 34 "storj.io/minio/cmd/logger" 35 "storj.io/minio/pkg/handlers" 36 jsonrpc "storj.io/minio/pkg/rpc" 37 trace "storj.io/minio/pkg/trace" 38 ) 39 40 // recordRequest - records the first recLen bytes 41 // of a given io.Reader 42 type recordRequest struct { 43 // Data source to record 44 io.Reader 45 // Response body should be logged 46 logBody bool 47 // Internal recording buffer 48 buf bytes.Buffer 49 // request headers 50 headers http.Header 51 // total bytes read including header size 52 bytesRead int 53 } 54 55 func (r *recordRequest) Read(p []byte) (n int, err error) { 56 n, err = r.Reader.Read(p) 57 r.bytesRead += n 58 59 if r.logBody { 60 r.buf.Write(p[:n]) 61 } 62 if err != nil { 63 return n, err 64 } 65 return n, err 66 } 67 func (r *recordRequest) Size() int { 68 sz := r.bytesRead 69 for k, v := range r.headers { 70 sz += len(k) + len(v) 71 } 72 return sz 73 } 74 75 // Return the bytes that were recorded. 76 func (r *recordRequest) Data() []byte { 77 // If body logging is enabled then we return the actual body 78 if r.logBody { 79 return r.buf.Bytes() 80 } 81 // ... otherwise we return <BODY> placeholder 82 return logger.BodyPlaceHolder 83 } 84 85 var ldapPwdRegex = regexp.MustCompile("(^.*?)LDAPPassword=([^&]*?)(&(.*?))?$") 86 87 // redact LDAP password if part of string 88 func redactLDAPPwd(s string) string { 89 parts := ldapPwdRegex.FindStringSubmatch(s) 90 if len(parts) > 0 { 91 return parts[1] + "LDAPPassword=*REDACTED*" + parts[3] 92 } 93 return s 94 } 95 96 // getOpName sanitizes the operation name for mc 97 func getOpName(name string) (op string) { 98 op = strings.TrimPrefix(name, "github.com/minio/minio/cmd.") 99 op = strings.TrimSuffix(op, "Handler-fm") 100 op = strings.Replace(op, "ObjectAPIHandlers", "s3", 1) 101 op = strings.Replace(op, "adminAPIHandlers", "admin", 1) 102 op = strings.Replace(op, "(*webAPIHandlers)", "web", 1) 103 op = strings.Replace(op, "(*storageRESTServer)", "internal", 1) 104 op = strings.Replace(op, "(*peerRESTServer)", "internal", 1) 105 op = strings.Replace(op, "(*lockRESTServer)", "internal", 1) 106 op = strings.Replace(op, "(*stsAPIHandlers)", "sts", 1) 107 op = strings.Replace(op, "LivenessCheckHandler", "healthcheck", 1) 108 op = strings.Replace(op, "ReadinessCheckHandler", "healthcheck", 1) 109 op = strings.Replace(op, "-fm", "", 1) 110 return op 111 } 112 113 // WebTrace gets trace of web request 114 func WebTrace(ri *jsonrpc.RequestInfo) trace.Info { 115 r := ri.Request 116 w := ri.ResponseWriter 117 118 name := ri.Method 119 // Setup a http request body recorder 120 reqHeaders := r.Header.Clone() 121 reqHeaders.Set("Host", r.Host) 122 if len(r.TransferEncoding) == 0 { 123 reqHeaders.Set("Content-Length", strconv.Itoa(int(r.ContentLength))) 124 } else { 125 reqHeaders.Set("Transfer-Encoding", strings.Join(r.TransferEncoding, ",")) 126 } 127 128 now := time.Now().UTC() 129 t := trace.Info{TraceType: trace.HTTP, FuncName: name, Time: now} 130 t.NodeName = r.Host 131 if globalIsDistErasure { 132 t.NodeName = globalLocalNodeName 133 } 134 if t.NodeName == "" { 135 t.NodeName = globalLocalNodeName 136 } 137 138 // strip only standard port from the host address 139 if host, port, err := net.SplitHostPort(t.NodeName); err == nil { 140 if port == "443" || port == "80" { 141 t.NodeName = host 142 } 143 } 144 145 vars := mux.Vars(r) 146 rq := trace.RequestInfo{ 147 Time: now, 148 Proto: r.Proto, 149 Method: r.Method, 150 Path: SlashSeparator + pathJoin(vars["bucket"], vars["object"]), 151 RawQuery: redactLDAPPwd(r.URL.RawQuery), 152 Client: handlers.GetSourceIP(r), 153 Headers: reqHeaders, 154 } 155 156 rw, ok := w.(*logger.ResponseWriter) 157 if ok { 158 rs := trace.ResponseInfo{ 159 Time: time.Now().UTC(), 160 Headers: rw.Header().Clone(), 161 StatusCode: rw.StatusCode, 162 Body: logger.BodyPlaceHolder, 163 } 164 165 if rs.StatusCode == 0 { 166 rs.StatusCode = http.StatusOK 167 } 168 169 t.RespInfo = rs 170 t.CallStats = trace.CallStats{ 171 Latency: rs.Time.Sub(rw.StartTime), 172 InputBytes: int(r.ContentLength), 173 OutputBytes: rw.Size(), 174 TimeToFirstByte: rw.TimeToFirstByte, 175 } 176 } 177 178 t.ReqInfo = rq 179 return t 180 } 181 182 // Trace gets trace of http request 183 func Trace(f http.HandlerFunc, logBody bool, w http.ResponseWriter, r *http.Request) trace.Info { 184 name := getOpName(runtime.FuncForPC(reflect.ValueOf(f).Pointer()).Name()) 185 186 // Setup a http request body recorder 187 reqHeaders := r.Header.Clone() 188 reqHeaders.Set("Host", r.Host) 189 if len(r.TransferEncoding) == 0 { 190 reqHeaders.Set("Content-Length", strconv.Itoa(int(r.ContentLength))) 191 } else { 192 reqHeaders.Set("Transfer-Encoding", strings.Join(r.TransferEncoding, ",")) 193 } 194 195 reqBodyRecorder := &recordRequest{Reader: r.Body, logBody: logBody, headers: reqHeaders} 196 r.Body = ioutil.NopCloser(reqBodyRecorder) 197 198 now := time.Now().UTC() 199 t := trace.Info{TraceType: trace.HTTP, FuncName: name, Time: now} 200 201 t.NodeName = r.Host 202 if globalIsDistErasure { 203 t.NodeName = globalLocalNodeName 204 } 205 206 if t.NodeName == "" { 207 t.NodeName = globalLocalNodeName 208 } 209 210 // strip only standard port from the host address 211 if host, port, err := net.SplitHostPort(t.NodeName); err == nil { 212 if port == "443" || port == "80" { 213 t.NodeName = host 214 } 215 } 216 217 rq := trace.RequestInfo{ 218 Time: now, 219 Proto: r.Proto, 220 Method: r.Method, 221 Path: r.URL.Path, 222 RawQuery: redactLDAPPwd(r.URL.RawQuery), 223 Client: handlers.GetSourceIP(r), 224 Headers: reqHeaders, 225 } 226 227 rw := logger.NewResponseWriter(w) 228 rw.LogErrBody = true 229 rw.LogAllBody = logBody 230 231 // Execute call. 232 f(rw, r) 233 234 rs := trace.ResponseInfo{ 235 Time: time.Now().UTC(), 236 Headers: rw.Header().Clone(), 237 StatusCode: rw.StatusCode, 238 Body: rw.Body(), 239 } 240 241 // Transfer request body 242 rq.Body = reqBodyRecorder.Data() 243 244 if rs.StatusCode == 0 { 245 rs.StatusCode = http.StatusOK 246 } 247 248 t.ReqInfo = rq 249 t.RespInfo = rs 250 251 t.CallStats = trace.CallStats{ 252 Latency: rs.Time.Sub(rw.StartTime), 253 InputBytes: reqBodyRecorder.Size(), 254 OutputBytes: rw.Size(), 255 TimeToFirstByte: rw.TimeToFirstByte, 256 } 257 return t 258 }