前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >GoLang 日志库 zap 的使用

GoLang 日志库 zap 的使用

原创
作者头像
饶文津
修改2020-06-22 16:39:19
10.2K0
修改2020-06-22 16:39:19
举报
文章被收录于专栏:饶文津的专栏饶文津的专栏

zap 是 Uber 开源的 go语言的日志库,它的优势在于实时写结构化日志(Structured Logging)到文件有很好的性能。结构化日志就是说相比于直接输出日志文本,使用 json 或者其它编码方式使日志结构化,这样可以方便后续用各种工具分析处理和查找,比如用 ELK(Elasticsearch, Logstash and Kibana)。根据 zap 自己的基准库测试结果,它比其它结构化日志的库(比如我之前使用的 logrus )要有更好的性能。接下来主要介绍一下 zap 库的使用方法。

注:下文将忽略引用库的代码:

代码语言:javascript
复制
import "go.uber.org/zap"

全局的 logger

zap 的基础用法是创建一个 logger 实例,然后在所有要用它的地方将它作为参数传过去用:

代码语言:javascript
复制
logger, _ := zap.NewProduction()
defer logger.Sync() // 将 buffer 中的日志写到文件中
logger.Info("this is a test log")

或者是用一个全局的 logger 实例,zap 库自己提供的全局的 logger 是zap.S()zap.L()。可以用 ReplaceGlobals来将全局的 logger 替换为我们通过配置定制的 logger :

代码语言:javascript
复制
logger := zap.NewExample()
defer logger.Sync()

undo := zap.ReplaceGlobals(logger)
defer undo()

zap.L().Info("replaced zap's global loggers")

Sugar 的作用

zap 默认的 logger 不支持格式化输出,要打印指定值要用 zap.Stringzap.Int 等封装,代码就显得非常冗长,如:

代码语言:javascript
复制
logger, _ := zap.NewDevelopment() // 忽略了错误
logger.Info("this is a test log", zap.String("name", "x"), zap.Int("age", 20))

但它提供了 Sugar(语法糖的糖),只要一点点额外的性能损失(但是仍比大部分库快),可以比较简单地格式化输出。

代码语言:javascript
复制
sugar := logger.Sugar()
sugar.Infof("name is %s", "x") // 格式化输出
sugar.Infow("this is a test log", "name", "x", "age", 20) // 第二个开始每一对是一个键值

// 使用全局的 SugaredLogger
name := "x"
zap.S().Info("this is a test log: name=", name) // 用法相当于 fmt.Print

定制 zap 提供了可配置的 logger,配置一个 logger 时至少需要以下设置:

代码语言:javascript
复制
logger, _ := zap.Config{
    Encoding: "json", // 配置编码方式(json 或 console)
    Level: zap.NewAtomicLevelAt(zapcore.DebugLevel), // 输出级别
    OutputPaths: []string{"stdout"}, // 输出目的地
}.Build()

其中 OutputPaths 可以用来设置希望日志输出到的文件路径。不过上面这样设置后,message 信息就不能打印出来:

代码语言:javascript
复制
logger.Info("something to log") // 只会打印 {}
logger.Sugar().Infow("test", "name", "xxx") // 也只打印 {"name": "xxx"}

需要如下设置EncoderConfig中的 MessageKey 才能打印出message,同时也可设置需要打印的其它key:

代码语言:javascript
复制
logger, _ := zap.Config{
    Encoding: "json",
    Level: zap.NewAtomicLevelAt(zapcore.DebugLevel),
    OutputPaths: []string{"stdout"},
    EncoderConfig: zapcore.EncoderConfig{  
        MessageKey: "message",  
        LevelKey:    "level",
        EncodeLevel: zapcore.CapitalLevelEncoder,// INFO
        
        TimeKey:    "time",
        EncodeTime: zapcore.ISO8601TimeEncoder,

        CallerKey:    "caller",
        EncodeCaller: zapcore.ShortCallerEncoder,
    },
}.Build()

其中:

  • 时间可以设置为 ISO 8601 format,或者 Unix时间戳(秒、毫秒、纳秒)
  • level 可以设置为 capital 或者 lowercase,还可设置有颜色的,但是json方式输出时不会生效。
  • caller 就是调用者,可以设置 short (package/file:line)或者 full( /full/path/to/package/file:line)。

另外,也可以使用 zap 提供的 ProductionEncoderConfig 等配置,进行定制:

代码语言:javascript
复制
cfg := zap.NewProductionEncoderConfig()
cfg.EncodeTime = zapcore.ISO8601TimeEncoder

file, _ := os.Create(LogPath)
core := zapcore.NewTee(
    zapcore.NewCore(zapcore.NewJSONEncoder(cfg), zapcore.AddSync(file), zapcore.InfoLevel),
    zapcore.NewCore(zapcore.NewConsoleEncoder(cfg)), zapcore.Lock(os.Stdout), zapcore.DebugLevel),
)
logger := zap.New(core)
defer logger.Sync()

Sync 的作用

注意到官方文档提示说要在退出程序前执行一下logger.Sync(), 它是做什么的呢?默认情况,Linux 写文件都是异步的,写的内容会先缓存在内存里,在合适的时间刷(flush)到磁盘中。而 Sync 是一个强制将缓存的数据立刻刷入磁盘的命令。所以 GoLang 标准库中的 File 就有 Sync 函数来对应这个命令。因此 logger.Sync()做的事情就是对所有输出目标文件执行 Sync。 如果不执行,就有可能导致部分内容没有被写入磁盘。

为什么性能比较好?

最后我们简单看看 zap 为提高性能做的主要工作。zap 提供的默认的 logger 避免了 interface{}的存在(因为你必须声明 Field 的类型),这样减少了反射(Reflection)用的时间。且 zap 使用了 sync.Pool 来减少给 buffer 分配空间的时间。

总结一下,使用 zap 不需要我们付出多少额外的工作量,却可以得到比较明显的性能提升,因此如果你的项目需要输出结构化的日志到文件,不妨使用 zap。

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 全局的 logger
  • Sugar 的作用
  • Sync 的作用
  • 为什么性能比较好?
相关产品与服务
日志服务
日志服务(Cloud Log Service,CLS)是腾讯云提供的一站式日志服务平台,提供了从日志采集、日志存储到日志检索,图表分析、监控告警、日志投递等多项服务,协助用户通过日志来解决业务运维、服务监控、日志审计等场景问题。
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档