01
介绍
我们在之前的文章中介绍过标准库 log
包的使用方式,它虽然使用方便,但是它支持的功能比较简单。
本文我们介绍 uber 开源的日志库 zap
,首先使用 Gin 框架构建一个 Web 应用,然后通过在该 Web 应用中记录日志,来介绍 zap
的使用方式。
最后,我们再使用开源的日志切割库 lumberjack
,进行日志切割。
02
使用 Gin 构建一个 Web 应用
本文重点不是介绍 gin 框架的使用方式,所以我们仅使用 gin 框架构建一个简单的 Web 应用,代码如下:
func main() {
r := gin.Default()
r.GET("/ping", ping)
r.Run()
}
func ping(c *gin.Context) {
c.JSON(http.StatusOK, gin.H{
"message": "pong",
})
}
阅读上面这段代码,访问 http://127.0.0.1:8080/ping
,返回结果是 {"message":"pong"}
。
然后,我们使用 zap
记录 ping
函数的请求日志。
03
Gin 框架使用 zap 日志库
Zap 支持两种模式,分别是 SugaredLogger
和 Logger
,其中 SugaredLogger
模式比 Logger
模式执行速度更慢。
SugaredLogger 模式
使用 Zap 日志库,首先需要使用 New
函数创建一个 Logger
,代码如下:
func New(core zapcore.Core, options ...Option) *Logger
使用 New
函数,接收一个 zapcore.Core
类型的参数和一个 Option
类型的可选参数,返回一个 *Logger
。
其中 zap.Core
类型的参数,可以使用 NewCore
函数创建,接收三个参数,分别是 zapcore.Encoder
类型,zapcore.WriteSyncer
类型和 zapcore.LevelEnabler
类型,分别用于指定日志格式、日志路径和日志级别。
func NewCore(enc Encoder, ws WriteSyncer, enab LevelEnabler) Core
其中 zapcore.Encoder
类型的参数,可以使用 NewProductionEncoderConfig
函数创建,返回一个用于生产环境的固定日志编码配置。
// NewProductionEncoderConfig returns an opinionated EncoderConfig for
// production environments.
func NewProductionEncoderConfig() zapcore.EncoderConfig {
return zapcore.EncoderConfig{
TimeKey: "ts",
LevelKey: "level",
NameKey: "logger",
CallerKey: "caller",
FunctionKey: zapcore.OmitKey,
MessageKey: "msg",
StacktraceKey: "stacktrace",
LineEnding: zapcore.DefaultLineEnding,
EncodeLevel: zapcore.LowercaseLevelEncoder,
EncodeTime: zapcore.EpochTimeEncoder,
EncodeDuration: zapcore.SecondsDurationEncoder,
EncodeCaller: zapcore.ShortCallerEncoder,
}
}
我们可以修改任意配置选项的值。
其中 zapcore.WriteSyncer
类型的参数,可以使用 AddSync
函数创建,该函数接收一个 io.Writer
类型的参数。
func AddSync(w io.Writer) WriteSyncer
其中 zapcore.LevelEnabler
类型的参数,可以使用 zapcore
包定义的常量 zapcore.DebugLevel
,该常量是 zapcore.Level
类型,并且 zapcore.Level
类型实现了 zapcore.LevelEnabler
接口。
完整代码:
var sugaredLogger *zap.SugaredLogger
func main() {
InitLogger()
defer sugaredLogger.Sync()
r := gin.Default()
r.GET("/ping", ping)
r.Run()
}
func ping(c *gin.Context) {
sugaredLogger.Debug("call func ping")
c.JSON(http.StatusOK, gin.H{
"message": "pong",
})
}
func InitLogger() {
core := zapcore.NewCore(enc(), ws(), enab())
logger := zap.New(core)
sugaredLogger = logger.Sugar()
}
func enc() zapcore.Encoder {
cfg := zap.NewProductionEncoderConfig()
cfg.TimeKey = "time"
cfg.EncodeTime = zapcore.TimeEncoderOfLayout("2006-01-02 15:04:05")
return zapcore.NewJSONEncoder(cfg)
}
func ws() zapcore.WriteSyncer {
logFileName := fmt.Sprintf("./%v.log", time.Now().Format("2006-01-02"))
logFile, err := os.Create(logFileName)
if err != nil {
log.Fatal(err)
}
return zapcore.AddSync(logFile)
}
func enab() zapcore.LevelEnabler {
return zapcore.DebugLevel
}
运行程序,执行 curl http://127.0.0.1:8080/ping
。
可以看到,生成的日志文件 xxx.log
,文件中是 json
格式的日志内容,我们可以根据实际需求修改为其他格式。
开发中,可能我们希望日志可以同时输出到日志文件和终端中,可以使用函数 NewMultiWriteSyncer
,代码如下:
func wsV2() zapcore.WriteSyncer {
return zapcore.NewMultiWriteSyncer(ws(), zapcore.AddSync(os.Stdout))
}
除了使用zap.New()
创建Logger
之外,Zap 还提供了开箱即用的三种创建Logger
的方式,分别是函数NewProduction
,NewDevelopment
和Example()
,感兴趣的读者朋友们,可以试用一下。
Logger 模式
接下来,我们简单介绍一下 Logger
模式,它主要用于性能和类型安全比较重要的场景中,但是,它没有 SugaredLogger
模式简单易用,我们可以根据实际场景选择使用哪种模式。
我们修改一下现有代码,新创建 InitLoggerV2
函数,其中 enc
,ws
和 enab
函数的代码与 SugaredLogger
模式保持一致。
var loggerV2 *zap.Logger
func main() {
InitLoggerV2()
defer loggerV2.Sync()
r := gin.Default()
r.GET("/ping", ping)
r.Run()
}
func ping(c *gin.Context) {
loggerV2.Debug("call func ping", zap.Int("code", 200))
c.JSON(http.StatusOK, gin.H{
"message": "pong",
})
}
func InitLoggerV2() {
core := zapcore.NewCore(enc(), ws(), enab())
loggerV2 = zap.New(core)
}
阅读上面这段代码,我们可以发现,在使用 zap
记录日志时,我们需要显示指定数据类型,一般用于性能和类型安全比较重要的场景中。
04
zap 日志库使用 lumberjack 库进行日志切割
Zap 日志库也不支持日志切割的功能,我们可以使用 lumberjack
日志切割库进行日志切割,关于 lumberjack
库的使用方式,我们在之前的文章介绍过,此处不再重复介绍,直接上代码:
func wsV3() zapcore.WriteSyncer {
logFileName := fmt.Sprintf("./%v.log", time.Now().Format("2006-01-02"))
lumberjackLogger := &lumberjack.Logger{
Filename: logFileName,
MaxSize: 1,
MaxBackups: 3,
MaxAge: 28,
Compress: false,
}
return zapcore.AddSync(lumberjackLogger)
}
lumberjack.Logger
的字段含义:
- Filename 日志保存文件路径
- MaxSize 日志文件大小,单位是
MB
- MaxBackups 保留的日志文件数量
- MaxAge 日志文件的最长保留时间,单位是天
- Compress 日志文件是否需要压缩
05
总结
本文我们通过在 Gin 构建的应用中,使用 Zap 记录请求日志,介绍了 Zap 的使用方式,最后还通过 lumberjack
日志切割库进行切割日志。
更多关于 Zap 的内容,感兴趣的读者朋友们可以阅读官方文档或源码。
参考资料:
- https://github.com/uber-go/zap
- https://pkg.go.dev/go.uber.org/zap