-
Notifications
You must be signed in to change notification settings - Fork 0
/
queryhook.go
158 lines (135 loc) · 3.36 KB
/
queryhook.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
package db
import (
"context"
"database/sql"
"fmt"
"time"
"github.com/uptrace/bun"
"go.uber.org/zap"
"go.uber.org/zap/zapcore"
)
type QueryHook struct {
errorFieldName string
precision time.Duration
logger *zap.Logger
enabled bool
verbose bool
durationAsField bool
errorAsField bool
duration bool
queryLevel zapcore.Level
errorLevel zapcore.Level
}
type Option func(*QueryHook)
// WithEnabled enables/disables the hook.
func WithEnabled(on bool) Option {
return func(h *QueryHook) {
h.enabled = on
}
}
// WithVerbose configures the hook to log all queries
// (by default, only failed queries are logged).
func WithVerbose(on bool) Option {
return func(h *QueryHook) {
h.verbose = on
}
}
// WithDurationAsField configures the hook to set the duration as field,
// written in the message by default.
func WithDurationAsField() Option {
return func(h *QueryHook) {
h.duration = true
h.durationAsField = true
}
}
// WithDurationPrecision configures the hook to log the duration with
// the specified precision.
// e.g. passing time.Millisecond returns a duration in ms.
func WithDurationPrecision(precision time.Duration) Option {
return func(h *QueryHook) {
h.durationAsField = true
}
}
// WithErrorAsField configures the hook to log the error as a field.
func WithErrorAsField(field string) Option {
return func(h *QueryHook) {
h.errorAsField = true
h.errorFieldName = field
}
}
// WithLevels configures the hook to make proper usage of zap levels.
func WithLevels(queryLevel, errorLevel zapcore.Level) Option {
return func(h *QueryHook) {
h.queryLevel = queryLevel
h.errorLevel = errorLevel
}
}
// WithDuration configures the hook to log the duration.
func WithDuration() Option {
return func(h *QueryHook) {
h.duration = true
}
}
// NewQueryHook creates a new query hook.
func NewQueryHook(logger *zap.Logger, opts ...Option) *QueryHook {
qh := &QueryHook{
errorFieldName: "error",
precision: time.Millisecond,
logger: logger,
enabled: true,
verbose: false,
durationAsField: false,
errorAsField: false,
duration: false,
queryLevel: zapcore.DebugLevel,
errorLevel: zapcore.ErrorLevel,
}
for _, opt := range opts {
opt(qh)
}
return qh
}
func (h *QueryHook) BeforeQuery(ctx context.Context, _ *bun.QueryEvent) context.Context { return ctx }
func (h *QueryHook) AfterQuery(_ context.Context, event *bun.QueryEvent) {
if !h.enabled {
return
}
var level zapcore.Level
var err error
switch event.Err {
case nil, sql.ErrNoRows, sql.ErrTxDone:
if !h.verbose {
return
}
level = h.queryLevel
err = nil
default:
level = h.errorLevel
err = event.Err
}
now := time.Now()
dur := now.Sub(event.StartTime)
message := event.Query
fields := []zap.Field{}
if h.duration && h.durationAsField {
fields = append(fields, zap.Field{
Key: "duration",
Type: zapcore.StringerType,
Interface: dur.Round(h.precision),
})
} else if h.duration {
message = fmt.Sprintf("duration: %s %s", dur.Round(h.precision), message)
}
if err != nil {
if h.errorAsField {
fields = append(fields, zap.Field{
Key: h.errorFieldName,
Type: zapcore.ErrorType,
Interface: err,
})
} else {
message = fmt.Sprintf("%s error: %s", message, err)
}
}
h.logger.Log(level, message, fields...)
}