前言
Zap 是一款由 uber-go 开发的开源日志库,它支持多种日志级别和结构化,并对性能和内存分配做了极致的优化。
试用期大作业的后端中我们使用了 Zap 来进行日志记录,今日探索日志滚动时顺带回顾了一下 Zap 的基本用法,于是决定水一篇博客(逃
项目地址:https://github.com/uber-go/zap
快速使用
1
| go get -u go.uber.org/zap
|
Zap 库的使用与其他的日志库非常相似,我们需要先创建一个日志记录器,然后调用相应的方法来记录不同级别的日志。
Zap 提供了两种日志记录器:Sugared Logger
和 Logger
.
举例如下:
1 2 3 4 5 6 7 8 9
| func main() { sugar := zap.NewExample().Sugar() sugar.Infof("hello! name:%s,age:%d", "xiaomin", 20)
logger := zap.NewExample() logger.Info("hello!", zap.String("name", "xiaomin"), zap.Int("age", 20)) }
|
输出结果:
1 2 3
| {"level":"info","msg":"hello! name:xiaomin,age:20"} {"level":"info","msg":"hello!","name":"xiaomin","age":20}
|
要创建一个 Logger,Zap 提供了三个默认配置:Example
,Development
,Production
,分别对应测试环境、开发环境和生产环境。
1 2 3 4 5 6 7 8 9 10 11 12 13
| func main() { logger := zap.NewExample() logger.Info("Example")
logger, _ = zap.NewDevelopment() logger.Info("Development")
logger, _ = zap.NewProduction() logger.Info("Production") }
|
相应的输出如下:
1 2 3 4 5 6 7 8
| {"level":"info","msg":"Example"}
2024-10-24T22:52:16.544+0800 INFO ConfessionWallServer/main.go:14 Development
{"level":"info","ts":1729781536.5583117,"caller":"ConfessionWallServer/main.go:18","msg":"Production"}
|
可以看到,日志等级,日志输出格式,默认字段都有所差异。
定制 Logger
Zap 提供了丰富的配置选项。
使用自定义配置
我们可以通过自己创建 Config 来配置 Logger 的行为。
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
| func main() { config := zap.Config{ Level: zap.NewAtomicLevelAt(zap.InfoLevel), Encoding: "json", EncoderConfig: zap.EncoderConfig{ TimeKey: "time", LevelKey: "level", NameKey: "logger", CallerKey: "caller", MessageKey: "msg", StacktraceKey: "stacktrace", LineEnding: zapcore.DefaultLineEnding, EncodeLevel: zapcore.LowercaseLevelEncoder, EncodeTime: zapcore.ISO8601TimeEncoder, EncodeDuration: zapcore.SecondsDurationEncoder, EncodeCaller: zapcore.ShortCallerEncoder, }, OutputPaths: []string{"stdout"}, ErrorOutputPaths: []string{"stderr"}, }
logger, _ := config.Build()
defer logger.Sync()
logger.Info("hello!", zap.String("name", "xiaomin"), zap.Int("age", 20)) }
|
记录调用信息
Zap 提供了 AddCaller()
方法,可以记录调用者的信息,包括文件名、函数名、行号。
前提是必须设置 CallerKey
字段,因此 NewExample() 不能输出调用者信息。
1 2 3 4 5 6
| func main() { logger, _ := zap.NewProduction(zap.AddCaller()) defer logger.Sync()
logger.Info("hello world") }
|
输出结果:
1
| {"level":"info","ts":1587740198.9508286,"caller":"caller/main.go:9","msg":"hello world"}
|
有时我们稍微封装了一下记录日志的方法,但是我们希望输出的文件名和行号是调用封装函数的位置,这时可以使用 zap.AddCallerSkip(skip int)
向上跳过:
1 2 3 4 5 6 7 8 9 10 11
| func Output(msg string, fields ...zap.Field) { zap.L().Info(msg, fields...) }
func main() { logger, _ := zap.NewProduction(zap.AddCaller(), zap.AddCallerSkip(1)) defer logger.Sync()
zap.ReplaceGlobals(logger) Output("hello world") }
|
输出结果:
1
| {"level":"info","ts":1587740501.5592482,"caller":"skip/main.go:15","msg":"hello world"}
|
记录堆栈信息
Zap 提供了 AddStacktrace()
方法,可以记录堆栈信息。
前提是必须设置 StacktraceKey
字段,因此 NewExample() 不能输出堆栈信息。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
| func f1() { f2("hello world") }
func f2(msg string, fields ...zap.Field) { zap.L().Warn(msg, fields...) }
func main() { logger, _ := zap.NewProduction(zap.AddStacktrace(zapcore.WarnLevel)) defer logger.Sync()
zap.ReplaceGlobals(logger)
f1() }
|
将 zapcore.WarnLevel
传入 AddStacktrace()
,之后 Warn 和 Error 级别的日志会输出堆栈,Debug 和 Info 则不会。
运行结果:
1
| {"level":"warn","ts":1729783529.2137501,"caller":"ConfessionWallServer/main.go:13","msg":"hello world","stacktrace":"main.f2\n\tD:/07_Github/ConfessionWallServer/main.go:13\nmain.f1\n\tD:/07_Github/ConfessionWallServer/main.go:9\nmain.main\n\tD:/07_Github/ConfessionWallServer/main.go:22\nruntime.main\n\tC:/Program Files/Go/src/runtime/proc.go:272"}
|
将 stacktrace
单独拉出来看:
1 2 3 4 5 6 7 8
| main.f2 D:/07_Github/ConfessionWallServer/main.go:13 main.f1 D:/07_Github/ConfessionWallServer/main.go:9 main.main D:/07_Github/ConfessionWallServer/main.go:22 runtime.main C:/Program Files/Go/src/runtime/proc.go:272
|
我们很清楚地看到调用路径。
预设日志字段
有些时候我们需要在每条日志中添加一些预设字段,可以通过 zap.Fields(fs ...Field)
来实现。
1 2 3 4 5 6 7 8
| func main() { logger := zap.NewExample(zap.Fields( zap.Int("serverId", 114514), zap.String("serverName", "ConfessionWallServer"), ))
logger.Info("hello world") }
|
输出:
1
| {"level":"info","msg":"hello world","serverId":114514,"serverName":"ConfessionWallServer"}
|
设置全局 Logger
为了方便使用,Zap 提供了 ReplaceGlobals(logger *Logger)
,可以将一个 Logger 设置为全局的 Logger。
zap.L()
获取全局 Logger,zap.S()
获取全局 Sugared Logger。
1 2 3 4 5 6 7 8
| func main() { logger := zap.NewExample() defer logger.Sync()
zap.ReplaceGlobals(logger) zap.L().Info("Global Logger") zap.S().Infof("Global %s", "Sugared Logger") }
|
输出:
1 2
| {"level":"info","msg":"Global Logger"} {"level":"info","msg":"Global Sugared Logger"}
|
注意:若没有设置全局 Logger,则调用 zap.L()
和 zap.S()
并不会有日志输出。
实现日志滚动
lumberjack 是一个高效且易用的日志滚动包,它允许开发人员将日志写入自动滚动的文件中,从而使日志管理更加简单。
1
| go get -u github.com/natefinch/lumberjack
|
lumberjack 提供了一个滚动记录器 logger,它实现了 io.Writer
和 io.Closer
接口,我们可以使用 zapcore.AddSync
来将其与 Zap 结合使用。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
| func main() { lumberJackLogger := &lumberjack.Logger{ Filename: filename, MaxSize: maxsize, MaxAge: maxAge, MaxBackups: maxBackup, Compress: false, }
core := zapcore.NewCore( zapcore.NewJSONEncoder(zap.NewDevelopmentEncoderConfig()), zapcore.AddSync(lumberJackLogger), zapcore.DebugLevel, )
logger := zap.New(core) }
|
更多高级用法请参考 Github 项目文档,在此不多赘述。