vitess.io/vitess@v0.16.2/go/vt/vttablet/tabletserver/tabletenv/logstats.go (about) 1 /* 2 Copyright 2019 The Vitess Authors. 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 tabletenv 18 19 import ( 20 "fmt" 21 "html/template" 22 "io" 23 "net/url" 24 "strings" 25 "time" 26 27 "context" 28 29 "vitess.io/vitess/go/sqltypes" 30 "vitess.io/vitess/go/streamlog" 31 "vitess.io/vitess/go/vt/callerid" 32 "vitess.io/vitess/go/vt/callinfo" 33 34 querypb "vitess.io/vitess/go/vt/proto/query" 35 ) 36 37 const ( 38 // QuerySourceConsolidator means query result is found in consolidator. 39 QuerySourceConsolidator = 1 << iota 40 // QuerySourceMySQL means query result is returned from MySQL. 41 QuerySourceMySQL 42 ) 43 44 // LogStats records the stats for a single query 45 type LogStats struct { 46 Ctx context.Context 47 Method string 48 Target *querypb.Target 49 PlanType string 50 OriginalSQL string 51 BindVariables map[string]*querypb.BindVariable 52 rewrittenSqls []string 53 RowsAffected int 54 NumberOfQueries int 55 StartTime time.Time 56 EndTime time.Time 57 MysqlResponseTime time.Duration 58 WaitingForConnection time.Duration 59 QuerySources byte 60 Rows [][]sqltypes.Value 61 TransactionID int64 62 ReservedID int64 63 Error error 64 CachedPlan bool 65 } 66 67 // NewLogStats constructs a new LogStats with supplied Method and ctx 68 // field values, and the StartTime field set to the present time. 69 func NewLogStats(ctx context.Context, methodName string) *LogStats { 70 return &LogStats{ 71 Ctx: ctx, 72 Method: methodName, 73 StartTime: time.Now(), 74 } 75 } 76 77 // Send finalizes a record and sends it 78 func (stats *LogStats) Send() { 79 stats.EndTime = time.Now() 80 StatsLogger.Send(stats) 81 } 82 83 // ImmediateCaller returns the immediate caller stored in LogStats.Ctx 84 func (stats *LogStats) ImmediateCaller() string { 85 return callerid.GetUsername(callerid.ImmediateCallerIDFromContext(stats.Ctx)) 86 } 87 88 // EffectiveCaller returns the effective caller stored in LogStats.Ctx 89 func (stats *LogStats) EffectiveCaller() string { 90 return callerid.GetPrincipal(callerid.EffectiveCallerIDFromContext(stats.Ctx)) 91 } 92 93 // EventTime returns the time the event was created. 94 func (stats *LogStats) EventTime() time.Time { 95 return stats.EndTime 96 } 97 98 // AddRewrittenSQL adds a single sql statement to the rewritten list 99 func (stats *LogStats) AddRewrittenSQL(sql string, start time.Time) { 100 stats.QuerySources |= QuerySourceMySQL 101 stats.NumberOfQueries++ 102 stats.rewrittenSqls = append(stats.rewrittenSqls, sql) 103 stats.MysqlResponseTime += time.Since(start) 104 } 105 106 // TotalTime returns how long this query has been running 107 func (stats *LogStats) TotalTime() time.Duration { 108 return stats.EndTime.Sub(stats.StartTime) 109 } 110 111 // RewrittenSQL returns a semicolon separated list of SQL statements 112 // that were executed. 113 func (stats *LogStats) RewrittenSQL() string { 114 return strings.Join(stats.rewrittenSqls, "; ") 115 } 116 117 // SizeOfResponse returns the approximate size of the response in 118 // bytes (this does not take in account protocol encoding). It will return 119 // 0 for streaming requests. 120 func (stats *LogStats) SizeOfResponse() int { 121 if stats.Rows == nil { 122 return 0 123 } 124 size := 0 125 for _, row := range stats.Rows { 126 for _, field := range row { 127 size += field.Len() 128 } 129 } 130 return size 131 } 132 133 // FmtQuerySources returns a comma separated list of query 134 // sources. If there were no query sources, it returns the string 135 // "none". 136 func (stats *LogStats) FmtQuerySources() string { 137 if stats.QuerySources == 0 { 138 return "none" 139 } 140 sources := make([]string, 2) 141 n := 0 142 if stats.QuerySources&QuerySourceMySQL != 0 { 143 sources[n] = "mysql" 144 n++ 145 } 146 if stats.QuerySources&QuerySourceConsolidator != 0 { 147 sources[n] = "consolidator" 148 n++ 149 } 150 return strings.Join(sources[:n], ",") 151 } 152 153 // ContextHTML returns the HTML version of the context that was used, or "". 154 // This is a method on LogStats instead of a field so that it doesn't need 155 // to be passed by value everywhere. 156 func (stats *LogStats) ContextHTML() template.HTML { 157 return callinfo.HTMLFromContext(stats.Ctx) 158 } 159 160 // ErrorStr returns the error string or "" 161 func (stats *LogStats) ErrorStr() string { 162 if stats.Error != nil { 163 return stats.Error.Error() 164 } 165 return "" 166 } 167 168 // CallInfo returns some parts of CallInfo if set 169 func (stats *LogStats) CallInfo() (string, string) { 170 ci, ok := callinfo.FromContext(stats.Ctx) 171 if !ok { 172 return "", "" 173 } 174 return ci.Text(), ci.Username() 175 } 176 177 // Logf formats the log record to the given writer, either as 178 // tab-separated list of logged fields or as JSON. 179 func (stats *LogStats) Logf(w io.Writer, params url.Values) error { 180 if !streamlog.ShouldEmitLog(stats.OriginalSQL, uint64(stats.RowsAffected), uint64(len(stats.Rows))) { 181 return nil 182 } 183 184 rewrittenSQL := "[REDACTED]" 185 formattedBindVars := "\"[REDACTED]\"" 186 187 if !streamlog.GetRedactDebugUIQueries() { 188 rewrittenSQL = stats.RewrittenSQL() 189 190 _, fullBindParams := params["full"] 191 formattedBindVars = sqltypes.FormatBindVariables( 192 stats.BindVariables, 193 fullBindParams, 194 streamlog.GetQueryLogFormat() == streamlog.QueryLogFormatJSON, 195 ) 196 } 197 198 // TODO: remove username here we fully enforce immediate caller id 199 callInfo, username := stats.CallInfo() 200 201 // Valid options for the QueryLogFormat are text or json 202 var fmtString string 203 switch streamlog.GetQueryLogFormat() { 204 case streamlog.QueryLogFormatText: 205 fmtString = "%v\t%v\t%v\t'%v'\t'%v'\t%v\t%v\t%.6f\t%v\t%q\t%v\t%v\t%q\t%v\t%.6f\t%.6f\t%v\t%v\t%v\t%q\t\n" 206 case streamlog.QueryLogFormatJSON: 207 fmtString = "{\"Method\": %q, \"CallInfo\": %q, \"Username\": %q, \"ImmediateCaller\": %q, \"Effective Caller\": %q, \"Start\": \"%v\", \"End\": \"%v\", \"TotalTime\": %.6f, \"PlanType\": %q, \"OriginalSQL\": %q, \"BindVars\": %v, \"Queries\": %v, \"RewrittenSQL\": %q, \"QuerySources\": %q, \"MysqlTime\": %.6f, \"ConnWaitTime\": %.6f, \"RowsAffected\": %v,\"TransactionID\": %v,\"ResponseSize\": %v, \"Error\": %q}\n" 208 } 209 210 _, err := fmt.Fprintf( 211 w, 212 fmtString, 213 stats.Method, 214 callInfo, 215 username, 216 stats.ImmediateCaller(), 217 stats.EffectiveCaller(), 218 stats.StartTime.Format("2006-01-02 15:04:05.000000"), 219 stats.EndTime.Format("2006-01-02 15:04:05.000000"), 220 stats.TotalTime().Seconds(), 221 stats.PlanType, 222 stats.OriginalSQL, 223 formattedBindVars, 224 stats.NumberOfQueries, 225 rewrittenSQL, 226 stats.FmtQuerySources(), 227 stats.MysqlResponseTime.Seconds(), 228 stats.WaitingForConnection.Seconds(), 229 stats.RowsAffected, 230 stats.TransactionID, 231 stats.SizeOfResponse(), 232 stats.ErrorStr(), 233 ) 234 return err 235 }