zap
是 uber 开源的高性能日志库,提供了快速的、结构化的、可分级的日志记录.
目前常见的 log 库中,大多使用 json.Marshal
和 fmt.Fprintf
来记录大量 interface{}
,这种基于反射的序列化和字符串格式化会占用大量 CPU 资源并进行许多小的内存的分配,这会使得日志记录变慢,从而影响整体应用程序.
而 zap
采用了上述不同的方法.它包含一个无反射、零分配的 JSON 编码器,并提供了基础 Logger
力求尽可能避免序列化开销和内存分配.
快速开始 zap
库的使用与其他的日志库非常相似.先创建一个 logger
,然后调用各个级别的方法记录日志(Debug/Info/Error/Warn).我们可以通过文档 中提供的一些示例来快速了解该库的使用方式.
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 package mainimport ( "time" "go.uber.org/zap" ) func main () { sugar := zap.NewExample().Sugar() defer sugar.Sync() sugar.Infow("failed to fetch URL" , "url" , "http://example.com" , "attempt" , 3 , "backoff" , time.Second, ) sugar.Infof("failed to fetch URL: %s" , "http://example.com" ) logger := zap.NewExample() defer logger.Sync() logger.Info("failed to fetch URL" , zap.String("url" , "http://example.com" ), zap.Int("attempt" , 3 ), zap.Duration("backoff" , time.Second), ) }
如上示例中,使用 zap
包提供的 NewExample()
方法创建 Logger
对象,并打印日志.相比于 sugar
,logger
对象更快,分配的资源也少的多,但是它只支持强类型,结构化的日志记录.
另外,zap
提供了几个快速创建 logger
的方法: zap.NewExample()
,zap.NewDevelopment()
,zap.NewProduction()
以及高度定制化的 zap.New()
.在使用前 3 个 logger 时, zap 会使用一些预定义的配置.详情可通过 Github 查看.
option 在通过以上方法创建 logger
对象时,zap 支持传入一些 Option
对象来调整 logger 对象的内部属性,从而定制 Logger 对象的行为.zap
库提供了丰富的构建 Option
对象的方法供我们选择.常用的如
打印日志的行号 可以通过添加 zap.AddCaller()
返回的 Options
对象对日志添加行号.该 Options
不适用于 zap.NewExample()
返回的 logger
对象(因为该对象的 Config
没有 CallerKey
属性)
1 2 3 4 5 6 7 8 func main () { logger, _ := zap.NewProduction(zap.AddCaller()) defer logger.Sync() logger.Info("hello world" ) }
再看下面,我们对日志进行了一些封装.
1 2 3 4 5 6 7 8 9 10 11 func log (logger *zap.Logger, msg string ) () { logger.Info(msg) } func main () { logger, _ := zap.NewProduction(zap.AddCaller()) defer logger.Sync() log(logger, "hello world" ) }
此时再看输出的行号就不准确了,因此需要跳过一些调用.zap
包提供了 AddCallerSkip(skip int)
函数跳过一些调用.如下:
1 2 3 4 5 6 7 8 9 10 11 func log (logger *zap.Logger, msg string ) () { logger.Info(msg) } func main () { logger, _ := zap.NewProduction(zap.AddCaller(),zap.AddCallerSkip(1 )) defer logger.Sync() log(logger, "hello world" ) }
此时再看输出日志的调用就准确了.
添加调用栈 有时在出现错误时,我们希望输出代码的调用栈信息.zap
包提供了 AddStackTrace(lvl zapcore.LevelEnabler)
实现这个功能.如下:
1 2 3 4 5 6 7 8 9 10 11 func log (logger *zap.Logger, msg string ) () { logger.Error(msg) } func main () { logger, _ := zap.NewProduction(zap.AddCallerSkip(1 ), zap.AddStacktrace(zapcore.ErrorLevel)) defer logger.Sync() log(logger, "hello world" ) }
Filed logger
对象在记录日志时,需要将 zap 提供的格式化的 Filed
对象来强制定义字段的类型,避免了 interface{}
反射所造成的性能损失.zap
包中提供了很多构造各种类型 Filed
对象的方法.如 Bool
,String
等.zap 还支持形如 Boolp
,Bools
的方法,用于传入该对象的指针与切片对象.如下
1 2 3 func Bool (key string , val bool ) Field func Boolp (key string , val *bool ) Field func Bools (key string , bs []bool ) Field
定制 Logger 我们可以通过调用 NexExample()/NewDevelopment()/NewProduction()
方法创建 zap
内置的默认的 Logger
对象.我们也可以通过自定义 Config
对象,并通过其 Build
方法构建自定义的 Logger
对象.其中,Config
结构体定义如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 type Config struct { Level AtomicLevel `json:"level" yaml:"level"` Development bool `json:"development" yaml:"development"` DisableCaller bool `json:"disableCaller" yaml:"disableCaller"` DisableStacktrace bool `json:"disableStacktrace" yaml:"disableStacktrace"` Encoding string `json:"encoding" yaml:"encoding"` EncoderConfig zapcore.EncoderConfig `json:"encoderConfig" yaml:"encoderConfig"` OutputPaths []string `json:"outputPaths" yaml:"outputPaths"` ErrorOutputPaths []string `json:"errorOutputPaths" yaml:"errorOutputPaths"` InitialFields map [string ]interface {} `json:"initialFields" yaml:"initialFields"` }
可通过定义 Config
后进行构建 Logger
对象
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 func main () { rawJSON := []byte (`{ "level":"debug", "encoding":"json", "outputPaths": ["stdout", "server.log"], "errorOutputPaths": ["stderr"], "initialFields":{"name":"dj"}, "encoderConfig": { "messageKey": "message", "levelKey": "level", "levelEncoder": "lowercase" } }` ) var cfg zap.Config if err := json.Unmarshal(rawJSON, &cfg); err != nil { panic (err) } logger, err := cfg.Build() if err != nil { panic (err) } defer logger.Sync() logger.Info("server start work successfully!" ) }
可以看到标准输出 std 和 server.log 的日志
1 {"level":"info","message":"server start work successfully!","name":"dj"}
搭配标准日志库 如果项目一开始使用的是标准日志库 log
,后面想转为 zap
.我们可以调用 zap.NewStdLog(l *Logger) *log.Logger
返回一个标准的 log.Logger
,内部实际上写入的还是我们之前创建的 zap.Logger
.如下:
1 2 3 4 5 6 7 8 func main () { logger := zap.NewExample() defer logger.Sync() std := zap.NewStdLog(logger) std.Print("standard logger wrapper" ) }
常用函数及方法 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 41 42 43 func NewStdLog (l *Logger) *log .Logger func NewAtomicLevel () AtomicLevel func (lvl AtomicLevel) SetLevel (l zapcore.Level) func New (core zapcore.Core, options ...Option) *Logger func NewDevelopment (options ...Option) (*Logger, error) func NewExample (options ...Option) *Logger func NewProduction (options ...Option) (*Logger, error) func (log *Logger) Sync () error func (log *Logger) With (fields ...Field) *Logger func (log *Logger) WithOptions (opts ...Option) *Logger func (log *Logger) Sugar () *SugaredLogger func (log *Logger) Info (msg string , fields ...Field) func AddCaller () Option func WithCaller (enabled bool ) Option func AddCallerSkip (skip int ) Option func AddStacktrace (lvl zapcore.LevelEnabler) Option func Bool (key string , val bool ) Field func Boolp (key string , val *bool ) Field func Bools (key string , bs []bool ) Field
zap 高性能的秘诀
避免 GC: 对象复用
内建的 Encoder: 避免反射
使用写时复制机制,避免竞态
具体可参见 深度 | 从Go高性能日志库zap看如何实现高性能Go组件
参考: