参考资料
- 深度 | 从Go高性能日志库zap看如何实现高性能Go组件
- https://cloud.tencent.com/developer/article/1645126
- https://www.liwenzhou.com/posts/Go/zap/
日志组件
- 日志组件对于计算资源的消耗十分巨大,会导致整个服务成本居高不下。
- 日志作为代码行为的记录,是程序执行逻辑和异常最直接的反馈。
- 日志中同样蕴含了大量有价值可以被挖掘的信息
日志需求
- 日志记录:将事件记录到文件而非控制台
- 日志切割:日志能按文件大小、时间、间隔等切割
- 日志级别:支持不同日志级别
- 日志答应:能打印基本信息,如调用文件、函数名称、行号、时间等
设计思路
- 输入数据应该被良好的组织且易于编码,同时需要具有高效的空间利用率。无论是
formator
方式还是key-value
方式,本质上都是对输入数据的组织形式。格式化数据往往有利于后续分析和处理,比如JSON就是一种易用的日志格式。 - 日志能够具有不同级别,基本的日志级别种必不可少的如debug/info/warning/error/fatal。除了针对每条日志的级别,还需要日志组件的级别,用来屏蔽某些级别的日志。
- 组织日志的输出流,首先需要一个高效地编码器来格式化后,再将经过过滤的日志输出。
- 设计一套简单易用的接口,需要包含logger对象和config。
解决方案
-
uber-go/zap
高性能但不支持日志切割 -
natefinch/lumberjack
单独实现日志切割
uber-go/zap
支持不同日志级别打印信息但不支持日志分割,日志分割官方建议使用natefinch/lumberjack
包,结合这两个库来实现日志功能。
uber-go/zap
Zap是Uber开源的Go高性能日志库,其优势在于实时写结构化日志(Structured Logging)到文件具有很好的性能。结构化日志实际上是指不直接输出日志文本,才是采用JSON或其它编码方式使日志内容结构化,方便后续分析和查找,比如采用ELK(Elasticsearch Logstatash Kibana)。
日志输出中有两种方式字段和消息,字段用来结构化输出错误相关的上下文环境,消息简明扼要的阐述错误本身。Zap跟Logrus以及标准库中的log
类似都提倡采用结构化的日志格式,而不是将所有消息放到消息体中。
安装包
$ go get -uv go.uber.org/zap
$ go get -uv github.com/natefinch/lumberjack
设计结构

