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  }