1.介绍 Zap
是uber
开源的日志库,支持日志级别分级 、结构化记录,对性能和内存分配做了极致的优化。目前 Star 12.8 源码地址: https://github.com/uber-go/zap
官方性能测试图
2.安装 go get -u go .uber.org/zap
3.日志记录器 Zap
提供了两种类型的日志记录器: SugaredLogger
和Logger
,两者对比如下:
SugaredLogger
: 在性能很好但不是很关键的上下文中使用,它比其他结构化日志记录包快4-10倍,并且支持结构化和printf
风格的日志记录。与 log15
和 go-kit
一样,SugaredLogger
的结构化日志 api
类型灵活,并接受可变的键值对的数量。
Logger
: 在每一微秒和每一次内存分配都很重要的上下文中,使用Logger
。它甚至比SugaredLogger
更快,内存分配次数也更少,但它只支持强类型的结构化日志记录。
4.创建Logger 4.1 创建Logger几种方式 在Zap
中通过调用zap.NewProduction()
、zap.NewDevelopment()
或者zap.Example()
可创建一个Logger
。
他们创建的Logger
,唯一的区别在于它将记录的信息不同。
使用场景如下:
zap.NewProduction()
: 在生产环境中使用
zap.NewDevelopment()
: 在开发环境中使用
zap.Example()
: 适合用在测试代码中
4.2 使用示例 a.代码 func TestCreateLogger (t *testing.T) { logger := zap.NewExample() defer logger.Sync() logger.Info("NewExample" , zap.String("name" ,"张三" ), zap.Int("age" ,18 ), ) productionLogger, _ := zap.NewProduction() defer productionLogger.Sync() productionLogger.Info("NewProduction" , zap.String("name" ,"张三" ), zap.Int("age" ,18 ), ) devLogger, _ := zap.NewDevelopment() defer devLogger.Sync() devLogger.Info("NewDevelopment" , zap.String("name" ,"张三" ), zap.Int("age" ,18 ), ) }
b. 输出 === RUN TestCreateLogger {"level" :"info" ,"msg" :"NewExample" ,"name" :"张三" ,"age" :18 } {"level" :"info" ,"ts" :1624005421.7035909 ,"caller" :"test/zap_test.go:25" ,"msg" :"NewProduction" ,"name" :"张三" ,"age" :18 }2021 -06 -18 T16:37 :01.703 +0800 INFO test/zap_test.go :31 NewDevelopment {"name" : "张三" , "age" : 18 } --- PASS: TestCreateLogger (0.00 s) PASS
zap
底层 API 可以设置缓存,所以一般使用defer logger.Sync()
将缓存同步到文件中
4.3 总结
使用NewProduction()
记录日志,默认会记录调用函数信息、日期和时间。
NewExample
和NewProduction()
默认都是使用json
格式记录日志,而NewDevelopment
不是。
默认情况下日志都会打印到应用程序的控制台界面。
记录日志时,尽量调用zap.T(key,val)
对应的类型方法,这也是zap
高性能原因的一部分。
5.记录日志 5.1 使用默认记录器(Logger
) a.代码示例 func TestRecordLogWithDefault (t *testing.T) { logger := zap.NewExample() defer logger.Sync() logger.Debug("这是debug日志" ) logger.Debug("这是debug日志" ,zap.String("name" ,"张三" )) logger.Info("这是info日志" ,zap.Int("age" ,18 )) logger.Error("这是error日志" ,zap.Int("line" ,130 ),zap.Error(fmt.Errorf("错误示例" ))) logger.Warn("这是Warn日志" ) }
b.输出 === RUN TestRecordLogWithDefault {"level" :"debug" ,"msg" :"这是debug日志" } {"level" :"debug" ,"msg" :"这是debug日志" ,"name" :"张三" } {"level" :"info" ,"msg" :"这是info日志" ,"age" :18} {"level" :"error" ,"msg" :"这是error日志" ,"line" :130,"error" :"错误示例" } {"level" :"warn" ,"msg" :"这是Warn日志" } --- PASS: TestRecordLogWithDefault (0.00s)
5.2 使用默认记录器(Sugar
) a.代码示例 func TestRecordLogWithSuage (t *testing.T) { logger := zap.NewExample() sugarLogger := logger.Sugar() defer sugarLogger.Sync() sugarLogger.Debug("这是debug日志 " ,zap.String("name" ,"张三" )) sugarLogger.Debugf("这是Debugf日志 name:%s " ,"张三" ) sugarLogger.Info("这是info日志" ,zap.Int("age" ,18 )) sugarLogger.Infof("这是Infof日志 内容:%v" ,map [string ]string {"爱好" :"动漫" }) sugarLogger.Error("这是error日志" ,zap.Int("line" ,130 ),zap.Error(fmt.Errorf("错误示例" ))) sugarLogger.Errorf("这是Errorf日志,错误信息:%s" ,"错误报告!" ) sugarLogger.Warn("这是Warn日志" ) sugarLogger.Warnf("这是Warnf日志 %v" ,[]int {1 ,2 ,4 ,5 }) }
b.输出 === RUN TestRecordLogWithSuage {"level" :"debug" ,"msg" :"这是debug日志 {name 15 0 张三 <nil>}" } {"level" :"debug" ,"msg" :"这是Debugf日志 name:张三 " } {"level" :"info" ,"msg" :"这是info日志{age 11 18 <nil>}" } {"level" :"info" ,"msg" :"这是Infof日志 内容:map[爱好:动漫]" } {"level" :"error" ,"msg" :"这是error日志{line 11 130 <nil>} {error 26 0 错误示例}" } {"level" :"error" ,"msg" :"这是Errorf日志,错误信息:错误报告!" } {"level" :"warn" ,"msg" :"这是Warn日志" } {"level" :"warn" ,"msg" :"这是Warnf日志 [1 2 4 5]" } --- PASS: TestRecordLogWithSuage (0.00s) PASS
6.定制Logger 除了zap.NewProduction()
、zap.NewDevelopment()
、zap.Example()
还可以通过zap.New(...)
创建一个Logger
。
6.1 定制一: 输出到文件 a.代码示例 func Test2File (t *testing.T) { fileHandle, _ := os.Create("./test.log" ) writeFile := zapcore.AddSync(fileHandle) encoder := zapcore.NewJSONEncoder(zap.NewDevelopmentEncoderConfig()) zcore := zapcore.NewCore(encoder, zapcore.Lock(writeFile), zap.DebugLevel) logger := zap.New(zcore) defer logger.Sync() logger.Info("输出日志到文件" , zap.String("name" , "张三" )) }
6.2 定制二: 同时输入文件和控制台 func TestPrintFileAndStd (t *testing.T) { fileHandle, _ := os.Create("./test.log" ) writeFile := zapcore.NewMultiWriteSyncer(fileHandle,os.Stdout) encoder := zapcore.NewJSONEncoder(zap.NewDevelopmentEncoderConfig()) zcore := zapcore.NewCore(encoder, zapcore.Lock(writeFile), zap.DebugLevel) logger := zap.New(zcore) defer logger.Sync() logger.Info("输出日志到文件" , zap.String("name" , "张三" )) }
7.切割日志 Zap
本身不支持文件切割和日志归档,好在开源强大,贡献出Lumberjack ,它是一个Go
包,用于将日志写入滚动文件。
7.1 安装Lumberjack
go get -u github.com/natefinch/lumberjack
7.2 集成到Zap a. 代码示例 func getLumberjackConfig () zapcore.WriteSyncer { lumberjackLogger := &lumberjack.Logger{ Filename: "./zap.log" , MaxSize: 1 , MaxBackups: 3 , MaxAge: 1 , Compress: false , } return zapcore.AddSync(lumberjackLogger) }func TestCutAndArchive (t *testing.T) { encoder := zapcore.NewJSONEncoder(zap.NewDevelopmentEncoderConfig()) core := zapcore.NewCore(encoder, getLumberjackConfig(), zap.DebugLevel) sugarLogger := zap.New(core).Sugar() defer sugarLogger.Sync() sugarLogger.Infof("日志内容:%s" ,strings.Repeat("日志" ,90000 )) }
b. 效果
在代码中设置单文件最大容量为1MB,如上图所示当文件超过1MB(527+527 > 1024)
时,则分割。
c. 没有记录Caller
和Stacktrace
定制后的日志记录,发现没有记录日志打印的行号,以及错误时没有记录Stacktrace
,需要按照以下改进:
zap.New(core,zap.AddCaller(),zap.AddStacktrace(zap.ErrorLevel))
日志效果:
{"level" :"warn" ,"ts" :1627012281.7510028,"msg" :"解析JWT失败" ,"error" :"token contains an invalid number of segments" } {"level" :"WARN" ,"time" :"2021/07/23 - 15:29:43.780" ,"caller" :"middleware/jwt.go:118" ,"msg" :"解析JWT失败" ,"error" :"token contains an invalid number of segments" }