Go 每日一库之 zerolog

简介

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

快速使用

先安装:github

$ go get github.com/rs/zerolog/log
复制代码

后使用:golang

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()以后,日志会被输出:app

{"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等日志库的比较。感兴趣能够去看看:github.com/rs/zerolog#…zerolog的性能比zap还要优秀!

总结

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

你们若是发现好玩、好用的 Go 语言库,欢迎到 Go 每日一库 GitHub 上提交 issue😄

参考

  1. zerolog GitHub:github.com/rs/zerolog
  2. Go 每日一库 GitHub:github.com/darjun/go-d…

个人博客:darjun.github.io

欢迎关注个人微信公众号【GoUpUp】,共同窗习,一块儿进步~

相关文章
相关标签/搜索