Go 日志记录库:uber-go 的日志操作库 zap 使用
zap 是 uber 开源的一个高性能,结构化,分级记录的日志记录包。
go1.20.2
zap v1.24.0
高性能:zap 对日志输出进行了多项优化以提高它的性能
日志分级:有 Debug,Info,Warn,Error,DPanic,Panic,Fatal 等
日志记录结构化:日志内容记录是结构化的,比如 json 格式输出
自定义格式:用户可以自定义输出的日志格式
自定义公共字段:用户可以自定义公共字段,大家输出的日志内容就共同拥有了这些字段
调试:可以打印文件名、函数名、行号、日志时间等,便于调试程序
自定义调用栈级别:可以根据日志级别输出它的调用栈信息
Namespace:日志命名空间。定义命名空间后,所有日志内容就在这个命名空间下。命名空间相当于一个文件夹
支持 hook 操作
看github官网的对比图,下面的对比图来自:https://github.com/uber-go/zap#performance
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 |
基于反射的序列化和字符串格式化,它们都是 CPU 密集型计算且分配很多小的内存。具体到 Go 语言中,使用 encoding/json 和 fmt.Fprintf 格式化 interface{} 会使程序性能降低。
Zap 咋解决呢?Zap 使用一个无反射、零分配的 JOSN 编码器,基础 Logger 尽可能避免序列化开销和内存分配开销。在此基础上,zap 还构建了更高级的 SuggaredLogger。
zap 安装:
go get -u go.uber.org/zap
zap 提供了 2 种日志记录器:SugaredLogger
和 Logger
。
在需要性能但不是很重要的情况下,使用 SugaredLogger 较合适。它比其它结构化日志包快 4-10 倍,包括 结构化日志和 printf 风格的 API。看下面使用 SugaredLogger 例子:
logger, _ := zap.NewProduction() defer logger.Sync() // zap底层有缓冲。在任何情况下执行 defer logger.Sync() 是一个很好的习惯 sugar := logger.Sugar() sugar.Infow("failed to fetch URL", // 字段是松散类型,不是强类型 "url", url, "attempt", 3, "backoff", time.Second, ) sugar.Infof("Failed to fetch URL: %s", url)
当性能和类型安全很重要时,请使用 Logger。它比 SugaredLogger 更快,分配的资源更少,但它只支持结构化日志和强类型字段。
logger, _ := zap.NewProduction() defer logger.Sync() logger.Info("failed to fetch URL", // 字段是强类型,不是松散类型 zap.String("url", url), zap.Int("attempt", 3), zap.Duration("backoff", time.Second), )
zap 为我们提供了三种快速创建 logger 的方法: zap.NewProduction()
,zap.NewDevelopment()
,zap.NewExample()
。
见名思义,Example 一般用在测试代码中,Development 用在开发环境中,Production 用在生成环境中。这三种方法都预先设置好了配置信息。
NewExample 构建一个 logger,专门为在 zap 的测试示例使用。它将 DebugLevel 及以上日志用 JSON 格式标准输出,但它省略了时间戳和调用函数,以保持示例输出的简短和确定性。
为什么说 zap.NewExample()
是 zap 为我们提供快速创建 logger 的方法呢?
因为在这个方法里,zap 已经定义好了日志配置项部分默认值。来看它的代码:
// https://github.com/uber-go/zap/blob/v1.24.0/logger.go#L127 func NewExample(options ...Option) *Logger { encoderCfg := zapcore.EncoderConfig{ MessageKey: "msg", // 日志内容key:val, 前面的key设为msg LevelKey: "level", // 日志级别的key设为level NameKey: "logger", // 日志名 EncodeLevel: zapcore.LowercaseLevelEncoder, //日志级别,默认小写 EncodeTime: zapcore.ISO8601TimeEncoder, // 日志时间 EncodeDuration: zapcore.StringDurationEncoder, } core := zapcore.NewCore(zapcore.NewJSONEncoder(encoderCfg), os.Stdout, DebugLevel) return New(core).WithOptions(options...) }
使用例子:
package main import ( "go.uber.org/zap" ) func main() { logger := zap.NewExample() logger.Debug("this is debug message") logger.Info("this is info message") logger.Info("this is info message with fileds", zap.Int("age", 37), zap.String("agender", "man"), ) logger.Warn("this is warn message") logger.Error("this is error 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":37,"agender":"man"} {"level":"warn","msg":"this is warn message"} {"level":"error","msg":"this is error message"}
NewDevelopment() 构建一个开发使用的 Logger,它以人性化的格式将 DebugLevel 及以上日志信息输出。它的底层使用
NewDevelopmentConfig().Build(...Option)
构建。它的日志格式各种设置在函数 NewDevelopmentEncoderConfig() 里,想查看详情设置,请点进去查看。
使用例子:
package main import ( "time" "go.uber.org/zap" ) func main() { logger, _ := zap.NewDevelopment() defer logger.Sync() logger.Info("failed to fetch url", // 强类型字段 zap.String("url", "http://example.com"), zap.Int("attempt", 3), zap.Duration("duration", time.Second), ) logger.With( // 强类型字段 zap.String("url", "http://development.com"), zap.Int("attempt", 4), zap.Duration("duration", time.Second*5), ).Info("[With] failed to fetch url") }
输出:
2023-03-22T16:02:45.760+0800 INFO zapdemos/newdevelopment1.go:13 failed to fetch url {"url": "http://example.com", "attempt": 3, "duration": "1s"} 2023-03-22T16:02:45.786+0800 INFO zapdemos/newdevelopment1.go:25 [With] failed to fetch url {"url": "http://development.com", "attempt": 4, "duration": "5s"}
NewProduction() 构建了一个合理的 Prouction 日志记录器,它将 info 及以上的日志内容以 JSON 格式记写入标准错误里。
它的底层使用 NewProductionConfig().Build(...Option)
构建。它的日志格式设置在函数 NewProductionEncoderConfig 里。
使用例子:
package main import ( "time" "go.uber.org/zap" ) func main() { logger, _ := zap.NewProduction() defer logger.Sync() url := "http://zap.uber.io" sugar := logger.Sugar() sugar.Infow("failed to fetch URL", "url", url, "attempt", 3, "time", time.Second, ) sugar.Infof("Failed to fetch URL: %s", url) // 或更简洁 Sugar() 使用 // sugar := zap.NewProduction().Sugar() // defer sugar.Sync() }
输出:
{"level":"info","ts":1679472893.2944522,"caller":"zapdemos/newproduction1.go:16","msg":"failed to fetch URL","url":"http://zap.uber.io","attempt":3,"time":1} {"level":"info","ts":1679472893.294975,"caller":"zapdemos/newproduction1.go:22","msg":"Failed to fetch URL: http://zap.uber.io"}
在这 3 个函数中,可以传入一些配置项。为什么能传入配置项?我们来看看 NewExample() 函数定义:
func NewExample(options ...Option) *Logger
它的函数传参有一个 ...Option
选项,是一个 interface 类型,它关联的是 Logger struct。只要返回 Option 就可以传进 NewExample() 里。在 zap/options.go 文件中可以看到很多返回 Option 的函数,也就是说这些函数都可以传入 NewExample 函数里。这里用到了 Go 里面的一个编码技巧,函数选项模式。
zap.Fields() 添加字段到 Logger 中:
package main import ( "go.uber.org/zap" ) func main() { logger, _ := zap.NewProduction(zap.Fields( zap.String("log_name", "testlog"), zap.String("log_author", "prometheus"), )) defer logger.Sync() logger.Info("test fields output") logger.Warn("warn info") }
输出:
{"level":"info","ts":1679477929.842166,"caller":"zapdemos/fields.go:14","msg":"test fields output","log_name":"testlog","log_author":"prometheus"} {"level":"warn","ts":1679477929.842166,"caller":"zapdemos/fields.go:16","msg":"warn info","log_name":"testlog","log_author":"prometheus"}
zap.Hook() 添加回调函数:
Hook (钩子函数)回调函数为用户提供一种简单方法,在每次日志内容记录后运行这个回调函数,执行用户需要的操作。也就是说记录完日志后你还想做其它事情就可以调用这个函数。
package main import ( "fmt" "go.uber.org/zap" "go.uber.org/zap/zapcore" ) func main() { logger := zap.NewExample(zap.Hooks(func(entry zapcore.Entry) error { fmt.Println("[zap.Hooks]test Hooks") return nil })) defer logger.Sync() logger.Info("test output") logger.Warn("warn info") }
输出:
{"level":"info","msg":"test output"} [zap.Hooks]test Hooks {"level":"warn","msg":"warn info"} [zap.Hooks]test Hooks
从上面例子中看出,zap 有 2 种格式化日志方式:logger 和 sugared logger。
- 它有很好的性能,比一般日志包快 4-10 倍。
- 支持结构化的日志。
- 支持 printf 风格的日志。
- 日志字段不需要定义类型
- 它的性能比 sugared logger 还要快。
- 它只支持强类型的结构化日志。
它应用在对性能更加敏感日志记录中,它的内存分配次数更少。
比如如果每一次内存分配都很重要的话可以使用这个。对类型安全有严格要求也可以使用这个。
logger 和 sugaredlogger 相互转换:
// 创建 logger logger := zap.NewExample() defer logger.Sync() // 转换 SugaredLogger sugar := logger.Sugar() // 转换 logger plain := sugar.Desugar()
怎么快速构建一个 logger 呢?有下面种几种方法:
主要区别:
记录日志信息和结构不同。
Example 和 Production 是 json 格式输出,Development 是普通一行格式输出,如果后面带有字段输出话用json格式。
相同点:
怎么选择:
快速构建 logger 日志记录器最简单的方法就是用 zap 预定义了配置的方法:NewExample(), NewProduction()
和NewDevelopment()
,这 3 个方法通过单个函数调用就可以构建一个日志计记录器,也可以简单配置。
但是有的项目需要更多的定制,怎么办?zap 的 Config 结构和 zapcore 的 EncoderConfig 结构可以帮助你,让你能够进行自定义配置。
Config 配置项源码:
// zap v1.24.0 type Config struct { // 动态改变日志级别,在运行时你可以安全改变日志级别 Level AtomicLevel `json:"level" yaml:"level"` // 将日志记录器设置为开发模式,在 WarnLevel 及以上级别日志会包含堆栈跟踪信息 Development bool `json:"development" yaml:"development"` // 在日志中停止调用函数所在文件名、行数 DisableCaller bool `json:"disableCaller" yaml:"disableCaller"` // 完全禁止自动堆栈跟踪。默认情况下,在 development 中,warnlevel及以上日志级别会自动捕获堆栈跟踪信息 // 在 production 中,ErrorLevel 及以上也会自动捕获堆栈信息 DisableStacktrace bool `json:"disableStacktrace" yaml:"disableStacktrace"` // 设置采样策略。没有 SamplingConfing 将禁止采样 Sampling *SamplingConfig `json:"sampling" yaml:"sampling"` // 设置日志编码。可以设置为 console 和 json。也可以通过 RegisterEncoder 设置第三方编码格式 Encoding string `json:"encoding" yaml:"encoding"` // 为encoder编码器设置选项。详细设置信息在 zapcore.zapcore.EncoderConfig EncoderConfig zapcore.EncoderConfig `json:"encoderConfig" yaml:"encoderConfig"` // 日志输出地址可以是一个 URLs 地址或文件路径,可以设置多个 OutputPaths []string `json:"outputPaths" yaml:"outputPaths"` // 错误日志输出地址。默认输出标准错误信息 ErrorOutputPaths []string `json:"errorOutputPaths" yaml:"errorOutputPaths"` // 可以添加自定义的字段信息到 root logger 中。也就是每条日志都会携带这些字段信息,公共字段 InitialFields map[string]interface{} `json:"initialFields" yaml:"initialFields"` }
EncoderConfig 结构源码,它里面也有很多配置选项,具体请看 这里:
// zapcore@v1.24.0 type EncoderConfig struct { // 为log entry设置key。如果 key 为空,那么在日志中的这部分信息也会省略 MessageKey string `json:"messageKey" yaml:"messageKey"`//日志信息的健名,默认为msg LevelKey string `json:"levelKey" yaml:"levelKey"`//日志级别的健名,默认为level TimeKey string `json:"timeKey" yaml:"timeKey"`//记录日志时间的健名,默认为time NameKey string `json:"nameKey" yaml:"nameKey"` CallerKey string `json:"callerKey" yaml:"callerKey"` FunctionKey string `json:"functionKey" yaml:"functionKey"` StacktraceKey string `json:"stacktraceKey" yaml:"stacktraceKey"` SkipLineEnding bool `json:"skipLineEnding" yaml:"skipLineEnding"` LineEnding string `json:"lineEnding" yaml:"lineEnding"` // 日志编码的一些设置项 EncodeLevel LevelEncoder `json:"levelEncoder" yaml:"levelEncoder"` EncodeTime TimeEncoder `json:"timeEncoder" yaml:"timeEncoder"` EncodeDuration DurationEncoder `json:"durationEncoder" yaml:"durationEncoder"` EncodeCaller CallerEncoder `json:"callerEncoder" yaml:"callerEncoder"` // 与其它编码器不同, 这个编码器可选 EncodeName NameEncoder `json:"nameEncoder" yaml:"nameEncoder"` // 配置 interface{} 类型编码器。如果没设置,将用 json.Encoder 进行编码 NewReflectedEncoder func(io.Writer) ReflectedEncoder `json:"-" yaml:"-"` // 配置 console 中字段分隔符。默认使用 tab ConsoleSeparator string `json:"consoleSeparator" yaml:"consoleSeparator"` } type Entry struct { Level Level Time time.Time LoggerName string Message string Caller EntryCaller Stack string }
package main import ( "encoding/json" "go.uber.org/zap" ) // https://pkg.go.dev/go.uber.org/zap@v1.24.0#hdr-Configuring_Zap func main() { // 表示 zap.Config 的 json 原始编码 // outputPath: 设置日志输出路径,日志内容输出到标准输出和文件 logs.log // errorOutputPaths:设置错误日志输出路径 rawJSON := []byte(`{ "level": "debug", "encoding": "json", "outputPaths": ["stdout", "./logs.log"], "errorOutputPaths": ["stderr"], "initialFields": {"foo": "bar"}, "encoderConfig": { "messageKey": "message-customer", "levelKey": "level", "levelEncoder": "lowercase" } }`) // 把 json 格式数据解析到 zap.Config struct var cfg zap.Config if err := json.Unmarshal(rawJSON, &cfg); err != nil { panic(err) } // cfg.Build() 为配置对象创建一个 Logger // zap.Must() 封装了 Logger,Must()函数如果返回值不是 nil,就会报 panic。也就是检查Build是否错误 logger := zap.Must(cfg.Build()) defer logger.Sync() logger.Info("logger construction succeeded") } /* Must() 函数 // var logger = zap.Must(zap.NewProduction()) func Must(logger *Logger, err error) *Logger { if err != nil { panic(err) } return logger } */
consol 输出如下:
{"level":"info","message-customer":"logger construction succeeded","foo":"bar"}
并且在程序目录下生成了一个文件 logs.log,里面记录的日志内容也是上面consol输出内容。每运行一次就在日志文件末尾append一次内容。
上面的配置只是基本的自定义配置,如果有一些复杂的需求,比如在多个文件之间分割日志。
或者输出到不是 file 的文件中,比如输出到 kafka 中,那么就需要使用 zapcore 包。
在下面的例子中,我们将把日志输出到 kafka 中,并且也输出到 console 里。并且我们对 kafka 不同主题进行编码设置,对输出到 console 编码进行设置,也希望处理高优先级的日志。
官方例子:
package main import ( "io" "os" "go.uber.org/zap" "go.uber.org/zap/zapcore" ) func main() { // 首先,定义不同级别日志处理逻辑 highPriority := zap.LevelEnablerFunc(func(lvl zapcore.Level) bool { return lvl >= zapcore.ErrorLevel }) lowPriority := zap.LevelEnablerFunc(func(lvl zapcore.Level) bool { return lvl < zapcore.ErrorLevel }) // 假设有2个kafka 的 topic,一个 debugging,一个 errors // zapcore.AddSync 添加一个文件句柄。 topicDebugging := zapcore.AddSync(io.Discard) topicErrors := zapcore.AddSync(io.Discard) // 如果他们对并发使用不安全,我们可以用 zapcore.Lock 添加一个 mutex 互斥锁。 consoleDebugging := zapcore.Lock(os.Stdout) consoleErrors := zapcore.Lock(os.Stderr) // 设置 kafka 和 console 输出配置 kafkaEncoder := zapcore.NewJSONEncoder(zap.NewProductionEncoderConfig()) consoleEncoder := zapcore.NewConsoleEncoder(zap.NewDevelopmentEncoderConfig()) // 把上面的设置加入到 zapcore.NewCore() 函数里,然后再把他们加入到 zapcore.NewTee() 函数里 core := zapcore.NewTee( zapcore.NewCore(kafkaEncoder, topicErrors, highPriority), zapcore.NewCore(consoleEncoder, consoleErrors, highPriority), zapcore.NewCore(kafkaEncoder, topicDebugging, lowPriority), zapcore.NewCore(consoleEncoder, consoleDebugging, lowPriority), ) // 最后调用 zap.New() 函数 logger := zap.New(core) defer logger.Sync() logger.Info("constructed a logger") }
与上面例子2相似,但是比它简单
package main import ( "os" "go.uber.org/zap" "go.uber.org/zap/zapcore" ) func main() { writetofile() } func writetofile() { // 设置一些配置参数 config := zap.NewProductionEncoderConfig() config.EncodeTime = zapcore.ISO8601TimeEncoder fileEncoder := zapcore.NewJSONEncoder(config) defaultLogLevel := zapcore.DebugLevel // 设置 loglevel logFile, _ := os.OpenFile("./log-test-zap.json", os.O_WRONLY|os.O_CREATE|os.O_APPEND, 06666) // or os.Create() writer := zapcore.AddSync(logFile) logger := zap.New( zapcore.NewCore(fileEncoder, writer, defaultLogLevel), zap.AddCaller(), zap.AddStacktrace(zapcore.ErrorLevel), ) defer logger.Sync() url := "http://www.test.com" logger.Info("write log to file", zap.String("url", url), zap.Int("attemp", 3), ) }
这个与上面例子2相似
package main import ( "os" "go.uber.org/zap" "go.uber.org/zap/zapcore" ) func main() { writeToFileWithLogLevel() } func writeToFileWithLogLevel() { // 设置配置 config := zap.NewProductionEncoderConfig() config.EncodeTime = zapcore.ISO8601TimeEncoder fileEncoder := zapcore.NewJSONEncoder(config) logFile, _ := os.OpenFile("./log-debug-zap.json", os.O_WRONLY|os.O_CREATE|os.O_APPEND, 0666) //日志记录debug信息 errFile, _ := os.OpenFile("./log-err-zap.json", os.O_WRONLY|os.O_CREATE|os.O_APPEND, 0666) //日志记录error信息 teecore := zapcore.NewTee( zapcore.NewCore(fileEncoder, zapcore.AddSync(logFile), zap.DebugLevel), zapcore.NewCore(fileEncoder, zapcore.AddSync(errFile), zap.ErrorLevel), ) logger := zap.New(teecore, zap.AddCaller()) defer logger.Sync() url := "http://www.diff-log-level.com" logger.Info("write log to file", zap.String("url", url), zap.Int("time", 3), ) logger.With( zap.String("url", url), zap.String("name", "jimmmyr"), ).Error("test error ") }
主要是设置日志级别,和把 2 个设置的 NewCore 放入到方法 NewTee 中。
zap.Hook() :
Hook (钩子函数)回调函数为用户提供一种简单方法,在每次日志内容记录后运行这个回调函数,执行用户需要的操作。也就是说记录完日志后你还想做其它事情就可以调用这个函数。
package main import ( "fmt" "go.uber.org/zap" "go.uber.org/zap/zapcore" ) func main() { logger := zap.NewExample(zap.Hooks(func(entry zapcore.Entry) error { fmt.Println("[zap.Hooks]test Hooks") return nil })) defer logger.Sync() logger.Info("test output") logger.Warn("warn info") }
zap.Namespace():
创建一个命名空间,后面的字段都在这名字空间中。Namespace 就像一个文件夹,后面文件都放在这个文件夹里。
package main import ( "go.uber.org/zap" ) func main() { logger := zap.NewExample() defer logger.Sync() logger.Info("some message", zap.Namespace("shop"), zap.String("name", "LiLei"), zap.String("grade", "No2"), ) logger.Error("some error message", zap.Namespace("shop"), zap.String("name", "LiLei"), zap.String("grade", "No3"), ) }
输出:
{"level":"info","msg":"some message","shop":{"name":"LiLei","grade":"No2"}} {"level":"error","msg":"some error message","shop":{"name":"LiLei","grade":"No3"}}
lumberjack 这个库是按照日志大小切割日志文件。
安装 v2 版本:
go get -u github.com/natefinch/lumberjack@v2
Code:
log.SetOutput(&lumberjack.Logger{ Filename: "/var/log/myapp/foo.log", // 文件位置 MaxSize: 500, // megabytes,M 为单位,达到这个设置数后就进行日志切割 MaxBackups: 3, // 保留旧文件最大份数 MaxAge: 28, //days , 旧文件最大保存天数 Compress: true, // disabled by default,是否压缩日志归档,默认不压缩 })
参照它的文档和结合上面自定义配置的例子,写一个例子:
package main import ( "fmt" "go.uber.org/zap" "go.uber.org/zap/zapcore" "gopkg.in/natefinch/lumberjack.v2" ) func main() { lumberjacklogger := &lumberjack.Logger{ Filename: "./log-rotate-test.json", MaxSize: 1, // megabytes MaxBackups: 3, MaxAge: 28, //days Compress: true, // disabled by default } defer lumberjacklogger.Close() config := zap.NewProductionEncoderConfig() config.EncodeTime = zapcore.ISO8601TimeEncoder // 设置时间格式 fileEncoder := zapcore.NewJSONEncoder(config) core := zapcore.NewCore( fileEncoder, //编码设置 zapcore.AddSync(lumberjacklogger), //输出到文件 zap.InfoLevel, //日志等级 ) logger := zap.New(core) defer logger.Sync() // 测试分割日志 for i := 0; i < 8000; i++ { logger.With( zap.String("url", fmt.Sprintf("www.test%d.com", i)), zap.String("name", "jimmmyr"), zap.Int("age", 23), zap.String("agradege", "no111-000222"), ).Info("test info ") } }
zap 的使用,先创建 logger,再调用各个日志级别方法记录日志信息。比如 logger.Info()。
zap 提供了三种快速创建 logger 的方法: zap.Newproduction()
,zap.NewDevelopment()
,zap.NewExample()
。见名思义,Example 一般用在测试代码中,Development 用在开发环境中,Production 用在生成环境中。这三种方法都预先设置好了配置信息。它们的日志数据类型输出都是强类型。
当然,zap 也提供了给用户自定义的方法 zap.New()
。比如用户可以自定义一些配置信息等。
在上面的例子中,几乎都有 defer logger.Sync()
这段代码,为什么?因为 zap 底层 API 允许缓冲日志以提高性能,在默认情况下,日志记录器是没有缓冲的。但是在进程退出之前调用 Sync()
方法是一个好习惯。
如果你在 zap 中使用了 sugaredlogger,把 zap 创建 logger 的三种方法用 logger.Sugar()
包装下,那么 zap 就支持 printf 风格的格式化输出,也支持以 w 结尾的方法。如 Infow,Infof 等。这种就是通用类型日志输出,不是强类型输出,不需要强制指定输出的数据类型。它们的性能区别,通用类型会比强类型下降 50% 左右。
比如 Infow 的输出形式,Infow 不需要 zap.String 这种指定字段的数据类型。如下代码:
sugar := logger.Sugar() sugar.Infow("failed to fetch URL", "url", url, "attempt", 3, "backoff", time.Second, )
强类型输出,比如 Info 方法输出字段和值就需要指定数据类型:
logger.Info("failed to fetch url", // 强类型字段 zap.String("url", "http://example.com"), zap.Int("attempt", 3), zap.Duration("backoff", time.Second), )
强类型输出和通用类型输出区别
通用类型输出,经过 interface{} 转换会有性能损失,标准库的 fmt.Printf 为了通用性就用了 interface{} 这种”万能型“的数据类型,另外它还使用了反射,性能进一步降低。
zap 强类型输出,zap 为了提供日志输出性能,zap 的强类型输出没有使用 interface{} 和反射。zap 默认输出就是强类型。
上面介绍,zap 中 3 种创建 logger 方式(zap.Newproduction()
,zap.NewDevelopment()
,zap.NewExample()
)就是强类型日志字段,当然,也可以转化为通用类型,用 logger.Sugar()
方法创建 SugaredLogger。
zap.Namespace()
创建一个命名空间,后面的字段都在这名字空间中。Namespace 就像一个文件夹,后面文件都放在这个文件夹里。
logger.Info("some message", zap.Namespace("shop"), zap.String("shopid", "s1234323"), )
{"level":"info","msg":"some message","shop":{"shopid":"s1234323"}}
zap demos