github.com/minio/minio@v0.0.0-20240328213742-3f72439b8a27/cmd/http-tracer.go (about) 1 // Copyright (c) 2015-2021 MinIO, Inc. 2 // 3 // This file is part of MinIO Object Storage stack 4 // 5 // This program is free software: you can redistribute it and/or modify 6 // it under the terms of the GNU Affero General Public License as published by 7 // the Free Software Foundation, either version 3 of the License, or 8 // (at your option) any later version. 9 // 10 // This program is distributed in the hope that it will be useful 11 // but WITHOUT ANY WARRANTY; without even the implied warranty of 12 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 // GNU Affero General Public License for more details. 14 // 15 // You should have received a copy of the GNU Affero General Public License 16 // along with this program. If not, see <http://www.gnu.org/licenses/>. 17 18 package cmd 19 20 import ( 21 "context" 22 "net" 23 "net/http" 24 "reflect" 25 "regexp" 26 "runtime" 27 "strconv" 28 "strings" 29 "time" 30 31 "github.com/minio/madmin-go/v3" 32 "github.com/minio/minio/internal/handlers" 33 xhttp "github.com/minio/minio/internal/http" 34 "github.com/minio/minio/internal/mcontext" 35 ) 36 37 var ldapPwdRegex = regexp.MustCompile("(^.*?)LDAPPassword=([^&]*?)(&(.*?))?$") 38 39 // redact LDAP password if part of string 40 func redactLDAPPwd(s string) string { 41 parts := ldapPwdRegex.FindStringSubmatch(s) 42 if len(parts) > 3 { 43 return parts[1] + "LDAPPassword=*REDACTED*" + parts[3] 44 } 45 return s 46 } 47 48 // getOpName sanitizes the operation name for mc 49 func getOpName(name string) (op string) { 50 op = strings.TrimPrefix(name, "github.com/minio/minio/cmd.") 51 op = strings.TrimSuffix(op, "Handler-fm") 52 op = strings.Replace(op, "objectAPIHandlers", "s3", 1) 53 op = strings.Replace(op, "adminAPIHandlers", "admin", 1) 54 op = strings.Replace(op, "(*storageRESTServer)", "storageR", 1) 55 op = strings.Replace(op, "(*peerRESTServer)", "peer", 1) 56 op = strings.Replace(op, "(*lockRESTServer)", "lockR", 1) 57 op = strings.Replace(op, "(*stsAPIHandlers)", "sts", 1) 58 op = strings.Replace(op, "(*peerS3Server)", "s3", 1) 59 op = strings.Replace(op, "ClusterCheckHandler", "health.Cluster", 1) 60 op = strings.Replace(op, "ClusterReadCheckHandler", "health.ClusterRead", 1) 61 op = strings.Replace(op, "LivenessCheckHandler", "health.Liveness", 1) 62 op = strings.Replace(op, "ReadinessCheckHandler", "health.Readiness", 1) 63 op = strings.Replace(op, "-fm", "", 1) 64 return op 65 } 66 67 // If trace is enabled, execute the request if it is traced by other handlers 68 // otherwise, generate a trace event with request information but no response. 69 func httpTracerMiddleware(h http.Handler) http.Handler { 70 return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { 71 // Setup a http request response recorder - this is needed for 72 // http stats requests and audit if enabled. 73 respRecorder := xhttp.NewResponseRecorder(w) 74 75 // Setup a http request body recorder 76 reqRecorder := &xhttp.RequestRecorder{Reader: r.Body} 77 r.Body = reqRecorder 78 79 // Create tracing data structure and associate it to the request context 80 tc := mcontext.TraceCtxt{ 81 AmzReqID: w.Header().Get(xhttp.AmzRequestID), 82 RequestRecorder: reqRecorder, 83 ResponseRecorder: respRecorder, 84 } 85 86 r = r.WithContext(context.WithValue(r.Context(), mcontext.ContextTraceKey, &tc)) 87 88 reqStartTime := time.Now().UTC() 89 h.ServeHTTP(respRecorder, r) 90 reqEndTime := time.Now().UTC() 91 92 if globalTrace.NumSubscribers(madmin.TraceS3|madmin.TraceInternal) == 0 { 93 // no subscribers nothing to trace. 94 return 95 } 96 97 tt := madmin.TraceInternal 98 if strings.HasPrefix(tc.FuncName, "s3.") { 99 tt = madmin.TraceS3 100 } 101 102 // Calculate input body size with headers 103 reqHeaders := r.Header.Clone() 104 reqHeaders.Set("Host", r.Host) 105 if len(r.TransferEncoding) == 0 { 106 reqHeaders.Set("Content-Length", strconv.Itoa(int(r.ContentLength))) 107 } else { 108 reqHeaders.Set("Transfer-Encoding", strings.Join(r.TransferEncoding, ",")) 109 } 110 inputBytes := reqRecorder.Size() 111 for k, v := range reqHeaders { 112 inputBytes += len(k) + len(v) 113 } 114 115 // Calculate node name 116 nodeName := r.Host 117 if globalIsDistErasure { 118 nodeName = globalLocalNodeName 119 } 120 if host, port, err := net.SplitHostPort(nodeName); err == nil { 121 if port == "443" || port == "80" { 122 nodeName = host 123 } 124 } 125 126 // Calculate reqPath 127 reqPath := r.URL.RawPath 128 if reqPath == "" { 129 reqPath = r.URL.Path 130 } 131 132 // Calculate function name 133 funcName := tc.FuncName 134 if funcName == "" { 135 funcName = "<unknown>" 136 } 137 138 t := madmin.TraceInfo{ 139 TraceType: tt, 140 FuncName: funcName, 141 NodeName: nodeName, 142 Time: reqStartTime, 143 Duration: reqEndTime.Sub(respRecorder.StartTime), 144 Path: reqPath, 145 HTTP: &madmin.TraceHTTPStats{ 146 ReqInfo: madmin.TraceRequestInfo{ 147 Time: reqStartTime, 148 Proto: r.Proto, 149 Method: r.Method, 150 RawQuery: redactLDAPPwd(r.URL.RawQuery), 151 Client: handlers.GetSourceIP(r), 152 Headers: reqHeaders, 153 Path: reqPath, 154 Body: reqRecorder.Data(), 155 }, 156 RespInfo: madmin.TraceResponseInfo{ 157 Time: reqEndTime, 158 Headers: respRecorder.Header().Clone(), 159 StatusCode: respRecorder.StatusCode, 160 Body: respRecorder.Body(), 161 }, 162 CallStats: madmin.TraceCallStats{ 163 Latency: reqEndTime.Sub(respRecorder.StartTime), 164 InputBytes: inputBytes, 165 OutputBytes: respRecorder.Size(), 166 TimeToFirstByte: respRecorder.TimeToFirstByte, 167 }, 168 }, 169 } 170 171 globalTrace.Publish(t) 172 }) 173 } 174 175 func httpTrace(f http.HandlerFunc, logBody bool) http.HandlerFunc { 176 return func(w http.ResponseWriter, r *http.Request) { 177 tc, ok := r.Context().Value(mcontext.ContextTraceKey).(*mcontext.TraceCtxt) 178 if !ok { 179 // Tracing is not enabled for this request 180 f.ServeHTTP(w, r) 181 return 182 } 183 184 tc.FuncName = getOpName(runtime.FuncForPC(reflect.ValueOf(f).Pointer()).Name()) 185 tc.RequestRecorder.LogBody = logBody 186 tc.ResponseRecorder.LogAllBody = logBody 187 tc.ResponseRecorder.LogErrBody = true 188 189 f.ServeHTTP(w, r) 190 } 191 } 192 193 func httpTraceAll(f http.HandlerFunc) http.HandlerFunc { 194 return httpTrace(f, true) 195 } 196 197 func httpTraceHdrs(f http.HandlerFunc) http.HandlerFunc { 198 return httpTrace(f, false) 199 }