vitess.io/vitess@v0.16.2/go/vt/vttablet/tabletserver/querylogz.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 tabletserver 18 19 import ( 20 "fmt" 21 "io" 22 "net/http" 23 "strconv" 24 "strings" 25 "text/template" 26 "time" 27 28 "vitess.io/vitess/go/acl" 29 "vitess.io/vitess/go/vt/log" 30 "vitess.io/vitess/go/vt/logz" 31 "vitess.io/vitess/go/vt/sqlparser" 32 "vitess.io/vitess/go/vt/vttablet/tabletserver/tabletenv" 33 ) 34 35 var ( 36 querylogzHeader = []byte(` 37 <thead> 38 <tr> 39 <th>Method</th> 40 <th>Context</th> 41 <th>Effective Caller</th> 42 <th>Immediate Caller</th> 43 <th>Start</th> 44 <th>End</th> 45 <th>Duration</th> 46 <th>MySQL time</th> 47 <th>Conn wait</th> 48 <th>Plan</th> 49 <th>SQL</th> 50 <th>Queries</th> 51 <th>Sources</th> 52 <th>RowsAffected</th> 53 <th>Response Size</th> 54 <th>Transaction ID</th> 55 <th>Reserved ID</th> 56 <th>Error</th> 57 </tr> 58 </thead> 59 `) 60 querylogzFuncMap = template.FuncMap{ 61 "stampMicro": func(t time.Time) string { return t.Format(time.StampMicro) }, 62 "cssWrappable": logz.Wrappable, 63 "truncateQuery": sqlparser.TruncateForUI, 64 "unquote": func(s string) string { return strings.Trim(s, "\"") }, 65 } 66 querylogzTmpl = template.Must(template.New("example").Funcs(querylogzFuncMap).Parse(` 67 <tr class="{{.ColorLevel}}"> 68 <td>{{.Method}}</td> 69 <td>{{.ContextHTML}}</td> 70 <td>{{.EffectiveCaller}}</td> 71 <td>{{.ImmediateCaller}}</td> 72 <td>{{.StartTime | stampMicro}}</td> 73 <td>{{.EndTime | stampMicro}}</td> 74 <td>{{.TotalTime.Seconds}}</td> 75 <td>{{.MysqlResponseTime.Seconds}}</td> 76 <td>{{.WaitingForConnection.Seconds}}</td> 77 <td>{{.PlanType}}</td> 78 <td>{{.OriginalSQL | truncateQuery | unquote | cssWrappable}}</td> 79 <td>{{.NumberOfQueries}}</td> 80 <td>{{.FmtQuerySources}}</td> 81 <td>{{.RowsAffected}}</td> 82 <td>{{.SizeOfResponse}}</td> 83 <td>{{.TransactionID}}</td> 84 <td>{{.ReservedID}}</td> 85 <td>{{.ErrorStr}}</td> 86 </tr> 87 `)) 88 ) 89 90 func init() { 91 http.HandleFunc("/querylogz", func(w http.ResponseWriter, r *http.Request) { 92 ch := tabletenv.StatsLogger.Subscribe("querylogz") 93 defer tabletenv.StatsLogger.Unsubscribe(ch) 94 querylogzHandler(ch, w, r) 95 }) 96 } 97 98 // querylogzHandler serves a human readable snapshot of the 99 // current query log. 100 func querylogzHandler(ch chan any, w http.ResponseWriter, r *http.Request) { 101 if err := acl.CheckAccessHTTP(r, acl.DEBUGGING); err != nil { 102 acl.SendError(w, err) 103 return 104 } 105 timeout, limit := parseTimeoutLimitParams(r) 106 logz.StartHTMLTable(w) 107 defer logz.EndHTMLTable(w) 108 w.Write(querylogzHeader) 109 110 tmr := time.NewTimer(timeout) 111 defer tmr.Stop() 112 for i := 0; i < limit; i++ { 113 select { 114 case out := <-ch: 115 select { 116 case <-tmr.C: 117 return 118 default: 119 } 120 stats, ok := out.(*tabletenv.LogStats) 121 if !ok { 122 err := fmt.Errorf("unexpected value in %s: %#v (expecting value of type %T)", tabletenv.TxLogger.Name(), out, &tabletenv.LogStats{}) 123 io.WriteString(w, `<tr class="error">`) 124 io.WriteString(w, err.Error()) 125 io.WriteString(w, "</tr>") 126 log.Error(err) 127 continue 128 } 129 var level string 130 if stats.TotalTime().Seconds() < 0.01 { 131 level = "low" 132 } else if stats.TotalTime().Seconds() < 0.1 { 133 level = "medium" 134 } else { 135 level = "high" 136 } 137 tmplData := struct { 138 *tabletenv.LogStats 139 ColorLevel string 140 }{stats, level} 141 if err := querylogzTmpl.Execute(w, tmplData); err != nil { 142 log.Errorf("querylogz: couldn't execute template: %v", err) 143 } 144 case <-tmr.C: 145 return 146 } 147 } 148 } 149 150 func parseTimeoutLimitParams(req *http.Request) (time.Duration, int) { 151 timeout := 10 152 limit := 300 153 if ts, ok := req.URL.Query()["timeout"]; ok { 154 if t, err := strconv.Atoi(ts[0]); err == nil { 155 timeout = adjustValue(t, 0, 60) 156 } 157 } 158 if l, ok := req.URL.Query()["limit"]; ok { 159 if lim, err := strconv.Atoi(l[0]); err == nil { 160 limit = adjustValue(lim, 1, 200000) 161 } 162 } 163 return time.Duration(timeout) * time.Second, limit 164 } 165 166 func adjustValue(val int, lower int, upper int) int { 167 if val < lower { 168 return lower 169 } else if val > upper { 170 return upper 171 } 172 return val 173 }