Go语言高性能日志库zap使用

Zap简介

zap是一个非常快的、结构化的,分日志级别的Go日志库。

Github仓库地址:https://github.com/uber-go/zap

Zap性能

记录一条含有10个字段的日志

Package Time Time % to zap Objects Allocated
⚡️ zap 862 ns/op +0% 5 allocs/op
⚡️ zap (sugared) 1250 ns/op +45% 11 allocs/op
zerolog 4021 ns/op +366% 76 allocs/op
go-kit 4542 ns/op +427% 105 allocs/op
apex/log 26785 ns/op +3007% 115 allocs/op
logrus 29501 ns/op +3322% 125 allocs/op
log15 29906 ns/op +3369% 122 allocs/op

使用已有10个上下文字段的记录器记录消息

Package Time Time % to zap Objects Allocated
⚡️ zap 126 ns/op +0% 0 allocs/op
⚡️ zap (sugared) 187 ns/op +48% 2 allocs/op
zerolog 88 ns/op -30% 0 allocs/op
go-kit 5087 ns/op +3937% 103 allocs/op
log15 18548 ns/op +14621% 73 allocs/op
apex/log 26012 ns/op +20544% 104 allocs/op
logrus 27236 ns/op +21516% 113 allocs/op

记录一个静态字符串,没有任何上下文或printf样式的模板

Package Time Time % to zap Objects Allocated
⚡️ zap 118 ns/op +0% 0 allocs/op
⚡️ zap (sugared) 191 ns/op +62% 2 allocs/op
zerolog 93 ns/op -21% 0 allocs/op
go-kit 280 ns/op +137% 11 allocs/op
standard library 499 ns/op +323% 2 allocs/op
apex/log 1990 ns/op +1586% 10 allocs/op
logrus 3129 ns/op +2552% 24 allocs/op
log15 3887 ns/op +3194% 23 allocs/op

Zap安装

1go get -u go.uber.org/zap

快速入门

配置Zap Logger

Zap提供了两种类型的日志记录器—SugaredLoggerLogger

在性能很好但不是很关键的上下文中,使用SugaredLogger。它比其他结构化日志记录包快4-10倍,并且支持结构化和printf风格的日志记录。

在每一微秒和每一次内存分配都很重要的上下文中,使用Logger。它甚至比SugaredLogger更快,内存分配次数也更少,但它只支持强类型的结构化日志记录。

Logger

