-
Notifications
You must be signed in to change notification settings - Fork 2.1k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
querylog
: json format version 2
#15271
Changes from all commits
2fb06a7
fe608ba
8f63ad5
6d86698
150727c
4246773
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change | ||||
---|---|---|---|---|---|---|
@@ -0,0 +1,140 @@ | ||||||
/* | ||||||
Copyright 2024 The Vitess Authors. | ||||||
|
||||||
Licensed under the Apache License, Version 2.0 (the "License"); | ||||||
you may not use this file except in compliance with the License. | ||||||
You may obtain a copy of the License at | ||||||
|
||||||
http://www.apache.org/licenses/LICENSE-2.0 | ||||||
|
||||||
Unless required by applicable law or agreed to in writing, software | ||||||
distributed under the License is distributed on an "AS IS" BASIS, | ||||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||||||
See the License for the specific language governing permissions and | ||||||
limitations under the License. | ||||||
*/ | ||||||
|
||||||
package streamlog | ||||||
|
||||||
import ( | ||||||
"encoding/json" | ||||||
"errors" | ||||||
|
||||||
querypb "vitess.io/vitess/go/vt/proto/query" | ||||||
) | ||||||
|
||||||
var ErrUnrecognizedBindVarType = errors.New("unrecognized bind variable type") | ||||||
|
||||||
// BindVariableValue is used to store querypb.BindVariable values. | ||||||
type BindVariableValue struct { | ||||||
Type string | ||||||
Value []byte | ||||||
} | ||||||
|
||||||
// MarshalJSON renders the BindVariableValue as json and optionally redacts the value. | ||||||
func (bv BindVariableValue) MarshalJSON() ([]byte, error) { | ||||||
out := map[string]interface{}{ | ||||||
"Type": bv.Type, | ||||||
"Value": bv.Value, | ||||||
} | ||||||
if GetRedactDebugUIQueries() { | ||||||
out["Value"] = nil | ||||||
} | ||||||
return json.Marshal(out) | ||||||
} | ||||||
|
||||||
// BindVariable is a wrapper for marshal/unmarshaling querypb.BindVariable as json. | ||||||
// It ensures that the "Type" field is a string representation of the variable | ||||||
// type instead of an integer-based code that is less portable and human-readable. | ||||||
// | ||||||
// This allows a *querypb.BindVariable that would have marshaled | ||||||
// to this: | ||||||
// | ||||||
// {"Type":10262,"Value":"FmtAtEq6S9Y="} | ||||||
// | ||||||
// to marshal to this: | ||||||
// | ||||||
// {"Type":"VARBINARY","Value":"FmtAtEq6S9Y="} | ||||||
// | ||||||
// or if query redaction is enabled, like this: | ||||||
// | ||||||
// {"Type":"VARBINARY","Value":null} | ||||||
type BindVariable struct { | ||||||
Type string | ||||||
Value []byte | ||||||
Values []*BindVariableValue | ||||||
} | ||||||
|
||||||
// NewBindVariable returns a wrapped *querypb.BindVariable object. | ||||||
func NewBindVariable(bv *querypb.BindVariable) BindVariable { | ||||||
newBv := BindVariable{ | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. We know the capacity of |
||||||
Type: bv.Type.String(), | ||||||
Value: bv.Value, | ||||||
} | ||||||
for _, val := range bv.Values { | ||||||
newBv.Values = append(newBv.Values, &BindVariableValue{ | ||||||
Type: val.Type.String(), | ||||||
Value: val.Value, | ||||||
}) | ||||||
} | ||||||
return newBv | ||||||
} | ||||||
|
||||||
// NewBindVariables returns a string-map of wrapped *querypb.BindVariable objects. | ||||||
func NewBindVariables(bvs map[string]*querypb.BindVariable) map[string]BindVariable { | ||||||
out := make(map[string]BindVariable, len(bvs)) | ||||||
for key, bindVar := range bvs { | ||||||
out[key] = NewBindVariable(bindVar) | ||||||
} | ||||||
return out | ||||||
} | ||||||
|
||||||
// MarshalJSON renders the BindVariable as json and optionally redacts the value. | ||||||
func (bv BindVariable) MarshalJSON() ([]byte, error) { | ||||||
out := map[string]interface{}{ | ||||||
"Type": bv.Type, | ||||||
"Value": bv.Value, | ||||||
} | ||||||
if GetRedactDebugUIQueries() { | ||||||
out["Value"] = nil | ||||||
} | ||||||
return json.Marshal(out) | ||||||
} | ||||||
|
||||||
// bindVariablesValuesToProto converts a slice of *BindVariableValue to *querypb.Value. | ||||||
func bindVariablesValuesToProto(vals []*BindVariableValue) ([]*querypb.Value, error) { | ||||||
values := make([]*querypb.Value, len(vals)) | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
This needs to be allocated with length 0 or else you'll end up appending to a slice populated with zero values up front. |
||||||
for _, val := range vals { | ||||||
varType, found := querypb.Type_value[val.Type] | ||||||
if !found { | ||||||
return nil, ErrUnrecognizedBindVarType | ||||||
} | ||||||
values = append(values, &querypb.Value{ | ||||||
Type: querypb.Type(varType), | ||||||
Value: val.Value, | ||||||
}) | ||||||
} | ||||||
return values, nil | ||||||
} | ||||||
|
||||||
// BindVariablesToProto converts a string-map of BindVariable to a string-map of *querypb.BindVariable. | ||||||
func BindVariablesToProto(bvs map[string]BindVariable) (map[string]*querypb.BindVariable, error) { | ||||||
out := make(map[string]*querypb.BindVariable, len(bvs)) | ||||||
for key, bindVar := range bvs { | ||||||
// convert type string to querypb.Type. | ||||||
varType, found := querypb.Type_value[bindVar.Type] | ||||||
if !found { | ||||||
return nil, ErrUnrecognizedBindVarType | ||||||
} | ||||||
values, err := bindVariablesValuesToProto(bindVar.Values) | ||||||
if err != nil { | ||||||
return nil, err | ||||||
} | ||||||
out[key] = &querypb.BindVariable{ | ||||||
Type: querypb.Type(varType), | ||||||
Value: bindVar.Value, | ||||||
Values: values, | ||||||
} | ||||||
} | ||||||
return out, nil | ||||||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -51,6 +51,7 @@ | |
queryLogFilterTag string | ||
queryLogRowThreshold uint64 | ||
queryLogFormat = "text" | ||
queryLogJSONV2 bool | ||
) | ||
|
||
func GetRedactDebugUIQueries() bool { | ||
|
@@ -77,6 +78,14 @@ | |
queryLogFormat = newQueryLogFormat | ||
} | ||
|
||
func UseQueryLogJSONV2() bool { | ||
return queryLogJSONV2 | ||
} | ||
|
||
func SetQueryLogJSONV2(enabled bool) { | ||
queryLogJSONV2 = enabled | ||
} | ||
|
||
func init() { | ||
servenv.OnParseFor("vtcombo", registerStreamLogFlags) | ||
servenv.OnParseFor("vttablet", registerStreamLogFlags) | ||
|
@@ -90,6 +99,9 @@ | |
// QueryLogFormat controls the format of the query log (either text or json) | ||
fs.StringVar(&queryLogFormat, "querylog-format", queryLogFormat, "format for query logs (\"text\" or \"json\")") | ||
|
||
// QueryLogJSONFormat controls whether the new querylog json format is used | ||
fs.BoolVar(&queryLogJSONV2, "querylog-json-v2", false, "use v2 format for querylog-format=json") | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. If we already have |
||
|
||
// QueryLogFilterTag contains an optional string that must be present in the query for it to be logged | ||
fs.StringVar(&queryLogFilterTag, "querylog-filter-tag", queryLogFilterTag, "string that must be present in the query for it to be logged; if using a value as the tag, you need to disable query normalization") | ||
|
||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -36,14 +36,13 @@ | |
querypb "vitess.io/vitess/go/vt/proto/query" | ||
) | ||
|
||
// LogStats records the stats for a single vtgate query | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Keep this. |
||
type LogStats struct { | ||
Ctx context.Context | ||
Ctx context.Context `json:"-"` | ||
Method string | ||
TabletType string | ||
StmtType string | ||
SQL string | ||
BindVariables map[string]*querypb.BindVariable | ||
BindVariables map[string]streamlog.BindVariable `json:",omitempty"` | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. See below. |
||
StartTime time.Time | ||
EndTime time.Time | ||
ShardQueries uint64 | ||
|
@@ -52,13 +51,21 @@ | |
PlanTime time.Duration | ||
ExecuteTime time.Duration | ||
CommitTime time.Duration | ||
Error error | ||
Error error `json:",omitempty"` | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This seems incorrect. Did you intend for |
||
TablesUsed []string | ||
SessionUUID string | ||
CachedPlan bool | ||
ActiveKeyspace string // ActiveKeyspace is the selected keyspace `use ks` | ||
} | ||
|
||
type LogStatsJSON struct { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Name with JSONv2 to separate from the base implementation? |
||
RemoteAddr string | ||
Username string | ||
ImmediateCaller string | ||
EffectiveCaller string | ||
LogStats | ||
} | ||
|
||
// NewLogStats constructs a new LogStats with supplied Method and ctx | ||
// field values, and the StartTime field set to the present time. | ||
func NewLogStats(ctx context.Context, methodName, sql, sessionUUID string, bindVars map[string]*querypb.BindVariable) *LogStats { | ||
|
@@ -67,7 +74,7 @@ | |
Method: methodName, | ||
SQL: sql, | ||
SessionUUID: sessionUUID, | ||
BindVariables: bindVars, | ||
BindVariables: streamlog.NewBindVariables(bindVars), | ||
StartTime: time.Now(), | ||
} | ||
} | ||
|
@@ -137,10 +144,14 @@ | |
}() | ||
|
||
formattedBindVars := "\"[REDACTED]\"" | ||
if !streamlog.GetRedactDebugUIQueries() { | ||
if !streamlog.GetRedactDebugUIQueries() && !streamlog.UseQueryLogJSONV2() { | ||
_, fullBindParams := params["full"] | ||
bindVarsProto, err := streamlog.BindVariablesToProto(stats.BindVariables) | ||
if err != nil { | ||
return err | ||
} | ||
formattedBindVars = sqltypes.FormatBindVariables( | ||
stats.BindVariables, | ||
bindVarsProto, | ||
fullBindParams, | ||
streamlog.GetQueryLogFormat() == streamlog.QueryLogFormatJSON, | ||
) | ||
|
@@ -154,6 +165,16 @@ | |
case streamlog.QueryLogFormatText: | ||
fmtString = "%v\t%v\t%v\t'%v'\t'%v'\t%v\t%v\t%.6f\t%.6f\t%.6f\t%.6f\t%v\t%q\t%v\t%v\t%v\t%q\t%q\t%q\t%v\t%v\t%q\n" | ||
case streamlog.QueryLogFormatJSON: | ||
if streamlog.UseQueryLogJSONV2() { | ||
// flag --querylog-json-v2 | ||
return json.NewEncoder(w).Encode(LogStatsJSON{ | ||
EffectiveCaller: stats.EffectiveCaller(), | ||
ImmediateCaller: stats.ImmediateCaller(), | ||
LogStats: *stats, | ||
RemoteAddr: remoteAddr, | ||
Username: username, | ||
}) | ||
} | ||
fmtString = "{\"Method\": %q, \"RemoteAddr\": %q, \"Username\": %q, \"ImmediateCaller\": %q, \"Effective Caller\": %q, \"Start\": \"%v\", \"End\": \"%v\", \"TotalTime\": %.6f, \"PlanTime\": %v, \"ExecuteTime\": %v, \"CommitTime\": %v, \"StmtType\": %q, \"SQL\": %q, \"BindVars\": %v, \"ShardQueries\": %v, \"RowsAffected\": %v, \"Error\": %q, \"TabletType\": %q, \"SessionUUID\": %q, \"Cached Plan\": %v, \"TablesUsed\": %v, \"ActiveKeyspace\": %q}\n" | ||
} | ||
|
||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
For new code IMO.