-
-
Notifications
You must be signed in to change notification settings - Fork 0
/
gorm.go
171 lines (151 loc) · 5.19 KB
/
gorm.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
package logger
import (
"context"
"fmt"
"runtime"
"strconv"
"strings"
"time"
)
// GormLoggerInterface is a logger interface to help work with GORM
type GormLoggerInterface interface {
Error(context.Context, string, ...interface{})
GetMode() GormLogLevel
GetStackLevel() int
Info(context.Context, string, ...interface{})
SetMode(GormLogLevel) GormLoggerInterface
SetStackLevel(level int)
Trace(ctx context.Context, begin time.Time, fc func() (sql string, rowsAffected int64), err error)
Warn(context.Context, string, ...interface{})
}
// GormLogLevel is the GORM log level
type GormLogLevel int
const (
// Silent silent log level
Silent GormLogLevel = iota + 1
// Error error log level
Error
// Warn warn log level
Warn
// Info info log level
Info
)
// SlowQueryThreshold is the time cut-off for considering a query as "slow"
const SlowQueryThreshold = 5 * time.Second
// NewGormLogger will return a basic logger interface
func NewGormLogger(debugging bool, stackLevel int) GormLoggerInterface {
logLevel := Warn
if debugging {
logLevel = Info
}
return &basicGormLogger{
logLevel: logLevel,
stackLevel: stackLevel,
}
}
// basicGormLogger is a basic implementation of the logger interface if no custom logger is provided
type basicGormLogger struct {
logLevel GormLogLevel // Log level (info, error, etc)
stackLevel int // How many files/functions to traverse upwards to record the file/line
}
// SetMode will set the log mode
func (l *basicGormLogger) SetMode(level GormLogLevel) GormLoggerInterface {
newLogger := *l
newLogger.logLevel = level
return &newLogger
}
// SetStackLevel will set the stack level
func (l *basicGormLogger) SetStackLevel(level int) {
l.stackLevel = level
}
// GetStackLevel will get the current stack level
func (l *basicGormLogger) GetStackLevel() int {
return l.stackLevel
}
// GetMode will get the log mode
func (l *basicGormLogger) GetMode() GormLogLevel {
return l.logLevel
}
// Info print information
func (l *basicGormLogger) Info(_ context.Context, message string, params ...interface{}) {
if l.logLevel >= Info {
displayLog(INFO, l.stackLevel, message, params...)
}
}
// Warn print warn messages
func (l *basicGormLogger) Warn(_ context.Context, message string, params ...interface{}) {
if l.logLevel >= Warn {
displayLog(WARN, l.stackLevel, message, params...)
}
}
// Error print error messages
func (l *basicGormLogger) Error(_ context.Context, message string, params ...interface{}) {
if l.logLevel >= Error {
displayLog(ERROR, l.stackLevel, message, params...)
}
}
// Trace is for GORM/SQL tracing from datastore
func (l *basicGormLogger) Trace(_ context.Context, begin time.Time, fc func() (sql string, rowsAffected int64), err error) {
if l.logLevel <= Silent {
return
}
elapsed := time.Since(begin)
switch {
case err != nil && l.logLevel >= Error && (!strings.Contains(err.Error(), "record not found")):
sql, rows := fc()
Data(l.stackLevel, ERROR,
"error executing query",
MakeParameter("file", fileWithLineNum()),
MakeParameter("error", err.Error()),
MakeParameter("duration", fmt.Sprintf("%.3fms", float64(elapsed.Nanoseconds())/1e6)),
MakeParameter("rows", rows),
MakeParameter("sql", sql),
)
case elapsed > SlowQueryThreshold && l.logLevel >= Warn:
sql, rows := fc()
Data(l.stackLevel, WARN,
"warning executing query",
MakeParameter("file", fileWithLineNum()),
MakeParameter("slow_log", fmt.Sprintf("SLOW SQL >= %v", SlowQueryThreshold)),
MakeParameter("duration", fmt.Sprintf("%.3fms", float64(elapsed.Nanoseconds())/1e6)),
MakeParameter("rows", rows),
MakeParameter("sql", sql),
)
case l.logLevel == Info:
sql, rows := fc()
Data(l.stackLevel, INFO,
"executing sql query",
MakeParameter("file", fileWithLineNum()),
MakeParameter("duration", fmt.Sprintf("%.3fms", float64(elapsed.Nanoseconds())/1e6)),
MakeParameter("rows", rows),
MakeParameter("sql", sql),
)
}
}
// displayLog will display a log using logger
func displayLog(level LogLevel, stackLevel int, message string, params ...interface{}) {
var keyValues []KeyValue
if len(params) > 0 {
for index, val := range params {
keyValues = append(keyValues, MakeParameter(fmt.Sprintf("param_%d", index), val))
}
}
Data(stackLevel, level, message, keyValues...)
}
// fileWithLineNum return the file name and line number of the current file
// This is originally from GORM: https://github.com/go-gorm/gorm/blob/7837fb6fa001ef78bc76e66b48445dee7b2db37b/utils/utils.go#L23
// Copied method in order to not make GORM a dependency of the project for this tiny utility method
func fileWithLineNum() string {
// the first & second caller usually from gorm internal, so set index start from 3
for i := 2; i < 15; i++ {
_, file, line, ok := runtime.Caller(i)
if ok && (!strings.HasSuffix(file, "_test.go") && // Skip test files
!strings.Contains(file, "gorm.go") && // This is our local "gorm.go" file
// !strings.Contains(file, "migrator.go") && (This file actually is used when using Migrations
!strings.Contains(file, "callbacks.go") && // This file is a helper for GORM
!strings.Contains(file, "finisher_api.go")) { // This file is a helper for GORM
return file + ":" + strconv.FormatInt(int64(line), 10)
}
}
return ""
}