-
Notifications
You must be signed in to change notification settings - Fork 15
/
options.go
394 lines (348 loc) · 10.7 KB
/
options.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
392
393
394
package sqldblogger
import (
cryptoRand "crypto/rand"
"encoding/binary"
"fmt"
"math/rand"
"time"
)
type options struct {
errorFieldname string
durationFieldname string
timeFieldname string
startTimeFieldname string
sqlQueryFieldname string
sqlArgsFieldname string
stmtIDFieldname string
connIDFieldname string
txIDFieldname string
sqlQueryAsMsg bool
logArgs bool
logDriverErrSkip bool
wrapResult bool
minimumLogLevel Level
durationUnit DurationUnit
timeFormat TimeFormat
uidGenerator UIDGenerator
includeStartTime bool
preparerLevel Level
queryerLevel Level
execerLevel Level
}
// setDefaultOptions called first time before Log() called (see: OpenDriver()).
// To change option value, use With* functions below.
func setDefaultOptions(opt *options) {
opt.errorFieldname = "error"
opt.durationFieldname = "duration"
opt.timeFieldname = "time"
opt.startTimeFieldname = "start"
opt.sqlQueryFieldname = "query"
opt.sqlArgsFieldname = "args"
opt.stmtIDFieldname = "stmt_id"
opt.connIDFieldname = "conn_id"
opt.txIDFieldname = "tx_id"
opt.sqlQueryAsMsg = false
opt.minimumLogLevel = LevelDebug
opt.logArgs = true
opt.logDriverErrSkip = false
opt.wrapResult = true
opt.durationUnit = DurationMillisecond
opt.timeFormat = TimeFormatUnix
opt.uidGenerator = newDefaultUIDDGenerator()
opt.includeStartTime = false
opt.preparerLevel = LevelInfo
opt.queryerLevel = LevelInfo
opt.execerLevel = LevelInfo
}
// DurationUnit is total time spent on an actual driver function call calculated by time.Since(start).
type DurationUnit uint8
const (
// DurationNanosecond will format time.Since() result to nanosecond unit (1/1_000_000_000 second).
DurationNanosecond DurationUnit = iota
// DurationMicrosecond will format time.Since() result to microsecond unit (1/1_000_000 second).
DurationMicrosecond
// DurationMillisecond will format time.Since() result to millisecond unit (1/1_000 second).
DurationMillisecond
)
func (du DurationUnit) format(duration time.Duration) float64 {
nanosecond := float64(duration.Nanoseconds())
switch du {
case DurationNanosecond:
return nanosecond
case DurationMicrosecond:
return nanosecond / float64(time.Microsecond)
case DurationMillisecond:
return nanosecond / float64(time.Millisecond)
default:
return nanosecond
}
}
// TimeFormat is time.Now() format when Log() deliver the log message.
type TimeFormat uint8
const (
// TimeFormatUnix will format log time to unix timestamp.
TimeFormatUnix TimeFormat = iota
// TimeFormatUnixNano will format log time to unix timestamp with nano seconds.
TimeFormatUnixNano
// TimeFormatRFC3339 will format log time to time.RFC3339 format.
TimeFormatRFC3339
// TimeFormatRFC3339Nano will format log time to time.RFC3339Nano format.
TimeFormatRFC3339Nano
)
func (tf TimeFormat) format(logTime time.Time) interface{} {
switch tf {
case TimeFormatUnix:
return logTime.Unix()
case TimeFormatUnixNano:
return logTime.UnixNano()
case TimeFormatRFC3339:
return logTime.Format(time.RFC3339)
case TimeFormatRFC3339Nano:
return logTime.Format(time.RFC3339Nano)
default:
return logTime.Unix()
}
}
// UIDGenerator is an interface to generate unique ID for context call (connection, statement, transaction).
// The point of having unique id per context call is to easily track and analyze logs.
//
// Note: no possible way to track id when statement Execer(Context),Queryer(Context) called from under db.Tx.
type UIDGenerator interface {
UniqueID() string
}
const (
defaultUIDLen = 16
defaultUIDCharlist = "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ_"
)
// newDefaultUIDDGenerator default unique id generator using crypto/rand as math/rand seed.
func newDefaultUIDDGenerator() UIDGenerator {
var s [16]byte
if _, err := cryptoRand.Read(s[:]); err != nil {
panic(fmt.Sprintf("sqldblogger: could not get random bytes from cryto/rand: '%s'", err.Error()))
}
// seed math/rand with 16 random bytes from crypto/rand to make sure rand.Seed is not 1.
// rand.Seed will be used by rand.Read inside UniqueID().
rand.Seed(int64(binary.LittleEndian.Uint64(s[:])))
return &defaultUID{}
}
type defaultUID struct{}
// UniqueID Generate default 16 byte unique id using math/rand.
func (u *defaultUID) UniqueID() string {
var random, uid [defaultUIDLen]byte
// using math/rand.Read because it's slightly faster than crypto/rand.Read
// unique id always scoped under connectionID so there is no need to super-secure-random using crypto/rand.
//
// nolint // disable gosec check as it does not need crypto/rand
if _, err := rand.Read(random[:]); err != nil {
panic(fmt.Sprintf("sqldblogger: random read error from math/rand: '%s'", err.Error()))
}
for i := 0; i < defaultUIDLen; i++ {
uid[i] = defaultUIDCharlist[random[i]&62]
}
return string(uid[:])
}
// NullUID is used to disable unique id when set to WithUIDGenerator().
type NullUID struct{}
// UniqueID return empty string and unique id will not logged.
func (u *NullUID) UniqueID() string { return "" }
// Option is optional variadic type in OpenDriver().
type Option func(*options)
// WithUIDGenerator set custom unique id generator for context call (connection, statement, transaction).
//
// To disable unique id in log output, use &NullUID{}.
//
// Default: newDefaultUIDDGenerator() called from setDefaultOptions().
func WithUIDGenerator(gen UIDGenerator) Option {
return func(opt *options) {
opt.uidGenerator = gen
}
}
// WithErrorFieldname to customize error fieldname on log output.
//
// Default: "error"
func WithErrorFieldname(name string) Option {
return func(opt *options) {
opt.errorFieldname = name
}
}
// WithDurationFieldname to customize duration fieldname on log output.
//
// Default: "duration"
func WithDurationFieldname(name string) Option {
return func(opt *options) {
opt.durationFieldname = name
}
}
// WithTimeFieldname to customize log timestamp fieldname on log output.
//
// Default: "time"
func WithTimeFieldname(name string) Option {
return func(opt *options) {
opt.timeFieldname = name
}
}
// WithSQLQueryFieldname to customize SQL query fieldname on log output.
//
// Default: "query"
func WithSQLQueryFieldname(name string) Option {
return func(opt *options) {
opt.sqlQueryFieldname = name
}
}
// WithSQLArgsFieldname to customize SQL query arguments fieldname on log output.
//
// Default: "args"
func WithSQLArgsFieldname(name string) Option {
return func(opt *options) {
opt.sqlArgsFieldname = name
}
}
// WithMinimumLevel set minimum level to be logged. Logger will always log level >= minimum level.
//
// Options: LevelTrace < LevelDebug < LevelInfo < LevelError
//
// Default: LevelDebug
func WithMinimumLevel(lvl Level) Option {
return func(opt *options) {
if lvl > LevelError || lvl < LevelTrace {
return
}
opt.minimumLogLevel = lvl
}
}
// WithLogArguments set flag to log SQL query argument or not.
//
// When set to false, any SQL and result/rows argument on Queryer(Context) and Execer(Context) will not logged.
//
// When set to true, argument type string and []byte will subject to trim on parseArgs() log output.
//
// Default: true
func WithLogArguments(flag bool) Option {
return func(opt *options) {
opt.logArgs = flag
}
}
// WithLogDriverErrorSkip set flag for driver.ErrSkip.
//
// If driver not implement optional interfaces, driver will return driver.ErrSkip and sql.DB will handle that.
// driver.ErrSkip could be false alarm in log analyzer because it was not actual error from app.
//
// When set to false, logger will log any driver.ErrSkip.
//
// Default: true
func WithLogDriverErrorSkip(flag bool) Option {
return func(opt *options) {
opt.logDriverErrSkip = flag
}
}
// WithDurationUnit to customize log duration unit.
//
// Options: DurationMillisecond | DurationMicrosecond | DurationNanosecond
//
// Default: DurationMillisecond
func WithDurationUnit(unit DurationUnit) Option {
return func(opt *options) {
opt.durationUnit = unit
}
}
// WithTimeFormat to customize log time format.
//
// Options: TimeFormatUnix | TimeFormatUnixNano | TimeFormatRFC3339 | TimeFormatRFC3339Nano
//
// Default: TimeFormatUnix
func WithTimeFormat(format TimeFormat) Option {
return func(opt *options) {
if format < TimeFormatUnix || format > TimeFormatRFC3339Nano {
return
}
opt.timeFormat = format
}
}
// WithSQLQueryAsMessage set SQL query as message in log output (only for function call with SQL query).
//
// Default: false
func WithSQLQueryAsMessage(flag bool) Option {
return func(opt *options) {
opt.sqlQueryAsMsg = flag
}
}
// WithConnectionIDFieldname to customize connection ID fieldname on log output.
//
// Default: "conn_id"
func WithConnectionIDFieldname(name string) Option {
return func(opt *options) {
opt.connIDFieldname = name
}
}
// WithStatementIDFieldname to customize prepared statement ID fieldname on log output.
//
// Default: "stmt_id"
func WithStatementIDFieldname(name string) Option {
return func(opt *options) {
opt.stmtIDFieldname = name
}
}
// WithTransactionIDFieldname to customize database transaction ID fieldname on log output.
//
// Default: "tx_id"
func WithTransactionIDFieldname(name string) Option {
return func(opt *options) {
opt.txIDFieldname = name
}
}
// WithWrapResult set flag to wrap Queryer(Context) and Execer(Context) driver.Rows/driver.Result response.
//
// When set to false, result returned from db (driver.Rows/driver.Result object),
// will returned as is without wrapped inside &rows{} and &result{}.
//
// Default: true
func WithWrapResult(flag bool) Option {
return func(opt *options) {
opt.wrapResult = flag
}
}
// WithIncludeStartTime flag to include actual start time before actual driver execution.
//
// Can be useful if we want to combine Log implementation with tracing from context
// and set start time span manually.
//
// Default: false
func WithIncludeStartTime(flag bool) Option {
return func(opt *options) {
opt.includeStartTime = flag
}
}
// WithStartTimeFieldname to customize start time fieldname on log output.
//
// If WithIncludeStartTime true, start time fieldname will use this value.
//
// Default: "start"
func WithStartTimeFieldname(name string) Option {
return func(opt *options) {
opt.startTimeFieldname = name
}
}
// WithPreparerLevel set default level of Prepare(Context) method calls.
//
// Default: LevelInfo
func WithPreparerLevel(lvl Level) Option {
return func(opt *options) {
opt.preparerLevel = lvl
}
}
// WithQueryerLevel set default level of Query(Context) method calls.
//
// Default: LevelInfo
func WithQueryerLevel(lvl Level) Option {
return func(opt *options) {
opt.queryerLevel = lvl
}
}
// WithExecerLevel set default level of Exec(Context) method calls.
//
// Default: LevelInfo
func WithExecerLevel(lvl Level) Option {
return func(opt *options) {
opt.execerLevel = lvl
}
}