核心库 | 描述 |
---|---|
zapcore | 定义低级接口(zap所依赖的核心接口),接口的实现和依赖分离以最大化降低代码之间的耦合度,可直接针对zapcore封装以便于二开。 |
zapgrpc | grpc logger的封装实现,便于grpc 用户添加log。 |
internal/bufferpool & buffer | buffer提供了append field功能,通过append将基础类型添加到buffer中,同时使用sync.Pool提供的对象池技术通过对象复用来减少内存分配。 |
internal/ztest & zaptest | zaptest warp提供了mock接口便于测试,zap代码整体单元测试覆盖到了98.9%。 |
核心库 | 描述 |
---|---|
zapcore | Zap库的核心逻辑,包括field 的管理、level 的判断、encode 编码日志、输出日志等。 |
logger | Zap库的接口层,包含Log 对象、 Level对象、 Field对象、 config`等基本对象 |
encoder | JSON等编码方式的实现 |
utils | 工具库 |
日志记录器
- Zap的基本用法是首先创建一个全局的Logger日志记录器实例,然后在需要使用的位置作为参数传入。
- Zap自身提供了全局的Logger是
zap.S()
和zap.L()
, - 也可采用
zap.ReplaceGlobals()
将全局的Logger替换为客制化的Logger。
type Logger struct {
core zapcore.Core
development bool
name string
errorOutput zapcore.WriteSyncer
addCaller bool
addStack zapcore.LevelEnabler
callerSkip int
}
Zap提供了两种类型的日志记录器:zap.Logger
和zap.SugaredLogger
zap.Logger
zap.Logger
在每一微秒和每一次内存分配重要的上下文中使用,比zap.SugaredLogger
更快,内存分配次数更少,但只支持强类型的结构化日志。
默认的zap.Logger
日志记录器需要结构化标签,对每个标签需使用特定值类型的函数。
创建方式
func New(core zapcore.Core, options ...Option) *Logger
func NewNop() *Logger
func NewProduction(options ...Option) (*Logger, error)
func NewDevelopment(options ...Option) (*Logger, error)
func NewExample(options ...Option) *Logger
不同创建方式之间的区别在于记录的信息不同,例如zap.NewProduction()
默认会及记录调用函数的信息以及日期时间等。
-
zap.NewExample()
和zap.NewProduction()
使用JSON格式输出 -
zap.Development()
使用行的形式输出
zap.Example
logger := zap.NewExample()
logger.Info("message") // {"level":"info","msg":"message"}
logger.Debug("message") // {"level":"debug","msg":"message"}
logger.Warn("message") // {"level":"warn","msg":"message"}
logger.Error("message") // {"level":"error","msg":"message"}
logger.Panic("message") // {"level":"panic","msg":"message"} panic: message
zap.NewProduction
logger, err := zap.NewProduction()
if err != nil {
log.Println(err)
}
logger.Info("message") // {"level":"info","ts":1640526778.9686759,"caller":"test/main.go:63","msg":"message"}
logger.Debug("message") // 无
logger.Warn("message") // {"level":"warn","ts":1640526778.9686759,"caller":"test/main.go:65","msg":"message"}
logger.Error("message") // {"level":"error","ts":1640526778.9686759,"caller":"test/main.go:66","msg":"message","stacktrace":"main.main\n\tF:/Go/project/test/main.go:66\nruntime.main\n\tF:/Go/root/src/runtime/proc.go:255"}
logger.Panic("message") // {"level":"panic","ts":1640526778.9686759,"caller":"test/main.go:67","msg":"message","stacktrace":"main.main\n\tF:/Go/project/test/main.go:67\nruntime.main\n\tF:/Go/root/src/runtime/proc.go:255"}
- 不记录调用级别消息
- 始终会将调用者添加到日志文件中
- 以时间戳格式打印日期
- 以小写形式打印级别名称
- ERROR/DPANIC级别的记录会在堆栈中跟踪文件,WARN则不会。
zap.Development
logger, err := zap.NewDevelopment()
if err != nil {
log.Println(err)
}
logger.Info("message") // 2021-12-26T21:55:56.822+0800 INFO test/main.go:63 message
logger.Debug("message") // 2021-12-26T21:55:56.848+0800 DEBUG test/main.go:64 message
logger.Warn("message") // 2021-12-26T21:55:56.848+0800 WARN test/main.go:65 message
logger.Error("message") // 2021-12-26T21:55:56.848+0800 ERROR test/main.go:66 message
logger.Panic("message") // 2021-12-26T21:55:56.848+0800 PANIC test/main.go:67 message
- 从警告级别向上打印到堆栈中来追踪
- 始终打印包/文件/行(方法)
- 行尾会添加额外字段作为JSON字符串
- 以大写形式打印级别名称
- 以毫秒位单位打印ISO8601格式的时间戳
zap.New
若需定制日志记录器则需使用zap.New()
手动传递自定义的配置来创建zap.Logger
实例。
func New(core zapcore.Core, options ...Option) *Logger {
if core == nil {
return NewNop()
}
log := &Logger{
core: core,
errorOutput: zapcore.Lock(os.Stderr),
addStack: zapcore.FatalLevel + 1,
}
return log.WithOptions(options...)
}
zapcore.Core
zapcore.Core
定义低级接口(zap所依赖的核心接口),接口的实现和依赖分离以最大化降低代码之间的耦合度,可直接封装以便于二开。zapcore.Core
是Zap库的核心逻辑,包括field
的管理、level
的判断、encode
编码日志、输出日志等。
zapcore.Core
需要三个配置,分别是Encoder
、WriteSyncer
、LogLevel
配置项 | 描述 |
---|---|
Encoder | 编码器,如何写入日志。 |
WriteSyncer | 指定日志写到哪里 |
LogLevel | 哪种级别的日志将被写入 |
例如:自定义日志记录器
- 使用ISO8601时间编码器
- 使用大写字母记录日志级别
- 采用单行打印而非JSON
- 采用文件存储而非屏幕输出
//获取编码器
func getEncoder() zapcore.Encoder {
c := zap.NewProductionEncoderConfig()
c.EncodeTime = zapcore.ISO8601TimeEncoder //使用ISO8601时间编码器
c.EncodeLevel = zapcore.CapitalLevelEncoder //使用大写字母记录日志级别
e := zapcore.NewConsoleEncoder(c) //打印更符合人类观察的方式
return e
}
//日志保存位置
func getWriteSyncer(filename string) zapcore.WriteSyncer {
fp, _ := os.Create(filename)
return zapcore.AddSync(fp)
}
//自定义日志记录器
func Logger() *zap.Logger {
filename := "./tmp/log/test.log"
//创建日志内核实例
encoder := getEncoder() //日志编码方式
writeSyncer := getWriteSyncer(filename) //日志保存位置
logLevel := zapcore.DebugLevel //日志记录等级
core := zapcore.NewCore(encoder, writeSyncer, logLevel)
//创建日志记录器实例
//zap.AddCaller() 添加将调用函数信息记录到日志中功能
logger := zap.New(core, zap.AddCaller())
//sugar := logger.Sugar()
return logger
}
func main() {
logger := Logger()
logger.Info("message")
logger.Debug("message")
logger.Warn("message")
logger.Error("message")
logger.Panic("message")
}
2021-12-26T22:15:30.521+0800 INFO test/main.go:88 message
2021-12-26T22:15:30.550+0800 DEBUG test/main.go:89 message
2021-12-26T22:15:30.550+0800 WARN test/main.go:90 message
2021-12-26T22:15:30.550+0800 ERROR test/main.go:91 message
2021-12-26T22:15:30.550+0800 PANIC test/main.go:92 message
zap.SugaredLogger
zap.SugaredLogger
适用于性能很好但不是关键的上下文中,比其它结构化日志记录包快4到10倍,支持结构化和printf
风格的日志记录。zap.SugaredLogger
基于printf
分割的反射类型检测,提供更加简单的语法来添加混合类型的标签。
func (log *Logger) Sugar() *SugaredLogger
zap.Logger
默认是不支持格式化输出的,要打印指定值必须采用采用诸如zap.String()
、zap.Int()
等封装方法,因此代码显得很冗长。
zap.SugaredLogger
网友评论