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等日誌庫的比較。感興趣能夠去看看:https://github.com/rs/zerolog#benchmarkszerolog的性能比zap還要優秀!

總結

正是由於有不少人不知足於現狀,才帶來了技術的進步和豐富多彩的開源世界!

你們若是發現好玩、好用的 Go 語言庫,歡迎到 Go 每日一庫 GitHub 上提交 issue😄

參考

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

個人博客:https://darjun.github.io

歡迎關注個人微信公衆號【GoUpUp】,共同窗習,一塊兒進步~

相關文章
相關標籤/搜索