Go 每日一庫之 logrus

簡介

前一篇文章介紹了 Go 標準庫中的日誌庫 log。最後咱們也提到,log庫只提供了三組接口,功能過於簡單了。 今天,咱們來介紹一個日誌庫中的「明星庫」——logrus。本文編寫之時(2020.02.07),logrus 在 GitHub 上 star 數已達到 13.8k。 logrus徹底兼容標準的log庫,還支持文本、JSON 兩種日誌輸出格式。不少知名的開源項目都使用了這個庫,如大名鼎鼎的 docker。git

快速使用

第三方庫須要先安裝:程序員

$ go get github.com/sirupsen/logrus
複製代碼

後使用:github

package main

import (
  "github.com/sirupsen/logrus"
)

func main() {
  logrus.SetLevel(logrus.TraceLevel)

  logrus.Trace("trace msg")
  logrus.Debug("debug msg")
  logrus.Info("info msg")
  logrus.Warn("warn msg")
  logrus.Error("error msg")
  logrus.Fatal("fatal msg")
  logrus.Panic("panic msg")
}
複製代碼

logrus的使用很是簡單,與標準庫log相似。logrus支持更多的日誌級別:golang

  • Panic:記錄日誌,而後panic
  • Fatal:致命錯誤,出現錯誤時程序沒法正常運轉。輸出日誌後,程序退出;
  • Error:錯誤日誌,須要查看緣由;
  • Warn:警告信息,提醒程序員注意;
  • Info:關鍵操做,核心流程的日誌;
  • Debug:通常程序中輸出的調試信息;
  • Trace:很細粒度的信息,通常用不到;

日誌級別從上向下依次增長,Trace最大,Panic最小。logrus有一個日誌級別,高於這個級別的日誌不會輸出。 默認的級別爲InfoLevel。因此爲了能看到TraceDebug日誌,咱們在main函數第一行設置日誌級別爲TraceLevelweb

運行程序,輸出:redis

$ go run main.go
time="2020-02-07T21:22:42+08:00" level=trace msg="trace msg"
time="2020-02-07T21:22:42+08:00" level=debug msg="debug msg"
time="2020-02-07T21:22:42+08:00" level=info msg="info msg"
time="2020-02-07T21:22:42+08:00" level=info msg="warn msg"
time="2020-02-07T21:22:42+08:00" level=error msg="error msg"
time="2020-02-07T21:22:42+08:00" level=fatal msg="fatal msg"
exit status 1
複製代碼

因爲logrus.Fatal會致使程序退出,下面的logrus.Panic不會執行到。mongodb

另外,咱們觀察到輸出中有三個關鍵信息,timelevelmsgdocker

  • time:輸出日誌的時間;
  • level:日誌級別;
  • msg:日誌信息。

定製

輸出文件名

調用logrus.SetReportCaller(true)設置在輸出日誌中添加文件名和方法信息:windows

package main

import (
  "github.com/sirupsen/logrus"
)

func main() {
  logrus.SetReportCaller(true)

  logrus.Info("info msg")
}
複製代碼

輸出多了兩個字段file爲調用logrus相關方法的文件名,method爲方法名:bash

$ go run main.go
time="2020-02-07T21:46:03+08:00" level=info msg="info msg" func=main.main file="D:/code/golang/src/github.com/darjun/go-daily-lib/logrus/caller/main.go:10"
複製代碼

添加字段

有時候須要在輸出中添加一些字段,能夠經過調用logrus.WithFieldlogrus.WithFields實現。 logrus.WithFields接受一個logrus.Fields類型的參數,其底層實際上爲map[string]interface{}

// github.com/sirupsen/logrus/logrus.go
type Fields map[string]interface{}
複製代碼

下面程序在輸出中添加兩個字段nameage

package main

import (
  "github.com/sirupsen/logrus"
)

func main() {
  logrus.WithFields(logrus.Fields{
    "name": "dj",
    "age": 18,
  }).Info("info msg")
}
複製代碼

若是在一個函數中的全部日誌都須要添加某些字段,可使用WithFields的返回值。例如在 Web 請求的處理器中,日誌都要加上user_idip字段:

package main

import (
  "github.com/sirupsen/logrus"
)

func main() {
  requestLogger := logrus.WithFields(logrus.Fields{
    "user_id": 10010,
    "ip":      "192.168.32.15",
  })

  requestLogger.Info("info msg")
  requestLogger.Error("error msg")
}
複製代碼

實際上,WithFields返回一個logrus.Entry類型的值,它將logrus.Logger和設置的logrus.Fields保存下來。 調用Entry相關方法輸出日誌時,保存下來的logrus.Fields也會隨之輸出。

重定向輸出

默認狀況下,日誌輸出到io.Stderr。能夠調用logrus.SetOutput傳入一個io.Writer參數。後續調用相關方法日誌將寫到io.Writer中。 如今,咱們就能像上篇文章介紹log時同樣,能夠搞點事情了。傳入一個io.MultiWriter, 同時將日誌寫到bytes.Buffer、標準輸出和文件中:

