github.com/minio/minio@v0.0.0-20240328213742-3f72439b8a27/internal/grid/trace.go (about) 1 // Copyright (c) 2015-2023 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 grid 19 20 import ( 21 "context" 22 "fmt" 23 "net/http" 24 "strings" 25 "time" 26 27 "github.com/minio/madmin-go/v3" 28 "github.com/minio/minio/internal/pubsub" 29 ) 30 31 // TraceParamsKey allows to pass trace parameters to the request via context. 32 // This is only needed when un-typed requests are used. 33 // MSS, map[string]string types are preferred, but any struct with exported fields will work. 34 type TraceParamsKey struct{} 35 36 // traceRequests adds request tracing to the connection. 37 func (c *Connection) traceRequests(p *pubsub.PubSub[madmin.TraceInfo, madmin.TraceType]) { 38 c.trace = &tracer{ 39 Publisher: p, 40 TraceType: madmin.TraceInternal, 41 Prefix: "grid", 42 Local: c.Local, 43 Remote: c.Remote, 44 Subroute: "", 45 } 46 } 47 48 // subroute adds a specific subroute to the request. 49 func (c *tracer) subroute(subroute string) *tracer { 50 if c == nil { 51 return nil 52 } 53 c2 := *c 54 c2.Subroute = subroute 55 return &c2 56 } 57 58 type tracer struct { 59 Publisher *pubsub.PubSub[madmin.TraceInfo, madmin.TraceType] 60 TraceType madmin.TraceType 61 Prefix string 62 Local string 63 Remote string 64 Subroute string 65 } 66 67 const ( 68 httpScheme = "http://" 69 httpsScheme = "https://" 70 ) 71 72 func (c *muxClient) traceRoundtrip(ctx context.Context, t *tracer, h HandlerID, req []byte) ([]byte, error) { 73 if t == nil || t.Publisher.NumSubscribers(t.TraceType) == 0 { 74 return c.roundtrip(h, req) 75 } 76 77 // Following trimming is needed for consistency between outputs with other internode traces. 78 local := strings.TrimPrefix(strings.TrimPrefix(t.Local, httpsScheme), httpScheme) 79 remote := strings.TrimPrefix(strings.TrimPrefix(t.Remote, httpsScheme), httpScheme) 80 81 start := time.Now() 82 body := bytesOrLength(req) 83 resp, err := c.roundtrip(h, req) 84 end := time.Now() 85 status := http.StatusOK 86 errString := "" 87 if err != nil { 88 errString = err.Error() 89 if IsRemoteErr(err) == nil { 90 status = http.StatusInternalServerError 91 } else { 92 status = http.StatusBadRequest 93 } 94 } 95 96 prefix := t.Prefix 97 if p := handlerPrefixes[h]; p != "" { 98 prefix = p 99 } 100 trace := madmin.TraceInfo{ 101 TraceType: t.TraceType, 102 FuncName: prefix + "." + h.String(), 103 NodeName: remote, 104 Time: start, 105 Duration: end.Sub(start), 106 Path: t.Subroute, 107 Error: errString, 108 HTTP: &madmin.TraceHTTPStats{ 109 ReqInfo: madmin.TraceRequestInfo{ 110 Time: start, 111 Proto: "grid", 112 Method: "REQ", 113 Client: local, 114 Headers: nil, 115 Path: t.Subroute, 116 Body: []byte(body), 117 }, 118 RespInfo: madmin.TraceResponseInfo{ 119 Time: end, 120 Headers: nil, 121 StatusCode: status, 122 Body: []byte(bytesOrLength(resp)), 123 }, 124 CallStats: madmin.TraceCallStats{ 125 InputBytes: len(req), 126 OutputBytes: len(resp), 127 TimeToFirstByte: end.Sub(start), 128 }, 129 }, 130 } 131 // If the context contains a TraceParamsKey, add it to the trace path. 132 v := ctx.Value(TraceParamsKey{}) 133 // Should match SingleHandler.Call checks. 134 switch typed := v.(type) { 135 case *MSS: 136 trace.Path += typed.ToQuery() 137 case map[string]string: 138 m := MSS(typed) 139 trace.Path += m.ToQuery() 140 case *URLValues: 141 trace.Path += typed.Values().Encode() 142 case *NoPayload, *Bytes: 143 trace.Path = fmt.Sprintf("%s?payload=%T", trace.Path, typed) 144 case string: 145 trace.Path = fmt.Sprintf("%s?%s", trace.Path, typed) 146 default: 147 } 148 trace.HTTP.ReqInfo.Path = trace.Path 149 150 t.Publisher.Publish(trace) 151 return resp, err 152 }