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