package main

import (
  "bytes"
  "io"
  "log"
  "os"

  "github.com/sirupsen/logrus"
)

func main() {
  writer1 := &bytes.Buffer{}
  writer2 := os.Stdout
  writer3, err := os.OpenFile("log.txt", os.O_WRONLY|os.O_CREATE, 0755)
  if err != nil {
    log.Fatalf("create file log.txt failed: %v", err)
  }

  logrus.SetOutput(io.MultiWriter(writer1, writer2, writer3))
  logrus.Info("info msg")
}
複製代碼

自定義

實際上,考慮到易用性,庫通常會使用默認值建立一個對象,包最外層的方法通常都是操做這個默認對象。

咱們以前好幾篇文章都提到過這點:

這個技巧應用在不少庫的開發中,logrus也是如此:

// github.com/sirupsen/logrus/exported.go
var (
  std = New()
)

func StandardLogger() *Logger {
  return std
}

func SetOutput(out io.Writer) {
  std.SetOutput(out)
}

func SetFormatter(formatter Formatter) {
  std.SetFormatter(formatter)
}

func SetReportCaller(include bool) {
  std.SetReportCaller(include)
}

func SetLevel(level Level) {
  std.SetLevel(level)
}
複製代碼

首先,使用默認配置定義一個Logger對象stdSetOutput/SetFormatter/SetReportCaller/SetLevel這些方法都是調用std對象的對應方法!

咱們固然也能夠建立本身的Logger對象,使用方式與直接調用logrus的方法相似:

package main

import "github.com/sirupsen/logrus"

func main() {
  log := logrus.New()

  log.SetLevel(logrus.InfoLevel)
  log.SetFormatter(&logrus.JSONFormatter{})

  log.Info("info msg")
}
複製代碼

日誌格式

logrus支持兩種日誌格式,文本和 JSON,默認爲文本格式。能夠經過logrus.SetFormatter設置日誌格式:

package main

import (
  "github.com/sirupsen/logrus"
)

func main() {
  logrus.SetLevel(logrus.TraceLevel)
  logrus.SetFormatter(&logrus.JSONFormatter{})

  logrus.Trace("trace msg")
  logrus.Debug("debug msg")
  logrus.Info("info msg")
  logrus.Warn("warn msg")
  logrus.Error("error msg")
  logrus.Fatal("fatal msg")
  logrus.Panic("panic msg")
}
複製代碼

程序輸出 JSON 格式的日誌:

$ go run main.go 
{"level":"trace","msg":"trace msg","time":"2020-02-07T21:40:04+08:00"}
{"level":"debug","msg":"debug msg","time":"2020-02-07T21:40:04+08:00"}
{"level":"info","msg":"info msg","time":"2020-02-07T21:40:04+08:00"}
{"level":"info","msg":"warn msg","time":"2020-02-07T21:40:04+08:00"}
{"level":"error","msg":"error msg","time":"2020-02-07T21:40:04+08:00"}
{"level":"fatal","msg":"fatal msg","time":"2020-02-07T21:40:04+08:00"}
exit status 1
複製代碼

第三方格式

除了內置的TextFormatterJSONFormatter,還有很多第三方格式支持。咱們這裏介紹一個nested-logrus-formatter

先安裝:

$ go get github.com/antonfisher/nested-logrus-formatter
複製代碼

後使用:

package main

import (
  nested "github.com/antonfisher/nested-logrus-formatter"
  "github.com/sirupsen/logrus"
)

func main() {
  logrus.SetFormatter(&nested.Formatter{
    HideKeys:    true,
    FieldsOrder: []string{"component", "category"},
  })

  logrus.Info("info msg")
}
複製代碼

程序輸出:

Feb  8 15:22:59.077 [INFO] info msg
複製代碼

nested格式提供了多個字段用來定製行爲:

// github.com/antonfisher/nested-logrus-formatter/formatter.go
type Formatter struct {
  FieldsOrder     []string
  TimestampFormat string  
  HideKeys        bool    
  NoColors        bool    
  NoFieldsColors  bool    
  ShowFullLevel   bool    
  TrimMessages    bool    
}
複製代碼
  • 默認,logrus輸出日誌中字段是key=value這樣的形式。使用nested格式,咱們能夠經過設置HideKeystrue隱藏鍵,只輸出值;
  • 默認,logrus是按鍵的字母序輸出字段,能夠設置FieldsOrder定義輸出字段順序;
  • 經過設置TimestampFormat設置日期格式。
package main

import (
  "time"

  nested "github.com/antonfisher/nested-logrus-formatter"
  "github.com/sirupsen/logrus"
)

func main() {
  logrus.SetFormatter(&nested.Formatter{
    // HideKeys: true,
    TimestampFormat: time.RFC3339,
    FieldsOrder:     []string{"name", "age"},
  })

  logrus.WithFields(logrus.Fields{
    "name": "dj",
    "age":  18,
  }).Info("info msg")
}
複製代碼

若是不隱藏鍵,程序輸出:

$ 2020-02-08T15:40:07+08:00 [INFO] [name:dj] [age:18] info msg
複製代碼

隱藏鍵,程序輸出:

$ 2020-02-08T15:41:58+08:00 [INFO] [dj] [18] info msg
複製代碼

注意到,咱們將時間格式設置成time.RFC3339,即2006-01-02T15:04:05Z07:00這種形式。

經過實現接口logrus.Formatter能夠實現本身的格式。

// github.com/sirupsen/logrus/formatter.go
type Formatter interface {
  Format(*Entry) ([]byte, error)
}
複製代碼

設置鉤子

還能夠爲logrus設置鉤子,每條日誌輸出前都會執行鉤子的特定方法。因此,咱們能夠添加輸出字段、根據級別將日誌輸出到不一樣的目的地。 logrus也內置了一個syslog的鉤子,將日誌輸出到syslog中。這裏咱們實現一個鉤子,在輸出的日誌中增長一個app=awesome-web字段。

鉤子須要實現logrus.Hook接口:

// github.com/sirupsen/logrus/hooks.go
type Hook interface {
  Levels() []Level
  Fire(*Entry) error
}
複製代碼

Levels()方法返回感興趣的日誌級別,輸出其餘日誌時不會觸發鉤子。Fire是日誌輸出前調用的鉤子方法。

package main

import (
  "github.com/sirupsen/logrus"
)

type AppHook struct {
  AppName string
}

func (h *AppHook) Levels() []logrus.Level {
  return logrus.AllLevels
}

func (h *AppHook) Fire(entry *logrus.Entry) error {
  entry.Data["app"] = h.AppName
  return nil
}

func main() {
  h := &AppHook{AppName: "awesome-web"}
  logrus.AddHook(h)

  logrus.Info("info msg")
}
複製代碼

只須要在Fire方法實現中,爲entry.Data添加字段就會輸出到日誌中。

程序輸出:

$ time="2020-02-08T15:51:52+08:00" level=info msg="info msg" app=awesome-web
複製代碼

logrus的第三方 Hook 不少,咱們可使用一些 Hook 將日誌發送到 redis/mongodb 等存儲中:

這裏咱們演示一個 redis,感興趣自行驗證其餘的。先安裝logrus-redis-hook

$ go get github.com/rogierlommers/logrus-redis-hook
複製代碼

而後編寫程序:

package main

import (
  "io/ioutil"

  logredis "github.com/rogierlommers/logrus-redis-hook"
  "github.com/sirupsen/logrus"
)

func init() {
  hookConfig := logredis.HookConfig{
    Host:     "localhost",
    Key:      "mykey",
    Format:   "v0",
    App:      "aweosome",
    Hostname: "localhost",
    TTL:      3600,
  }

  hook, err := logredis.NewHook(hookConfig)
  if err == nil {
    logrus.AddHook(hook)
  } else {
    logrus.Errorf("logredis error: %q", err)
  }
}

func main() {
  logrus.Info("just some info logging...")

  logrus.WithFields(logrus.Fields{
    "animal": "walrus",
    "foo":    "bar",
    "this":   "that",
  }).Info("additional fields are being logged as well")

  logrus.SetOutput(ioutil.Discard)
  logrus.Info("This will only be sent to Redis")
}
複製代碼

爲了程序能正常工做,咱們還須要安裝redis

windows 上直接使用choco安裝 redis:

PS C:\Users\Administrator> choco install redis-64
Chocolatey v0.10.15
Installing the following packages:
redis-64
By installing you accept licenses for the packages.
Progress: Downloading redis-64 3.0.503... 100%

redis-64 v3.0.503 [Approved]
redis-64 package files install completed. Performing other installation steps.
 ShimGen has successfully created a shim for redis-benchmark.exe
 ShimGen has successfully created a shim for redis-check-aof.exe
 ShimGen has successfully created a shim for redis-check-dump.exe
 ShimGen has successfully created a shim for redis-cli.exe
 ShimGen has successfully created a shim for redis-server.exe
 The install of redis-64 was successful.
  Software install location not explicitly set, could be in package or
  default install location if installer.

Chocolatey installed 1/1 packages.
 See the log for details (C:\ProgramData\chocolatey\logs\chocolatey.log).
複製代碼

直接輸入redis-server,啓動服務器:

運行程序後,咱們使用redis-cli查看:

咱們看到mykey是一個list,每過來一條日誌,就在list後新增一項。

總結

本文介紹了logrus的基本用法。logrus的可擴展性很是棒,能夠引入第三方格式和 Hook 加強功能。在社區也比較受歡迎。

參考

  1. logrus GitHub 倉庫
  2. Hooks

個人博客

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

本文由博客一文多發平臺 OpenWrite 發佈!

相關文章
相關標籤/搜索