golang 日志库ZAP[uber-go zap]
1. 简要说明
zap 是 uber 开源的 Go 高性能日志库,支持不同的日志级别, 能够打印基本信息等,但不支持日志的分割,这里我们可以使用 lumberjack 也是 zap 官方推荐用于日志分割,结合这两个库我们就可以实现以下功能的日志机制:
能够将事件记录到文件中,而不是应用程序控制台;日志切割能够根据文件大小、时间或间隔等来切割日志文件;支持不同的日志级别,例如 DEBUG , INFO , WARN , ERROR 等;能够打印基本信息,如调用文件、函数名和行号,日志时间等;官网地址:https://github.com/uber-go/zap
2. 下载安装
1
2
3
4
5
6
7
8
9
|
// 初始化go mod 通过mod管理扩展包
go mod init zaplog
//使用下面命令安装
go get -u go.uber.org/zap
//如果下载失败,则使用以下命令重新下载安装
go get github.com/uber-go/zap
//下载安装成功后还有如下提示:
package github.com/uber-go/zap: code in directory
src/github.com/uber-go/zap expects import "go.uber.org/zap"
|
3. 配置 zap Logger
zap 提供了两种类型的日志记录器—和 Logger 和 Sugared Logger 。两者之间的区别是:
- 在每一微秒和每一次内存分配都很重要的上下文中,使用Logger。它甚至比SugaredLogger更快,内存分配次数也更少,但它只支持强类型的结构化日志记录。
- 在性能很好但不是很关键的上下文中,使用SugaredLogger。它比其他结构化日志记录包快 4-10 倍,并且支持结构化和 printf 风格的日志记录。
- 所以一般场景下我们使用 Sugared Logger 就足够了。
3.1 Logger
- 通过调用zap.NewProduction()/zap.NewDevelopment()或者zap.NewExample()创建一个 Logger 。
- 上面的每一个函数都将创建一个 logger 。唯一的区别在于它将记录的信息不同。例如 production logger 默认记录调用函数信息、日期和时间等。
- 通过 Logger 调用 INFO 、 ERROR 等。
- 默认情况下日志都会打印到应用程序的 console 界面。
3.1.1 NewExample
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
|
//代码示例:
package main
import (
"go.uber.org/zap"
)
func main() {
log := zap.NewExample()
log.Debug("this is debug message")
log.Info("this is info message")
log.Info("this is info message with fileds",
zap.Int("age", 24), zap.String("agender", "man"))
log.Warn("this is warn message")
log.Error("this is error message")
log.Panic("this is panic message")
}
//输出结果:
{"level":"debug","msg":"this is debug message"}
{"level":"info","msg":"this is info message"}
{"level":"info","msg":"this is info message with fileds","age":24,"agender":"man"}
{"level":"warn","msg":"this is warn message"}
{"level":"error","msg":"this is error message"}
{"level":"panic","msg":"this is panic message"}
panic: this is panic message
|
3.1.2 NewDevelopment
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
|
//代码示例:
func main() {
log, _ := zap.NewDevelopment()
log.Debug("this is debug message")
log.Info("this is info message")
log.Info("this is info message with fileds",
zap.Int("age", 24), zap.String("agender", "man"))
log.Warn("this is warn message")
log.Error("this is error message")
// log.DPanic("This is a DPANIC message")
// log.Panic("this is panic message")
// log.Fatal("This is a FATAL message")
}
//输出结果:
2020-06-12T18:51:11.457+0800 DEBUG task/main.go:9 this is debug message
2020-06-12T18:51:11.457+0800 INFO task/main.go:10 this is info message
2020-06-12T18:51:11.457+0800 INFO task/main.go:11 this is info message with fileds {"age": 24, "agender": "man"}
2020-06-12T18:51:11.457+0800 WARN task/main.go:13 this is warn message
main.main
/home/wohu/GoCode/src/task/main.go:13
runtime.main
/usr/local/go/src/runtime/proc.go:200
2020-06-12T18:51:11.457+0800 ERROR task/main.go:14 this is error message
main.main
/home/wohu/GoCode/src/task/main.go:14
runtime.main
/usr/local/go/src/runtime/proc.go:200
|
3.1.3 NewProduction
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
|
代码示例:
func main() {
log, _ := zap.NewProduction()
log.Debug("this is debug message")
log.Info("this is info message")
log.Info("this is info message with fileds",
zap.Int("age", 24), zap.String("agender", "man"))
log.Warn("this is warn message")
log.Error("this is error message")
// log.DPanic("This is a DPANIC message")
// log.Panic("this is panic message")
// log.Fatal("This is a FATAL message")
}
输出结果:
{"level":"info","ts":1591959367.316352,"caller":"task/main.go:10","msg":"this is info message"}
{"level":"info","ts":1591959367.3163702,"caller":"task/main.go:11","msg":"this is info message with fileds","age":24,"agender":"man"}
{"level":"warn","ts":1591959367.3163917,"caller":"task/main.go:13","msg":"this is warn message"}
{"level":"error","ts":1591959367.3163974,"caller":"task/main.go:14","msg":"this is error message","stacktrace":"main.main\n\t/home/wohu/GoCode/src/task/main.go:14\nruntime.main\n\t/usr/local/go/src/runtime/proc.go:200"}
|
3.1.4 对比总结
Example和Production使用的是 json 格式输出,Development 使用行的形式输出
- Development
- 从警告级别向上打印到堆栈中来跟踪
- 始终打印包/文件/行(方法)
- 在行尾添加任何额外字段作为 json 字符串
- 以大写形式打印级别名称
- 以毫秒为单位打印 ISO8601 格式的时间戳
- Production
- 调试级别消息不记录
- Error , Dpanic 级别的记录,会在堆栈中跟踪文件, Warn 不会
- 始终将调用者添加到文件中
- 以时间戳格式打印日期
- 以小写形式打印级别名称
- 在上面的代码中,我们首先创建了一个 Logger ,然后使用 Info / Error 等 Logger 方法记录消息。
日志记录器方法的语法是这样的:
1
|
func (log *Logger) MethodXXX(msg string, fields ...Field)
|
其中 MethodXXX 是一个可变参数函数,可以是 Info / Error / Debug / Panic 等。每个方法都接受一个消息字符串和任意数量的 zapcore.Field 长参数。
每个 zapcore.Field 其实就是一组键值对参数。
3.2 Sugared Logger
默认的 zap 记录器需要结构化标签,即对每个标签,需要使用特定值类型的函数。
1
|
log.Info("this is info message with fileds",zap.Int("age", 24), zap.String("agender", "man"))
|
虽然会显的很长,但是对性能要求较高的话,这是最快的选择。也可以使用suger logger, 它基于 printf 分割的反射类型检测,提供更简单的语法来添加混合类型的标签。
我们使用 Sugared Logger 来实现相同的功能。
- 大部分的实现基本都相同;
- 惟一的区别是,我们通过调用主 logger 的.Sugar()方法来获取一个SugaredLogger;
- 然后使用SugaredLogger以printf格式记录语句;
1
2
3
4
5
6
7
8
9
10
11
12
13
|
func main() {
logger, _ := zap.NewDevelopment()
slogger := logger.Sugar()
slogger.Debugf("debug message age is %d, agender is %s", 19, "man")
slogger.Info("Info() uses sprint")
slogger.Infof("Infof() uses %s", "sprintf")
slogger.Infow("Infow() allows tags", "name", "Legolas", "type", 1)
}
//输出结果:
2020-06-12T19:23:54.184+0800 DEBUG task/main.go:11 debug message age is 19, agender is man
2020-06-12T19:23:54.185+0800 INFO task/main.go:12 Info() uses sprint
2020-06-12T19:23:54.185+0800 INFO task/main.go:13 Infof() uses sprintf
2020-06-12T19:23:54.185+0800 INFO task/main.go:14 Infow() allows tags {"name": "Legolas", "type": 1}
|
如果需要,可以随时使用记录器上的 .Desugar() 方法从 sugar logger 切换到标准记录器。
1
2
3
4
|
log := slogger.Desugar()
log.Info("After Desugar; INFO message")
log.Warn("After Desugar; WARN message")
log.Error("After Desugar; ERROR message")
|
4. 将日志写入文件
我们将使用zap.New(…)方法来手动传递所有配置,而不是使用像zap.NewProduction()这样的预置方法来创建 logger 。
1
|
func New(core zapcore.Core, options ...Option) *Logger
|
zapcore.Core需要三个配置——Encoder,WriteSyncer,LogLevel。
Encoder :编码器(如何写入日志)。我们将使用开箱即用的 NewConsoleEncoder() ,并使用预先设置的 ProductionEncoderConfig() 。
1
|
zapcore.NewConsoleEncoder(zap.NewProductionEncoderConfig())
|
WriterSyncer :指定日志将写到哪里去。我们使用 zapcore.AddSync() 函数并且将打开的文件句柄传进去。
1
2
|
file, _ := os.Create("./test.log")
writeSyncer := zapcore.AddSync(file)
|
Log Level :哪种级别的日志将被写入。
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
32
33
34
35
36
37
38
39
40
|
// 代码示例:
package main
import (
"os"
"go.uber.org/zap"
"go.uber.org/zap/zapcore"
)
var sugarLogger *zap.SugaredLogger
func InitLogger() {
encoder := getEncoder()
writeSyncer := getLogWriter()
core := zapcore.NewCore(encoder, writeSyncer, zapcore.DebugLevel)
// zap.AddCaller() 添加将调用函数信息记录到日志中的功能。
logger := zap.New(core, zap.AddCaller())
sugarLogger = logger.Sugar()
}
func getEncoder() zapcore.Encoder {
encoderConfig := zap.NewProductionEncoderConfig()
encoderConfig.EncodeTime = zapcore.ISO8601TimeEncoder // 修改时间编码器
// 在日志文件中使用大写字母记录日志级别
encoderConfig.EncodeLevel = zapcore.CapitalLevelEncoder
// NewConsoleEncoder 打印更符合人们观察的方式
return zapcore.NewConsoleEncoder(encoderConfig)
}
func getLogWriter() zapcore.WriteSyncer {
file, _ := os.Create("./test.log")
return zapcore.AddSync(file)
}
func main() {
InitLogger()
sugarLogger.Info("this is info message")
sugarLogger.Infof("this is %s, %d", "aaa", 1234)
sugarLogger.Error("this is error message")
sugarLogger.Info("this is info message")
}
// 输出日志文件:
2020-06-16T09:01:06.192+0800 INFO task/main.go:40 this is info message
2020-06-16T09:01:06.192+0800 INFO task/main.go:41 this is aaa, 1234
2020-06-16T09:01:06.192+0800 ERROR task/main.go:42 this is error message
2020-06-16T09:01:06.192+0800 INFO task/main.go:43 this is info message
|
|