-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathredis.go
216 lines (203 loc) · 5.6 KB
/
redis.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
/**
* @Author: feymanlee@gmail.com
* @Description:
* @File: redis
* @Date: 2023/4/6 18:06
*/
package logit
import (
"context"
"errors"
"time"
"github.com/gin-gonic/gin"
"github.com/go-redis/redis/v8"
"go.uber.org/zap"
"go.uber.org/zap/zapcore"
)
const (
// 默认 logger 的名称
defaultRedisLoggerName = "redis"
// 默认 caller skip
defaultRedisLoggerCallerSkip = 4
// 上下文中保存开始时间的 key
ctxRedisStartKey CtxKey = "_log_redis_start_"
// 默认的 redis 慢查询时间,30ms
defaultSlowThreshold = time.Millisecond * 30
)
type RedisLoggerOptions struct {
Name string
// CallerSkip,默认值 4
CallerSkip int
// 慢请求时间阈值 请求处理时间超过该值则使用 Warn 级别打印日志
SlowThreshold time.Duration
// 日志输出路径,默认 []string{"console"}
// Optional.
OutputPaths []string
// 日志初始字段
// Optional.
InitialFields map[string]interface{}
// 是否关闭打印 caller,默认 false
// Optional.
DisableCaller bool
// 是否关闭打印 stack strace,默认 false
// Optional.
DisableStacktrace bool
// 配置日志字段 key 的名称
// Optional.
EncoderConfig *zapcore.EncoderConfig
// nil err level
NilErrLevel string
}
type RedisLogger struct {
name string
// 指定慢查询时间
slowThreshold time.Duration
callerSkip int
_logger *zap.Logger
nilErrLevel string
}
func NewRedisLogger(opt RedisLoggerOptions) (RedisLogger, error) {
l := RedisLogger{
name: defaultRedisLoggerName,
callerSkip: defaultRedisLoggerCallerSkip,
slowThreshold: defaultSlowThreshold,
nilErrLevel: opt.NilErrLevel,
}
if opt.CallerSkip != 0 {
l.callerSkip = opt.CallerSkip
}
if opt.Name != "" {
l.name = opt.Name
}
if opt.SlowThreshold > 0 {
l.slowThreshold = opt.SlowThreshold
}
var err error
l._logger, err = NewLogger(Options{
Level: "debug",
Format: "json",
OutputPaths: opt.OutputPaths,
InitialFields: opt.InitialFields,
DisableCaller: opt.DisableCaller,
DisableStacktrace: opt.DisableStacktrace,
EncoderConfig: opt.EncoderConfig,
})
l._logger = l._logger.Named(l.name)
return l, err
}
// CtxLogger
//
// @Description: 创建打印日志的 ctx logger
// @receiver l
// @param ctx
// @return *zap.Logger
func (l RedisLogger) CtxLogger(ctx context.Context) *zap.Logger {
_, ctxLogger := NewCtxLogger(ctx, l._logger, "")
return ctxLogger.WithOptions(zap.AddCallerSkip(l.callerSkip))
}
// BeforeProcess
//
// @Description: 实现 go-redis HOOK BeforeProcess 方法
// @receiver l
// @param ctx
// @param cmd
// @return context.Context
// @return error
func (l RedisLogger) BeforeProcess(ctx context.Context, cmd redis.Cmder) (context.Context, error) {
if gc, ok := ctx.(*gin.Context); ok {
// set start time in gin.Context
gc.Set(string(ctxRedisStartKey), time.Now())
return ctx, nil
}
// set start time in context
return context.WithValue(ctx, ctxRedisStartKey, time.Now()), nil
}
// AfterProcess
//
// @Description: 实现 go-redis HOOK AfterProcess 方法
// @receiver l
// @param ctx
// @param cmd
// @return error
func (l RedisLogger) AfterProcess(ctx context.Context, cmd redis.Cmder) error {
logger := l.CtxLogger(ctx)
cost := l.getCost(ctx)
if err := cmd.Err(); err != nil {
level := zap.ErrorLevel
if errors.Is(err, redis.Nil) {
var err1 error
if level, err1 = zapcore.ParseLevel(l.nilErrLevel); err1 != nil {
level = zap.ErrorLevel
}
}
logger.Log(level, "redis trace", zap.String("command", cmd.FullName()), zap.String("args", cmd.String()), zap.Float64("latency_ms", cost), zap.Error(err))
} else {
log := logger.Info
if cost > float64(l.slowThreshold) {
log = logger.Warn
}
log("redis trace", zap.String("command", cmd.FullName()), zap.String("args", cmd.String()), zap.Float64("latency_ms", cost))
}
return nil
}
// BeforeProcessPipeline
//
// @Description:
// @receiver l
// @param ctx
// @param cmds
// @return context.Context
// @return error
func (l RedisLogger) BeforeProcessPipeline(ctx context.Context, cmds []redis.Cmder) (context.Context, error) {
if gc, ok := ctx.(*gin.Context); ok {
// set start time in gin.Context
gc.Set(string(ctxRedisStartKey), time.Now())
return ctx, nil
}
// set start time in context
return context.WithValue(ctx, ctxRedisStartKey, time.Now()), nil
}
// AfterProcessPipeline
//
// @Description: 实现 go-redis HOOK AfterProcessPipeline 方法
// @receiver l
// @param ctx
// @param cmds
// @return error
func (l RedisLogger) AfterProcessPipeline(ctx context.Context, cmds []redis.Cmder) error {
logger := l.CtxLogger(ctx)
cost := l.getCost(ctx)
pipelineArgs := make([]string, 0, len(cmds))
pipelineErrs := make([]error, 0, len(cmds))
for _, cmd := range cmds {
pipelineArgs = append(pipelineArgs, cmd.String())
if err := cmd.Err(); err != nil {
pipelineErrs = append(pipelineErrs, err)
}
}
if len(pipelineErrs) > 0 {
logger.Warn("redis trace", zap.Any("args", pipelineArgs), zap.Bool("pipeline", true), zap.Float64("latency_ms", cost), zap.Errors("errors", pipelineErrs))
} else {
logger.Info("redis trace", zap.Any("args", pipelineArgs), zap.Bool("pipeline", true), zap.Float64("latency_ms", cost))
}
return nil
}
// getCost
//
// @Description: 获取命令执行耗时
// @receiver l
// @param ctx
// @return cost
func (l RedisLogger) getCost(ctx context.Context) (cost float64) {
var startTime time.Time
if gc, ok := ctx.(*gin.Context); ok {
// set start time in gin.Context
startTime = gc.GetTime(string(ctxRedisStartKey))
} else {
startTime = ctx.Value(ctxRedisStartKey).(time.Time)
}
if !startTime.IsZero() {
cost = time.Since(startTime).Seconds() * 1e3
}
return
}