通过调用zap.NewProduction()zap.NewDevelopment()、`zap.Example()其中一个创建Logger,3中模式日志格式有所不同。默认情况下日志都输出在控制台下。

 1package main
 2
 3import (
 4	"go.uber.org/zap"
 5)
 6
 7func main() {
 8	logger, _ := zap.NewProduction()
 9  //Sync刷新任何缓冲的日志条目。
10	defer logger.Sync()
11	logger.Info("this is a test log")
12	logger.Warn("this is a test log")
13	logger.Error("this is a test log")
14	logger.Fatal("this is a test log")
15
16}

输出结果

1{"level":"info","ts":1583479612.40354,"caller":"log/main.go:10","msg":"this is a test log"}
2{"level":"warn","ts":1583479612.403604,"caller":"log/main.go:11","msg":"this is a test log"}
3{"level":"error","ts":1583479612.4036129,"caller":"log/main.go:12","msg":"this is a test log","stacktrace":"main.main\n\t/Users/jerry/go/src/utils/log/main.go:12\nruntime.main\n\t/usr/local/go/src/runtime/proc.go:203"}

SugaredLogger

 1package main
 2
 3import (
 4	"go.uber.org/zap"
 5)
 6
 7func main() {
 8	logger, _ := zap.NewProduction()
 9  //Sync刷新任何缓冲的日志条目。
10	defer logger.Sync()
11	sugar := logger.Sugar()
12	sugar.Info("this is a test log")
13	sugar.Warn("this is a test log")
14	sugar.Error("this is a test log")
15	sugar.Fatal("this is a test log")
16
17}

输出结果

1{"level":"info","ts":1583479949.807585,"caller":"log/main.go:11","msg":"this is a test log"}
2{"level":"warn","ts":1583479949.807671,"caller":"log/main.go:12","msg":"this is a test log"}
3{"level":"error","ts":1583479949.807691,"caller":"log/main.go:13","msg":"this is a test log","stacktrace":"main.main\n\t/Users/jerry/go/src/utils/log/main.go:13\nruntime.m
4ain\n\t/usr/local/go/src/runtime/proc.go:203"}
5{"level":"fatal","ts":1583479949.8077528,"caller":"log/main.go:14","msg":"this is a test log","stacktrace":"main.main\n\t/Users/jerry/go/src/utils/log/main.go:14\nruntime.
6main\n\t/usr/local/go/src/runtime/proc.go:203"}
7exit status 1

大体上是差不多的,唯一的区别就是更加了格式化输出功能,像PrintPrintf

自定义Logger

很多时候我们的应用程序在都是后台跑着的,日志在控制台输出只是调试的时候用的到,我们一般都是将日志记录到本地的文件当中。

自带的日志库

 1package main
 2
 3import (
 4	"log"
 5	"os"
 6)
 7
 8func main() {
 9	fileName := "my.log"
10	logFile, err := os.Create(fileName)
11	if err != nil {
12		log.Fatalln("open file failed")
13	}
14	defer logFile.Close()
15	debugLog := log.New(logFile, "[Info]", log.Llongfile)
16	debugLog.Println("A Info message here")
17	debugLog.SetPrefix("[Debug]")
18	debugLog.Println("A Debug Message here")
19
20}

输出结果

1[Info]/Users/jerry/go/src/utils/log/main.go:16: A Info message here
2[Debug]/Users/jerry/go/src/utils/log/main.go:18: A Debug Message here

使用Zap日志库写入到文件

 1package main
 2
 3import (
 4	"os"
 5
 6	"go.uber.org/zap"
 7	"go.uber.org/zap/zapcore"
 8)
 9
10func initLogger(logpath string, loglevel string) *zap.Logger {
11	file, _ := os.Create(logpath)
12	w := zapcore.AddSync(file)
13	var level zapcore.Level
14	switch loglevel {
15	case "debug":
16		level = zap.DebugLevel
17	case "info":
18		level = zap.InfoLevel
19	case "error":
20		level = zap.ErrorLevel
21	default:
22		level = zap.InfoLevel
23	}
24	encoderConfig := zap.NewProductionEncoderConfig()
25	encoderConfig.EncodeTime = zapcore.ISO8601TimeEncoder
26	core := zapcore.NewCore(
27		zapcore.NewConsoleEncoder(encoderConfig),
28		w,
29		level,
30	)
31	logger := zap.New(core)
32	logger.Info("DefaultLogger init success")
33	return logger
34}
35
36func main() {
37
38	logger := initLogger("my.log", "info")
39	logger.Info("this is a test log", zap.Int("line", 47))
40	logger.Warn("this is a test log", zap.Int("line", 47))
41
42}

输出结果

1cat my.log 
22020-03-06T16:00:25.380+0800    info    DefaultLogger init success
32020-03-06T16:00:25.380+0800    info    this is a test log      {"line": 47}
42020-03-06T16:00:25.380+0800    warn    this is a test log      {"line": 47}

已经记录到文件中了

将日志进行序列化文件

lumberjack:支持文件按大小或者时间归档 GitHub地址:https://github.com/natefinch/lumberjack

安装lumberjack

1go get github.com/natefinch/lumberjack

使用lumberjack

使用方法也很简单,在initlog调用即可

 1package main
 2
 3import (
 4  "github.com/natefinch/lumberjack"
 5	"go.uber.org/zap"
 6	"go.uber.org/zap/zapcore"
 7)
 8
 9func initLogger(logpath string, loglevel string) *zap.Logger {
10	hook := lumberjack.Logger{
11		Filename:   logpath, // ⽇志⽂件路径
12		MaxSize:    1,       // megabytes
13		MaxBackups: 3,       // 最多保留3个备份
14		MaxAge:     7,       //days
15		Compress:   true,    // 是否压缩 disabled by default
16	}
17	w := zapcore.AddSync(&hook)
18	var level zapcore.Level
19	switch loglevel {
20	case "debug":
21		level = zap.DebugLevel
22	case "info":
23		level = zap.InfoLevel
24	case "error":
25		level = zap.ErrorLevel
26	default:
27		level = zap.InfoLevel
28	}
29	encoderConfig := zap.NewProductionEncoderConfig()
30	encoderConfig.EncodeTime = zapcore.ISO8601TimeEncoder
31	core := zapcore.NewCore(
32		zapcore.NewConsoleEncoder(encoderConfig),
33		w,
34		level,
35	)
36	logger := zap.New(core)
37	logger.Info("DefaultLogger init success")
38	return logger
39}
40
41func main() {
42
43  logger := initLogger("my.log", "info")
44	for i := 0; i <= 500000; i++ {
45		logger.Info("this is a test log", zap.Int("line", 47))
46		logger.Warn("this is a test log", zap.Int("line", 47))
47	}
48
49}

运行结果跟上面一样,只是到达指定大小之后会对文件进行切割。为演示方便我设置成1M大小进行切割

 1(☸ docker-desktop:default)➜  log ls -lh
 2total 18256
 3-rw-r--r--  1 jerry  staff   117B  3  6 16:13 go.mod
 4-rw-r--r--  1 jerry  staff   4.5K  3  6 16:13 go.sum
 5-rwxr-xr-x  1 jerry  staff   7.2M  3  6 16:15 log
 6-rw-r--r--  1 jerry  staff   1.2K  3  6 16:15 main.go
 7-rw-r--r--  1 jerry  staff    48K  3  6 16:16 my-2020-03-06T08-16-43.417.log.gz
 8-rw-r--r--  1 jerry  staff   1.0M  3  6 16:17 my.log
 9(☸ docker-desktop:default)➜  log ls -lh
10total 16352
11-rw-r--r--  1 jerry  staff   117B  3  6 16:13 go.mod
12-rw-r--r--  1 jerry  staff   4.5K  3  6 16:13 go.sum
13-rwxr-xr-x  1 jerry  staff   7.2M  3  6 16:15 log
14-rw-r--r--  1 jerry  staff   1.2K  3  6 16:15 main.go
15-rw-r--r--  1 jerry  staff    48K  3  6 16:16 my-2020-03-06T08-16-43.417.log.gz
16-rw-r--r--  1 jerry  staff    48K  3  6 16:17 my-2020-03-06T08-17-29.852.log.gz
17-rw-r--r--  1 jerry  staff    27K  3  6 16:17 my.log

参考