/
log.go
194 lines (156 loc) · 5.67 KB
/
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
package log
import (
"context"
"sync"
"time"
"github.com/sjxiang/miniblog/internal/pkg/consts"
"go.uber.org/zap"
"go.uber.org/zap/zapcore"
)
// Logger 定义了 miniblog 项目的日志接口,该接口只包含了支持的日志记录方法(还缺点东西)
type Logger interface {
Debugw(msg string, keyAndValues ...interface{})
Infow(msg string, keyAndValues ...interface{})
Warnw(msg string, keyAndValues ...interface{})
Errorw(msg string, keyAndValues ...interface{})
Panicw(msg string, keyAndValues ...interface{})
Fatalw(msg string, keyAndValues ...interface{})
Sync()
}
// zapLogger 是 Logger 接口的具体实现,它底层封装了 zap.Logger
type zapLogger struct {
z *zap.Logger
}
// 确保 zapLogger 实现了 Logger 接口. 以下变量赋值,可以使错误在编译期被发现.
var _ Logger = &zapLogger{}
var (
mu sync.Mutex
// std 定义了默认的全局 Logger
std = NewLogger(NewOptions())
)
// Init 使用指定的选项初始化 Logger
func Init(opts *Options) {
mu.Lock()
defer mu.Unlock()
std = NewLogger(opts)
}
// NewLogger 根据传入的 opts 创建 Logger
func NewLogger(opts *Options) *zapLogger {
if opts == nil {
opts = NewOptions()
}
// 将文本格式的日志级别,例如 info 转换为 zapcore.Level 类型以供后面使用
var zapLevel zapcore.Level
if err := zapLevel.UnmarshalText([]byte(opts.Level)); err != nil {
// 如果指定了非法的日志级别,则默认使用 info 级别
zapLevel = zapcore.InfoLevel
}
// 创建一个默认的 encoder 配置
encoderConfig := zap.NewProductionEncoderConfig()
// 自定义 MessageKey 为 message,message 语义更明确
encoderConfig.MessageKey = "message"
// 自定义 TimeKey 为 timestamp,timestamp 语义更明确
encoderConfig.TimeKey = "timestamp"
// 指定时间序列化函数,将时间序列化为 `2006-01-02 15:04:05.000` 格式,更易读
encoderConfig.EncodeTime = func(t time.Time, enc zapcore.PrimitiveArrayEncoder) {
enc.AppendString(t.Format("2006-01-02 15:04:05.000"))
}
// 指定 time.Duration 序列化函数,将 time.Duration 序列化为经过的毫秒数的浮点数
// 毫秒数比默认的秒数更精确
encoderConfig.EncodeDuration = func(d time.Duration, enc zapcore.PrimitiveArrayEncoder) {
enc.AppendFloat64(float64(d) / float64(time.Millisecond))
}
// 创建构建 zap.Logger 需要的配置
cfg := &zap.Config{
// 是否在日志中显示调用日志所在的文件和行号,例如:`"caller":"miniblog/miniblog.go:75"`
DisableCaller: opts.DisableCaller,
// 是否禁止在 panic 及以上级别打印堆栈信息
DisableStacktrace: opts.DisableStacktrace,
// 指定日志级别
Level: zap.NewAtomicLevelAt(zapLevel),
// 指定日志显示格式,可选值:console, json
Encoding: opts.Format,
EncoderConfig: encoderConfig,
// 指定日志输出位置
OutputPaths: opts.OutputPaths,
// 设置 zap 内部错误输出位置
ErrorOutputPaths: []string{"stderr"},
}
// 使用 cfg 创建 *zap.Logger 对象
z, err := cfg.Build(zap.AddStacktrace(zapcore.PanicLevel), zap.AddCallerSkip(1))
if err != nil {
panic(err)
}
logger := &zapLogger{z: z}
// 把标准库的 log.Logger 的 info 级别的输出重定向到 zap.Logger(有点误解)
zap.RedirectStdLog(z)
return logger
}
/*
两套实现,
1. 接口确保完备
2. 方便使用 log.Infow() 或者依赖注入实现,就是很多代码要写
*/
// Debugw 输出 debug 级别的日志.
func Debugw(msg string, keysAndValues ...interface{}) {
std.z.Sugar().Debugw(msg, keysAndValues...)
}
func (l *zapLogger) Debugw(msg string, keysAndValues ...interface{}) {
l.z.Sugar().Debugw(msg, keysAndValues...)
}
// Infow 输出 info 级别的日志.
func Infow(msg string, keysAndValues ...interface{}) {
std.z.Sugar().Infow(msg, keysAndValues...)
}
func (l *zapLogger) Infow(msg string, keysAndValues ...interface{}) {
l.z.Sugar().Infow(msg, keysAndValues...)
}
// Warnw 输出 warning 级别的日志.
func Warnw(msg string, keysAndValues ...interface{}) {
std.z.Sugar().Warnw(msg, keysAndValues...)
}
func (l *zapLogger) Warnw(msg string, keysAndValues ...interface{}) {
l.z.Sugar().Warnw(msg, keysAndValues...)
}
// Errorw 输出 error 级别的日志.
func Errorw(msg string, keysAndValues ...interface{}) {
std.z.Sugar().Errorw(msg, keysAndValues...)
}
func (l *zapLogger) Errorw(msg string, keysAndValues ...interface{}) {
l.z.Sugar().Errorw(msg, keysAndValues...)
}
// Panicw 输出 panic 级别的日志.
func Panicw(msg string, keysAndValues ...interface{}) {
std.z.Sugar().Panicw(msg, keysAndValues...)
}
func (l *zapLogger) Panicw(msg string, keysAndValues ...interface{}) {
l.z.Sugar().Panicw(msg, keysAndValues...)
}
// Fatalw 输出 fatal 级别的日志.
func Fatalw(msg string, keysAndValues ...interface{}) {
std.z.Sugar().Fatalw(msg, keysAndValues...)
}
func (l *zapLogger) Fatalw(msg string, keysAndValues ...interface{}) {
l.z.Sugar().Fatalw(msg, keysAndValues...)
}
// Sync 调用底层 zap.Logger 的 Sync 方法,将缓存中的日志刷新到磁盘文件中. 主程序需要在退出前调用 Sync.
func Sync() { std.Sync() }
func (l *zapLogger) Sync() {
_ = l.z.Sync()
}
// C 解析传入的 contetx,尝试提取关注的键值,并添加到 zap.Logger 结构化日志中
func C(ctx context.Context) *zapLogger {
return std.C(ctx)
}
func (l *zapLogger) C(ctx context.Context) *zapLogger {
lc := l.clone()
if requestID := ctx.Value(consts.XRequestIDKey); requestID != nil {
lc.z = lc.z.With(zap.Any(consts.XRequestIDKey, requestID))
}
return lc
}
// clone 深度拷贝 zapLogger(用户打上烙印,服务器白纸一张,可不能混淆;新建拷贝一份)
func (l *zapLogger) clone() *zapLogger {
lc := *l
return &lc
}