Skip to content

Uber开源-高性能日志库Zap

Zapuber开源的日志库,支持日志级别分级 、结构化记录,对性能和内存分配做了极致的优化。目前 Star 12.8 源码地址: https://github.com/uber-go/zap

官方性能测试图

Log a message and 10 fields:

Package Time Time % to zap Objects Allocated
⚡ zap 2900 ns/op +0% 5 allocs/op
⚡ zap (sugared) 3475 ns/op +20% 10 allocs/op
zerolog 10639 ns/op +267% 32 allocs/op
go-kit 14434 ns/op +398% 59 allocs/op
logrus 17104 ns/op +490% 81 allocs/op
apex/log 32424 ns/op +1018% 66 allocs/op
log15 33579 ns/op +1058% 76 allocs/op

Log a message with a logger that already has 10 fields of context:

Package Time Time % to zap Objects Allocated
⚡ zap 373 ns/op +0% 0 allocs/op
⚡ zap (sugared) 452 ns/op +21% 1 allocs/op
zerolog 288 ns/op -23% 0 allocs/op
go-kit 11785 ns/op +3060% 58 allocs/op
logrus 19629 ns/op +5162% 70 allocs/op
log15 21866 ns/op +5762% 72 allocs/op
apex/log 30890 ns/op +8182% 55 allocs/op

Log a static string, without any context or printf-style templating:

Package Time Time % to zap Objects Allocated
⚡ zap 381 ns/op +0% 0 allocs/op
⚡ zap (sugared) 410 ns/op +8% 1 allocs/op
zerolog 369 ns/op -3% 0 allocs/op
standard library 385 ns/op +1% 2 allocs/op
go-kit 606 ns/op +59% 11 allocs/op
logrus 1730 ns/op +354% 25 allocs/op
apex/log 1998 ns/op +424% 7 allocs/op
log15 4546 ns/op +1093% 22 allocs/op

2.安装

go get -u go.uber.org/zap

3.日志记录器

Zap提供了两种类型的日志记录器: SugaredLoggerLogger,两者对比如下:

SugaredLogger : 在性能很好但不是很关键的上下文中使用,它比其他结构化日志记录包快4-10倍,并且支持结构化和printf风格的日志记录。与 log15go-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
 logger := zap.NewExample()
 // 使用defer logger.Sync()将缓存同步到文件中。
 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-18T16:37:01.703+0800 INFO test/zap_test.go:31 NewDevelopment {"name": "张三", "age": 18}
--- PASS: TestCreateLogger (0.00s)
PASS

zap底层 API 可以设置缓存,所以一般使用defer logger.Sync()将缓存同步到文件中

4.3 总结

  1. 使用NewProduction()记录日志,默认会记录调用函数信息、日期和时间。
  2. NewExampleNewProduction() 默认都是使用json格式记录日志,而NewDevelopment不是。
  3. 默认情况下日志都会打印到应用程序的控制台界面。
  4. 记录日志时,尽量调用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日志")
 // 下面两个都会中断程序
 //logger.Fatal("这是Fatal日志")
 //logger.Panic("这是Panic日志")
}
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.代码示例
// 使用Sugar记录器
func TestRecordLogWithSuage(t *testing.T) {
 // 初始化记录器
 logger := zap.NewExample()
 // 把日志记录器转成Sugar
 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})
 // 下面两个都会中断程序
 //sugarLogger.Fatal("这是Fatal日志")
 //sugarLogger.Panic("这是Panic日志")
}
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)
 // 设置日志输出格式为JSON (参数复用NewDevelopmentEncoderConfig)
 encoder := zapcore.NewJSONEncoder(zap.NewDevelopmentEncoderConfig())
 // 返回zapcore.Core,并指定记录zap.DebugLevel级别及以上日志
 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)
 // 设置日志输出格式为JSON (参数复用NewDevelopmentEncoderConfig)
 encoder := zapcore.NewJSONEncoder(zap.NewDevelopmentEncoderConfig())
 // 返回zapcore.Core
 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

// 获取文件切割和归档配置信息
func getLumberjackConfig() zapcore.WriteSyncer {
 lumberjackLogger := &lumberjack.Logger{
  Filename: "./zap.log",//日志文件
  MaxSize: 1,//单文件最大容量(单位MB)
  MaxBackups: 3,//保留旧文件的最大数量
  MaxAge: 1,// 旧文件最多保存几天
  Compress: false, // 是否压缩/归档旧文件
 }
 return zapcore.AddSync(lumberjackLogger)
}

// 测试日志切割和归档
func TestCutAndArchive(t *testing.T) {
 // 设置日志输出格式为JSON (参数复用NewDevelopmentEncoderConfig)
 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))
}