-
Notifications
You must be signed in to change notification settings - Fork 3.7k
/
exec_log.go
391 lines (348 loc) · 14 KB
/
exec_log.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
// Copyright 2018 The Cockroach Authors.
//
// Use of this software is governed by the Business Source License
// included in the file licenses/BSL.txt.
//
// As of the Change Date specified in that file, in accordance with
// the Business Source License, use of this software will be governed
// by the Apache License, Version 2.0, included in the file
// licenses/APL.txt.
package sql
import (
"bytes"
"context"
"fmt"
"time"
"github.com/cockroachdb/cockroach/pkg/settings"
"github.com/cockroachdb/cockroach/pkg/sql/catalog"
"github.com/cockroachdb/cockroach/pkg/sql/catalog/descpb"
"github.com/cockroachdb/cockroach/pkg/sql/pgwire/pgerror"
"github.com/cockroachdb/cockroach/pkg/sql/privilege"
"github.com/cockroachdb/cockroach/pkg/util/log"
"github.com/cockroachdb/cockroach/pkg/util/log/eventpb"
"github.com/cockroachdb/cockroach/pkg/util/timeutil"
)
// This file contains facilities to report SQL activities to separate
// log channels.
//
// See the detailed log sink and format documentation
// (e.g. auto-generated files in docs/generated) for details about the
// general format of log entries.
//
// By default, the facilities in this file produce query logs
// using structured events. The payload of structured events
// is also auto-documented; see the corresponding event definitions
// for details.
//
// When the cluster setting `sql.log.unstructured_entries.enabled` is set
// (pre-v21.1 compatibility format, obsolete), the event payloads include
// the following fields:
//
// - a label indicating where the data was generated - useful for troubleshooting.
// - distinguishes e.g. exec, prepare, internal-exec, etc.
// - the current value of `application_name`
// - required for auditing, also helps filter out messages from a specific app.
// - the logging trigger.
// - "{}" for execution logs: any activity is worth logging in the exec log
// - the full text of the query.
// - the placeholder values. Useful for queries using placeholders.
// - "{}" when there are no placeholders.
// - the query execution time in milliseconds. For troubleshooting.
// - the number of rows that were produced. For troubleshooting.
// - the status of the query (OK for success, ERROR or full error
// message upon error). Needed for auditing and troubleshooting.
// - the number of times the statement was retried automatically
// by the server so far.
//
// TODO(knz): Remove this documentation for the obsolete format when
// support for the format is removed, post-v21.1.
// logStatementsExecuteEnabled causes the Executor to log executed
// statements and, if any, resulting errors.
var logStatementsExecuteEnabled = settings.RegisterBoolSetting(
"sql.trace.log_statement_execute",
"set to true to enable logging of executed statements",
false,
).WithPublic()
var slowQueryLogThreshold = settings.RegisterPublicDurationSettingWithExplicitUnit(
"sql.log.slow_query.latency_threshold",
"when set to non-zero, log statements whose service latency exceeds "+
"the threshold to a secondary logger on each node",
0,
settings.NonNegativeDuration,
)
var slowInternalQueryLogEnabled = settings.RegisterBoolSetting(
"sql.log.slow_query.internal_queries.enabled",
"when set to true, internal queries which exceed the slow query log threshold "+
"are logged to a separate log. Must have the slow query log enabled for this "+
"setting to have any effect.",
false,
).WithPublic()
var slowQueryLogFullTableScans = settings.RegisterBoolSetting(
"sql.log.slow_query.experimental_full_table_scans.enabled",
"when set to true, statements that perform a full table/index scan will be logged to the "+
"slow query log even if they do not meet the latency threshold. Must have the slow query "+
"log enabled for this setting to have any effect.",
false,
).WithPublic()
var unstructuredQueryLog = settings.RegisterBoolSetting(
"sql.log.unstructured_entries.enabled",
"when set, SQL execution and audit logs use the pre-v21.1 unstrucured format",
false,
)
var adminAuditLogEnabled = settings.RegisterBoolSetting(
"sql.log.admin_audit.enabled",
"when set, log SQL queries that effectively exploit the user's admin privileges",
false,
)
type executorType int
const (
executorTypeExec executorType = iota
executorTypeInternal
)
// vLevel returns the vmodule log level at which logs from the given executor
// should be written to the logs.
func (s executorType) vLevel() log.Level { return log.Level(s) + 2 }
var logLabels = []string{"exec", "exec-internal"}
// logLabel returns the log label for the given executor type.
func (s executorType) logLabel() string { return logLabels[s] }
// maybeLogStatement conditionally records the current statement
// (p.curPlan) to the exec / audit logs.
func (p *planner) maybeLogStatement(
ctx context.Context,
execType executorType,
numRetries, rows int,
err error,
queryReceived time.Time,
) {
p.maybeLogStatementInternal(ctx, execType, numRetries, rows, err, queryReceived)
}
var sqlPerfLogger log.ChannelLogger = log.SqlPerf
var sqlPerfInternalLogger log.ChannelLogger = log.SqlInternalPerf
func (p *planner) maybeLogStatementInternal(
ctx context.Context, execType executorType, numRetries, rows int, err error, startTime time.Time,
) {
// Note: if you find the code below crashing because p.execCfg == nil,
// do not add a test "if p.execCfg == nil { do nothing }" !
// Instead, make the logger work. This is critical for auditing - we
// can't miss any statement.
logV := log.V(2)
logExecuteEnabled := logStatementsExecuteEnabled.Get(&p.execCfg.Settings.SV)
slowLogThreshold := slowQueryLogThreshold.Get(&p.execCfg.Settings.SV)
slowLogFullTableScans := slowQueryLogFullTableScans.Get(&p.execCfg.Settings.SV)
slowQueryLogEnabled := slowLogThreshold != 0
slowInternalQueryLogEnabled := slowInternalQueryLogEnabled.Get(&p.execCfg.Settings.SV)
auditEventsDetected := len(p.curPlan.auditEvents) != 0
hasAdminRole, hasAdminRoleErr := p.HasAdminRole(ctx)
log.Warningf(ctx, "checking hasAdminRole failed %v", hasAdminRoleErr)
// Only log to adminAuditLog if the statement is explicitly executed by
// a user and the user has admin privilege (is directly or indirectly a
// member of the admin role).
shouldLogToAdminAuditLog := adminAuditLogEnabled.Get(&p.execCfg.Settings.SV) &&
(execType == executorTypeExec) && hasAdminRole
if !logV && !logExecuteEnabled && !auditEventsDetected && !slowQueryLogEnabled && !shouldLogToAdminAuditLog {
// Shortcut: avoid the expense of computing anything log-related
// if logging is not enabled by configuration.
return
}
// Compute the pieces of data that are going to be included in logged events.
// The session's application_name.
appName := p.EvalContext().SessionData.ApplicationName
// The duration of the query so far. Age is the duration expressed in milliseconds.
queryDuration := timeutil.Now().Sub(startTime)
age := float32(queryDuration.Nanoseconds()) / 1e6
// The text of the error encountered, if the query did in fact end
// in error.
execErrStr := ""
if err != nil {
execErrStr = err.Error()
}
// The type of execution context (execute/prepare).
lbl := execType.logLabel()
if unstructuredQueryLog.Get(&p.execCfg.Settings.SV) {
// This entire branch exists for the sake of backward
// compatibility with log parsers for v20.2 and prior. This format
// is obsolete and so this branch can be removed in v21.2.
//
// Look at the code "below" this if case for the main (default)
// logging output.
// The statement being executed.
stmtStr := p.curPlan.stmt.AST.String()
plStr := p.extendedEvalCtx.Placeholders.Values.String()
if logV {
// Copy to the debug log.
log.VEventf(ctx, execType.vLevel(), "%s %q %q %s %.3f %d %q %d",
lbl, appName, stmtStr, plStr, age, rows, execErrStr, numRetries)
}
// Now log!
if auditEventsDetected || shouldLogToAdminAuditLog {
auditErrStr := "OK"
if err != nil {
auditErrStr = "ERROR"
}
var buf bytes.Buffer
buf.WriteByte('{')
sep := ""
for _, ev := range p.curPlan.auditEvents {
mode := "READ"
if ev.writing {
mode = "READWRITE"
}
fmt.Fprintf(&buf, "%s%q[%d]:%s", sep, ev.desc.GetName(), ev.desc.GetID(), mode)
sep = ", "
}
if shouldLogToAdminAuditLog {
buf.WriteString(sep)
buf.WriteString("admin")
}
buf.WriteByte('}')
logTrigger := buf.String()
log.SensitiveAccess.Infof(ctx, "%s %q %s %q %s %.3f %d %s %d",
lbl, appName, logTrigger, stmtStr, plStr, age, rows, auditErrStr, numRetries)
}
if slowQueryLogEnabled && (queryDuration > slowLogThreshold || slowLogFullTableScans) {
logReason, shouldLog := p.slowQueryLogReason(queryDuration, slowLogThreshold)
var logger log.ChannelLogger
// Non-internal queries are always logged to the slow query log.
if execType == executorTypeExec {
logger = sqlPerfLogger
}
// Internal queries that surpass the slow query log threshold should only
// be logged to the slow-internal-only log if the cluster setting dictates.
if execType == executorTypeInternal && slowInternalQueryLogEnabled {
logger = sqlPerfInternalLogger
}
if logger != nil && shouldLog {
logger.Infof(ctx, "%.3fms %s %q {} %q %s %d %q %d %s",
age, lbl, appName, stmtStr, plStr, rows, execErrStr, numRetries, logReason)
}
}
if logExecuteEnabled {
log.SqlExec.Infof(ctx, "%s %q {} %q %s %.3f %d %q %d",
lbl, appName, stmtStr, plStr, age, rows, execErrStr, numRetries)
}
return
}
// New logging format in v21.1.
sqlErrState := ""
if err != nil {
sqlErrState = pgerror.GetPGCode(err).String()
}
execDetails := eventpb.CommonSQLExecDetails{
// Note: the current statement, application name, etc, are
// automatically populated by the shared logic in event_log.go.
ExecMode: lbl,
NumRows: uint64(rows),
SQLSTATE: sqlErrState,
ErrorText: execErrStr,
Age: age,
NumRetries: uint32(numRetries),
FullTableScan: p.curPlan.flags.IsSet(planFlagContainsFullTableScan),
FullIndexScan: p.curPlan.flags.IsSet(planFlagContainsFullIndexScan),
}
if logV {
// Copy to the debug log / trace.
log.VEventf(ctx, execType.vLevel(), "%+v", execDetails)
}
if auditEventsDetected {
// TODO(knz): re-add the placeholders and age into the logging event.
for _, ev := range p.curPlan.auditEvents {
mode := "r"
if ev.writing {
mode = "rw"
}
tableName := ""
if t, ok := ev.desc.(catalog.TableDescriptor); ok {
// We only have a valid *table* name if the object being
// audited is table-like (includes view, sequence etc). For
// now, this is sufficient because the auditing feature can
// only audit tables. If/when the mechanisms are extended to
// audit databases and schema, we need more logic here to
// extract a name to include in the logging events.
tn, err := p.getQualifiedTableName(ctx, t)
if err != nil {
log.Warningf(ctx, "name for audited table ID %d not found: %v", ev.desc.GetID(), err)
} else {
tableName = tn.FQString()
}
}
p.logEventOnlyExternally(ctx, ev.desc.GetID(),
&eventpb.SensitiveTableAccess{
CommonSQLExecDetails: execDetails,
TableName: tableName,
AccessMode: mode,
})
}
}
if slowQueryLogEnabled && (
// Did the user request pumping queries into the slow query log when
// the logical plan has full scans?
(slowLogFullTableScans && (execDetails.FullTableScan || execDetails.FullIndexScan)) ||
// Is the query actually slow?
queryDuration > slowLogThreshold) {
switch {
case execType == executorTypeExec:
// Non-internal queries are always logged to the slow query log.
p.logEventOnlyExternally(ctx, 0, /* log event not trigged by descriptor */
&eventpb.SlowQuery{CommonSQLExecDetails: execDetails})
case execType == executorTypeInternal && slowInternalQueryLogEnabled:
// Internal queries that surpass the slow query log threshold should only
// be logged to the slow-internal-only log if the cluster setting dictates.
p.logEventOnlyExternally(ctx, 0, /* log event not trigged by descriptor */
&eventpb.SlowQueryInternal{CommonSQLExecDetails: execDetails})
}
}
if logExecuteEnabled || shouldLogToAdminAuditLog {
p.logEventOnlyExternally(ctx, 0, /* log event not trigged by descriptor */
&eventpb.QueryExecute{CommonSQLExecDetails: execDetails})
}
}
// maybeAudit marks the current plan being constructed as flagged
// for auditing if the table being touched has an auditing mode set.
// This is later picked up by maybeLogStatement() above.
//
// It is crucial that this gets checked reliably -- we don't want to
// miss any statements! For now, we call this from CheckPrivilege(),
// as this is the function most likely to be called reliably from any
// caller that also uses a descriptor. Future changes that move the
// call to this method elsewhere must find a way to ensure that
// contributors who later add features do not have to remember to call
// this to get it right.
func (p *planner) maybeAudit(desc catalog.Descriptor, priv privilege.Kind) {
wantedMode := desc.GetAuditMode()
if wantedMode == descpb.TableDescriptor_DISABLED {
return
}
switch priv {
case privilege.INSERT, privilege.DELETE, privilege.UPDATE:
p.curPlan.auditEvents = append(p.curPlan.auditEvents, auditEvent{desc: desc, writing: true})
default:
p.curPlan.auditEvents = append(p.curPlan.auditEvents, auditEvent{desc: desc, writing: false})
}
}
func (p *planner) slowQueryLogReason(
queryDuration time.Duration, slowLogThreshold time.Duration,
) (reason string, shouldLog bool) {
var buf bytes.Buffer
buf.WriteByte('{')
sep := " "
if slowLogThreshold != 0 && queryDuration > slowLogThreshold {
fmt.Fprintf(&buf, "%sLATENCY_THRESHOLD", sep)
}
if p.curPlan.flags.IsSet(planFlagContainsFullTableScan) {
fmt.Fprintf(&buf, "%sFULL_TABLE_SCAN", sep)
}
if p.curPlan.flags.IsSet(planFlagContainsFullIndexScan) {
fmt.Fprintf(&buf, "%sFULL_SECONDARY_INDEX_SCAN", sep)
}
buf.WriteByte(' ')
buf.WriteByte('}')
reason = buf.String()
return reason, reason != "{ }"
}
// auditEvent represents an audit event for a single table.
type auditEvent struct {
// The descriptor being audited.
desc catalog.Descriptor
// Whether the event was for INSERT/DELETE/UPDATE.
writing bool
}