github.com/minio/console@v1.4.1/api/admin_trace.go (about) 1 // This file is part of MinIO Console Server 2 // Copyright (c) 2021 MinIO, Inc. 3 // 4 // This program is free software: you can redistribute it and/or modify 5 // it under the terms of the GNU Affero General Public License as published by 6 // the Free Software Foundation, either version 3 of the License, or 7 // (at your option) any later version. 8 // 9 // This program is distributed in the hope that it will be useful, 10 // but WITHOUT ANY WARRANTY; without even the implied warranty of 11 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 // GNU Affero General Public License for more details. 13 // 14 // You should have received a copy of the GNU Affero General Public License 15 // along with this program. If not, see <http://www.gnu.org/licenses/>. 16 17 package api 18 19 import ( 20 "context" 21 "encoding/json" 22 "net/http" 23 "strings" 24 "time" 25 26 "github.com/minio/madmin-go/v3" 27 "github.com/minio/websocket" 28 ) 29 30 // shortTraceMsg Short trace record 31 type shortTraceMsg struct { 32 Host string `json:"host"` 33 Time string `json:"time"` 34 Client string `json:"client"` 35 CallStats callStats `json:"callStats"` 36 FuncName string `json:"api"` 37 Path string `json:"path"` 38 Query string `json:"query"` 39 StatusCode int `json:"statusCode"` 40 StatusMsg string `json:"statusMsg"` 41 } 42 43 type callStats struct { 44 Rx int `json:"rx"` 45 Tx int `json:"tx"` 46 Duration string `json:"duration"` 47 Ttfb string `json:"timeToFirstByte"` 48 } 49 50 // trace filters 51 func matchTrace(opts TraceRequest, traceInfo madmin.ServiceTraceInfo) bool { 52 statusCode := int(opts.statusCode) 53 method := opts.method 54 funcName := opts.funcName 55 apiPath := opts.path 56 57 if statusCode == 0 && method == "" && funcName == "" && apiPath == "" { 58 // no specific filtering found trace all the requests 59 return true 60 } 61 62 // Filter request path if passed by the user 63 if apiPath != "" { 64 pathToLookup := strings.ToLower(apiPath) 65 pathFromTrace := strings.ToLower(traceInfo.Trace.Path) 66 67 return strings.Contains(pathFromTrace, pathToLookup) 68 } 69 70 // Filter response status codes if passed by the user 71 if statusCode > 0 && traceInfo.Trace.HTTP != nil { 72 statusCodeFromTrace := traceInfo.Trace.HTTP.RespInfo.StatusCode 73 74 return statusCodeFromTrace == statusCode 75 } 76 77 // Filter request method if passed by the user 78 if method != "" && traceInfo.Trace.HTTP != nil { 79 methodFromTrace := traceInfo.Trace.HTTP.ReqInfo.Method 80 81 return methodFromTrace == method 82 } 83 84 if funcName != "" { 85 funcToLookup := strings.ToLower(funcName) 86 funcFromTrace := strings.ToLower(traceInfo.Trace.FuncName) 87 88 return strings.Contains(funcFromTrace, funcToLookup) 89 } 90 91 return true 92 } 93 94 // startTraceInfo starts trace of the servers 95 func startTraceInfo(ctx context.Context, conn WSConn, client MinioAdmin, opts TraceRequest) error { 96 // Start listening on all trace activity. 97 traceCh := client.serviceTrace(ctx, opts.threshold, opts.s3, opts.internal, opts.storage, opts.os, opts.onlyErrors) 98 for { 99 select { 100 case <-ctx.Done(): 101 return nil 102 case traceInfo, ok := <-traceCh: 103 // zero value returned because the channel is closed and empty 104 if !ok { 105 return nil 106 } 107 if traceInfo.Err != nil { 108 LogError("error on serviceTrace: %v", traceInfo.Err) 109 return traceInfo.Err 110 } 111 if matchTrace(opts, traceInfo) { 112 // Serialize message to be sent 113 traceInfoBytes, err := json.Marshal(shortTrace(&traceInfo)) 114 if err != nil { 115 LogError("error on json.Marshal: %v", err) 116 return err 117 } 118 // Send Message through websocket connection 119 err = conn.writeMessage(websocket.TextMessage, traceInfoBytes) 120 if err != nil { 121 LogError("error writeMessage: %v", err) 122 return err 123 } 124 } 125 } 126 } 127 } 128 129 // shortTrace creates a shorter Trace Info message. 130 // Same implementation as github/minio/mc/cmd/admin-trace.go 131 func shortTrace(info *madmin.ServiceTraceInfo) shortTraceMsg { 132 t := info.Trace 133 s := shortTraceMsg{} 134 135 s.Time = t.Time.Format(time.RFC3339) 136 s.Path = t.Path 137 s.FuncName = t.FuncName 138 s.CallStats.Duration = t.Duration.String() 139 if info.Trace.HTTP != nil { 140 s.Query = t.HTTP.ReqInfo.RawQuery 141 s.StatusCode = t.HTTP.RespInfo.StatusCode 142 s.StatusMsg = http.StatusText(t.HTTP.RespInfo.StatusCode) 143 s.CallStats.Rx = t.HTTP.CallStats.InputBytes 144 s.CallStats.Tx = t.HTTP.CallStats.OutputBytes 145 s.CallStats.Ttfb = t.HTTP.CallStats.TimeToFirstByte.String() 146 if host, ok := t.HTTP.ReqInfo.Headers["Host"]; ok { 147 s.Host = strings.Join(host, "") 148 } 149 cSlice := strings.Split(t.HTTP.ReqInfo.Client, ":") 150 s.Client = cSlice[0] 151 } 152 153 return s 154 }