专栏首页GoUpUpGo 每日一库之 zerolog

Go 每日一库之 zerolog

简介

每个编程语言都有很多日志库,因为记录日志在每个项目中都是必须的。前面我们介绍了标准日志库log、好用的logrus和上一篇文章中介绍的由 uber 开源的高性能日志库zapzerolog相比zap更进了一步,它的 API 设计非常注重开发体验和性能。zerolog只专注于记录 JSON 格式的日志,号称 0 内存分配!

快速使用

先安装:

$ go get github.com/rs/zerolog/log

后使用:

package main

import "github.com/rs/zerolog/log"

func main() {
  log.Print("hello world")
}

常规使用与标准库log非常相似,只不过输出的是 JSON 格式的日志:

{"level":"debug","time":"2020-04-25T13:43:08+08:00","message":"hello world"}

字段

我们可以在日志中添加额外的字段信息,有助于调试和问题追踪。与zap一样,zerolog也区分字段类型,不同的是zerolog采用链式调用的方式:

func main() {
  log.Debug().
    Str("Scale", "833 cents").
    Float64("Interval", 833.09).
    Msg("Fibonacci is everywhere")

  log.Debug().
    Str("Name", "Tom").
    Send()
}

调用Msg()Send()之后,日志会被输出:

{"level":"debug","Scale":"833 cents","Interval":833.09,"time":"2020-04-25T13:55:44+08:00","message":"Fibonacci is everywhere"}
{"level":"debug","Name":"Tom","time":"2020-04-25T13:55:44+08:00"}

嵌套

记录的字段可以任意嵌套,这通过Dict()来实现:

func main() {
  log.Info().
    Dict("dict", zerolog.Dict().
      Str("bar", "baz").
      Int("n", 1),
    ).Msg("hello world")
}

输出中dict字段为嵌套结构:

{"level":"info","dict":{"bar":"baz","n":1},"time":"2020-04-25T14:34:51+08:00","message":"hello world"}

全局Logger

上面我们使用log.Debug()log.Info()调用的是全局的Logger。全局的Logger使用比较简单,不需要额外创建。

设置日志级别

每个日志库都有日志级别的概念,而且划分基本上都差不多。zerologpanic/fatal/error/warn/info/debug/trace这几种级别。我们可以调用SetGlobalLevel()设置全局Logger的日志级别。

func main() {
  debug := flag.Bool("debug", false, "sets log level to debug")
  flag.Parse()

  if *debug {
    zerolog.SetGlobalLevel(zerolog.DebugLevel)
  } else {
    zerolog.SetGlobalLevel(zerolog.InfoLevel)
  }

  log.Debug().Msg("This message appears only when log level set to debug")
  log.Info().Msg("This message appears when log level set to debug or info")

  if e := log.Debug(); e.Enabled() {
    e.Str("foo", "bar").Msg("some debug message")
  }
}

在上面代码中,我们根据传入的命令行选项设置日志级别是Debug还是Info。如果日志级别为InfoDebug的日志是不会输出的。也可以调用Enabled()方法来判断日志是否需要输出,需要时再调用相应方法输出,节省了添加字段和日志信息的开销:

if e := log.Debug(); e.Enabled() {
  e.Str("foo", "bar").Msg("some debug message")
}

先不加命令行参数运行,默认为Info级别,Debug日志不会输出:

$ go run main.go
{"level":"info","time":"2020-04-25T14:13:34+08:00","message":"This message appears when log level set to debug or info"}

加上-debug选项,DebugInfo日志都输出了:

$ go run main.go -debug
{"level":"debug","time":"2020-04-25T14:18:19+08:00","message":"This message appears only when log level set to debug"}
{"level":"info","time":"2020-04-25T14:18:19+08:00","message":"This message appears when log level set to debug or info"}
{"level":"debug","foo":"bar","time":"2020-04-25T14:18:19+08:00","message":"some debug 
message"}

不输出级别和信息

有时候我们不想输出日志级别(即level字段),这时可以使用log.Log()方法。有时,我们没有日志信息可输出,这时传一个空字符串给Msg()方法:

func main() {
  log.Log().
    Str("foo", "bar").
    Msg("")
}

运行:

{"foo":"bar","time":"2020-04-25T14:19:48+08:00"}

创建Logger

上面我们使用的都是全局的Logger,这种方式有一个明显的缺点:如果在某个地方修改了设置,将影响全局的日志记录。为了消除这种影响,我们需要创建新的Logger

func main() {
  logger := zerolog.New(os.Stderr)
  logger.Info().Str("foo", "bar").Msg("hello world")
}

调用zerlog.New()传入一个io.Writer作为日志写入器即可。

Logger

基于当前的Logger可以创建一个子Logger,子Logger可以在父Logger上附加一些额外的字段。调用logger.With()创建一个上下文,然后为它添加字段,最后调用Logger()返回一个新的Logger

func main() {
  logger := zerolog.New(os.Stderr)
  sublogger := logger.With().
    Str("foo", "bar").
    Logger()
  sublogger.Info().Msg("hello world")
}

sublogger会额外输出"foo": "bar"这个字段。

设置

zerolog提供了多种选项定制输入日志的行为。

美化输出

zerolog提供了一个ConsoleWriter可输出便于我们阅读的,带颜色的日志。调用zerolog.Output()来启用ConsoleWriter

func main() {
  logger := log.Output(zerolog.ConsoleWriter{Out: os.Stderr})
  logger.Info().Str("foo", "bar").Msg("hello world")
}

输出:

我们还能进一步对ConsoleWriter进行配置,定制输出的级别、信息、字段名、字段值的格式:

func main() {
  output := zerolog.ConsoleWriter{Out: os.Stderr, TimeFormat: time.RFC3339}
  output.FormatLevel = func(i interface{}) string {
    return strings.ToUpper(fmt.Sprintf("| %-6s|", i))
  }
  output.FormatMessage = func(i interface{}) string {
    return fmt.Sprintf("***%s****", i)
  }
  output.FormatFieldName = func(i interface{}) string {
    return fmt.Sprintf("%s:", i)
  }
  output.FormatFieldValue = func(i interface{}) string {
    return strings.ToUpper(fmt.Sprintf("%s", i))
  }

  logger := log.Output(output).With().Timestamp().Logger()
  logger.Info().Str("foo", "bar").Msg("hello world")
}

实际上就是对级别、信息、字段名和字段值设置钩子,输出前经过钩子函数转换一次:

ConsoleWriter的性能不够理想,建议只在开发环境中使用!

设置自动添加的字段名

输出的日志中级别默认的字段名为level,信息默认为message,时间默认为time。可以通过zerologLevelFieldName/MessageFieldName/TimestampFieldName来设置:

func main() {
  zerolog.TimestampFieldName = "t"
  zerolog.LevelFieldName = "l"
  zerolog.MessageFieldName = "m"

  logger := zerolog.New(os.Stderr).With().Timestamp().Logger()
  logger.Info().Msg("hello world")
}

输出:

{"l":"info","t":"2020-04-25T14:53:08+08:00","m":"hello world"}

注意,这个设置是全局的!!!

输出文件名和行号

有时我们需要输出文件名和行号,以便能很快定位代码位置,方便找出问题。这可以通过在创建子Logger时带入Caller()选项完成:

func main() {
  logger := zerolog.New(os.Stderr).With().Caller().Logger()
  logger.Info().Msg("hello world")
}

输出:

{"level":"info","caller":"d:/code/golang/src/github.com/darjun/go-daily-lib/zerolog/setting/file-line/main.go:11","message":"hello world"}

日志采样

有时候日志太多了反而对我们排查问题造成干扰,zerolog支持日志采样的功能,可以每隔多少条日志输出一次,其他日志丢弃:

func main() {
  sampled := log.Sample(&zerolog.BasicSampler{N: 10})

  for i := 0; i < 20; i++ {
    sampled.Info().Msg("will be logged every 10 message")
  }
}

结果只输出两条:

{"level":"info","time":"2020-04-25T15:01:02+08:00","message":"will be logged every 10 message"}
{"level":"info","time":"2020-04-25T15:01:02+08:00","message":"will be logged every 10 message"}

还有更高级的设置:

func main() {
  sampled := log.Sample(&zerolog.LevelSampler{
    DebugSampler: &zerolog.BurstSampler{
      Burst:       5,
      Period:      time.Second,
      NextSampler: &zerolog.BasicSampler{N: 100},
    },
  })

  sampled.Debug().Msg("hello world")
}

上面代码只采样Debug日志,在 1s 内最多输出 5 条日志,超过 5条 时,每隔 100 条输出一条。

钩子

zerolog支持钩子,我们可以针对不同的日志级别添加一些额外的字段或进行其他的操作:

type AddFieldHook struct {
}

func (AddFieldHook) Run(e *zerolog.Event, level zerolog.Level, msg string) {
  if level == zerolog.DebugLevel {
    e.Str("name", "dj")
  }
}

func main() {
  hooked := log.Hook(AddFieldHook{})
  hooked.Debug().Msg("")
}

如果是Debug级别,额外输出"name":"dj"字段:

{"level":"debug","time":"2020-04-25T15:09:04+08:00","name":"dj"}

性能

关于性能,GitHub 上有一份详细的性能测试,与logrus/zap等日志库的比较。感兴趣可以去看看:https://github.com/rs/zerolog#benchmarks。zerolog的性能比zap还要优秀!

总结

正是因为有很多人不满足于现状,才带来了技术的进步和丰富多彩的开源世界!

大家如果发现好玩、好用的 Go 语言库,欢迎到 Go 每日一库 GitHub 上提交 issue?

参考

  1. zerolog GitHub:https://github.com/rs/zerolog
  2. Go 每日一库 GitHub:https://github.com/darjun/go-daily-lib

本文分享自微信公众号 - GoUpUp(GoUp-Up),作者:dj

原文出处及转载信息见文内详细说明,如有侵权,请联系 yunjia_community@tencent.com 删除。

原始发表时间:2020-04-25

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

我来说两句

0 条评论
登录 后参与评论

相关文章

  • 在本地运行 fyne 官网

    要深入学习和理解一个框架,官方文档是必须要仔细阅读的。fyne 官网有非常系统和详尽的文档。官方网站:https://fyne.io/。有时候我们会有这样一个需...

    用户7731323
  • Go 每日一库之 watermill

    在上一篇文章Go 每日一库之 message-bus中,我们介绍了一款小巧、实现简单的异步通信库。作为学习,message-bus确实不错。但是在实际使用上,m...

    用户7731323
  • Go 每日一库之 cron

    cron一个用于管理定时任务的库,用 Go 实现 Linux 中crontab这个命令的效果。之前我们也介绍过一个类似的 Go 库——gron。gron代码小巧...

    用户7731323
  • python函数修饰符@的使用方法解析

    版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明...

    于小勇
  • 什么是 XLNet ? 为什么它的性能优于 BERT?

    XLNet:NLP领域中一个新的预训练方法,相比BERT可以显著提高20个任务的准确率。

    AI研习社
  • --PostgreSQL 的存储过程怎么写 与 质疑

    PostgreSQL 的存储过程在POSTGRESQL 11 有了改变,从统一的 create function 到 create procedure 到底能从...

    AustinDatabases
  • 【翻译】分布式计算的八个谬论

    基本上很多人在第一次构建分布式系统的时候,都会有如下的八种假定观点,而在后面的运行中证明这些假定都是错误的,而且这些假定都引来了巨大的麻烦和令人痛苦的经历。

    阿杜
  • 二值化神经网络(BNN)综述

    【GiantPandaCV导语】二值化神经网络BNN由于可以实现极高的压缩比和加速效果,所以它是推动以深度神经网络为代表的人工智能模型在资源受限和功耗受限的移动...

    BBuf
  • 伪异步 IO

    为了解决同步阻塞 I/O 面临的一个链路需要一个线程处理的问题,有人对它的线程模型进行了优化:后端通过一个线程池来处理多个客户端的请求接入,形成客户端个数 M:...

    happyJared
  • 微服务的最终一致性与事件流

    微服务是指一个个单个小型业务功能的服务,由于各个微服务开发部署都是独立的,因此微服务天然是分布式的,因此,分布式系统的设计问题如CAP定理同样适合微服务架构,虽...

    物流IT圈

扫码关注云+社区

领取腾讯云代金券