golang常用庫:日誌記錄庫-logrus使用

九卷發表於2021-11-12

介紹 logrus

它是一個結構化、外掛化的日誌記錄庫。完全相容 golang 標準庫中的日誌模組。它還內建了 2 種日誌輸出格式 JSONFormatter 和 TextFormatter,來定義輸出的日誌格式。

github地址:https://github.com/sirupsen/logrus

logrus 使用

使用的版本:logrus v1.8.1

1. 開始使用

package main

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

func main() {
	log.WithFields(log.Fields{
		"animal": "walrus",
	}).Info("a walrus appears")
}

執行輸出:

time="2021-11-11T17:41:48+08:00" level=info msg="a walrus appears" animal=walrus

2. 設定日誌格式,日誌級別,輸出方式

設定日誌格式

1)內建日誌格式

log Formatter,logrus內建的formatter有 2 種,logrus.TextFormatter 和 logrus.JSONFormatter

  • logrus.JSONFormatter{}, 設定為 json 格式,所有設定選項在 logrus.JSONFormatter

    log.SetFormatter(&log.JSONFormatter{
        TimestampFormat: "2006-01-02 15:04:05", // 設定json裡的日期輸出格式
    })
    
    log.SetFormatter(&log.JSONFormatter{}) // 設定為json格式
    
  • logrus.TextFormatter{},設定為文字格式,所有的設定選項在 logrus.TextFormatter

    log.SetFormatter(&log.TextFormatter{
        TimestampFormat: "2006-01-02 15:04:05",
        ForceColors:  true,
        EnvironmentOverrideColors: true,
        // FullTimestamp:true,
        // DisableLevelTruncation:true,
    })
    

2)自定義日誌格式

可以根據 Formatter 介面自定義日誌格式,裡面有一個 Format 方法,這個 Format 方法裡有一個struct型別資料 *Entry, Entry.Data 是所有欄位集合,Fields 型別為 map[string]interface{}。

比如:entry.Data["msg"],entry.Data["time"]`. The timestamp

package main

import (
	"fmt"

	jsoniter "github.com/json-iterator/go"
	log "github.com/sirupsen/logrus"
)

type MyJSONFormatter struct {
	JSONPrefix string
	Otherdata  string
}

func (my *MyJSONFormatter) Format(entry *log.Entry) ([]byte, error) {
	// fmt.Println(entry.Data["msg"])

	entry.Data["msg"] = fmt.Sprintf("%s%s", my.JSONPrefix, my.Otherdata)
	json, err := jsoniter.Marshal(&entry.Data)
	if err != nil {
		return nil, fmt.Errorf("failed to marshal fields to JSON , %w", err)
	}
	return append(json, '\n'), nil

}

func main() {
	formatter := &MyJSONFormatter{
		JSONPrefix: "jsonprefix-",
		Otherdata:  ":otherdata:",
	}

	log.SetFormatter(formatter)
	log.Info("this is customered formatter")
}

3)第三方自定義formatter設定日誌格式

等等

設定日誌級別

logrus日誌一共7級別, 從高到低: panic, fatal, error, warn, info, debug, trace.

  • log.SetLevel(log.WarnLevel) // 設定輸出警告級別

設定日誌輸出方式

  • log.SetOutput(os.Stdout) // 輸入到 Stdout,預設輸出到 Stderr
  • logfile, _ := os.OpenFile("./logrus.log", os.O_CREATE|os.O_RDWR|os.O_APPEND, 0644)
    logrus.SetOutput(logfile) // 輸出到檔案裡

例子:

package main

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

func init() {
	log.SetFormatter(&log.JSONFormatter{}) // 設定 format json
	log.SetLevel(log.WarnLevel) // 設定輸出警告級別
    // Output to stdout instead of the default stderr
    log.SetOutput(os.Stdout)
}

func main() {
	log.WithFields(log.Fields{
		"animal": "dog",
		"size":   10,
	}).Info("a group of dog emerges from the zoon")

	log.WithFields(log.Fields{
		"omg":    true,
		"number": 12,
	}).Warn("the group's number increased")

	log.WithFields(log.Fields{
		"omg":    true,
		"number": 100,
	}).Fatal("th ice breaks")

    // the logrus.Entry returned from WithFields()
	contextLogger := log.WithFields(log.Fields{
		"common": "this is a common filed",
		"other":  "i also should be logged always",
	})
	// 共同欄位輸出
	contextLogger.Info("I'll be logged with common and other field")
	contextLogger.Info("Me too")
}

執行輸出:

{"level":"warning","msg":"the group's number increased","number":12,"omg":true,"time":"2021-11-11T18:00:55+08:00"}
{"level":"fatal","msg":"th ice breaks","number":100,"omg":true,"time":"2021-11-11T18:00:55+08:00"}

從輸出的結果看出,Info 級別的日誌資訊都沒有輸出出來。

遮蔽設定日誌級別的程式碼

func init() {
	log.SetFormatter(&log.JSONFormatter{}) // 設定 format json
	// log.SetLevel(log.WarnLevel)            // 設定輸出警告級別
}

在執行輸出:

{"animal":"dog","level":"info","msg":"a group of dog emerges from the zoon","size":10,"time":"2021-11-11T18:26:45+08:00"}
{"level":"warning","msg":"the group's number increased","number":12,"omg":true,"time":"2021-11-11T18:26:45+08:00"}
{"level":"fatal","msg":"th ice breaks","number":100,"omg":true,"time":"2021-11-11T18:26:45+08:00"}
exit status 1

從輸出的日誌資訊來看,並沒有輸出 contextLogger 的日誌info資訊,日誌資訊沒有輸出,為啥沒有輸出日誌?

把上面的 Fatal 輸出日誌遮蔽掉:

// log.WithFields(log.Fields{
	// 	"omg":    true,
	// 	"number": 100,
	// }).Fatal("th ice breaks")

在執行輸出:

{"animal":"dog","level":"info","msg":"a group of dog emerges from the zoon","size":10,"time":"2021-11-11T18:28:56+08:00"}
{"level":"warning","msg":"the group's number increased","number":12,"omg":true,"time":"2021-11-11T18:28:56+08:00"}
{"common":"this is a common filed","level":"info","msg":"I'll be logged with common and other field","other":"i also should be logged always","time":"2021-11-11T18:28:56+08:00"}
{"common":"this is a common filed","level":"info","msg":"Me too","other":"i also should be logged always","time":"2021-11-11T18:28:56+08:00"}

這時候可以輸出 contextLogger 日誌資訊了。

3. logrus 的 Fatal 處理

上面的例子定義了輸出 Fatal 日誌後,其後的日誌都不能輸出了,這是為什麼?日誌後面有個資訊 exit status 1

因為 logrus 的 Fatal 輸出後,會執行 os.Exit(1)。那如果程式後面還有一些必要的程式要處理怎麼辦?

logrus 提供了 RegisterExitHandler 方法,在 fatal 異常時處理一些問題。

package main

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

func main() {
	log.SetFormatter(&log.TextFormatter{
		TimestampFormat: "2006-01-02 15:04:05",
	})

	log.RegisterExitHandler(func() {
		fmt.Println("發生了fatal異常,執行一些必要的處理工作")
	})

	log.Warn("warn")
	log.Fatal("fatal")
	log.Info("info") //不會執行
}

執行輸出:

time="2021-11-11 21:48:25" level=warning msg=warn
time="2021-11-11 21:48:25" level=fatal msg=fatal
發生了fatal異常,執行一些必要的處理工作
exit status 1

4. 切分日誌檔案

如果日誌檔案太大了,想切分成小檔案,但是 logrus 沒有提供這個功能。

一種是藉助linux系統的 logrotate 命令來切分 logrus 生成的日誌檔案。

另外一種是用 logrus 的 hook 功能,做一個切分日誌的外掛。找到了 file-rotatelogs,但是這個庫狀態

已經是 archived 狀態,庫作者現在不接受任何修改,他也不繼續維護了。所以使用還是慎重些。

logrus issue 裡找到了這個 https://github.com/natefinch/lumberjack 切割檔案的庫。

例子:

package main

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

	"gopkg.in/natefinch/lumberjack.v2"
)

func main() {
	logger := &lumberjack.Logger{
		Filename:   "./testlogrus.log",
		MaxSize:    500,  // 日誌檔案大小,單位是 MB
		MaxBackups: 3,    // 最大過期日誌保留個數
		MaxAge:     28,   // 保留過期檔案最大時間,單位 天
		Compress:   true, // 是否壓縮日誌,預設是不壓縮。這裡設定為true,壓縮日誌
	}

	log.SetOutput(logger) // logrus 設定日誌的輸出方式

}

5. 設定 logrus 例項

如果一個應用有多個地方使用日誌,可以單獨例項化一個 logrus,作為全域性的日誌例項。

package main

import (
	"os"

	"github.com/sirupsen/logrus"
)

var log = logrus.New()

func main() {
	log.Out = os.Stdout // 設定輸出日誌位置,可以設定日誌到file裡

	log.WithFields(logrus.Fields{
		"fruit": "apple",
		"size":  20,
	}).Info(" a lot of apples on the tree")
}

輸出:

time="2021-11-11T18:39:15+08:00" level=info msg=" a lot of apples on the tree" fruit=apple size=20

6. fields

在使用 logrus 時,鼓勵用 log.WithFields(log.Fields{}).Fatal() 這種方式替代 og.Fatalf("Failed to send event %s to topic %s with key %d"), 也就是不是用 %s,%d 這種方式格式化,而是直接傳入變數 event,topic 給 log.Fields ,這樣就顯得結構化日誌輸出,很人性化美觀。

log.WithFields(log.Fields{
  "event": event,
  "topic": topic,
  "key": key,
}).Fatal("Failed to send event")

7. 設定預設欄位

比如在鏈路追蹤裡,會有一個 rquest_id ,trace_id 等,想這個 log 一直帶有這 2 個欄位,logrus 怎麼設定?

可以用 log.WithFields(log.Fields{"request_id": request_id, "trace_id": trace_id})

requestLogger := log.WithFields(log.Fields{"request_id": request_id, "trace_id": trace_id})
requestLogger.Info("something happened on that request")
requestLogger.Warn("something not great happened")

例子:

package main

import (
	"github.com/google/uuid"
	log "github.com/sirupsen/logrus"
)

func main() {
	uid := uuid.New()
	request_id := uid
	trace_id := uid
	requestLogger := log.WithFields(log.Fields{"request_id": request_id, "trace_id": trace_id})
	requestLogger.Info("something happened on that request")
	requestLogger.Warn("something not great happened")
}

8. hook 鉤子-擴充套件logrus功能

hook 給 logrus 提供了強大的可擴充套件功能.

使用者可以給 logrus 編寫鉤子外掛,根據自己的日誌需求編寫 hook。

logrus 也有一些內建外掛hooks

第三方給 logrus 編寫的 hook, 第三方hook列表

官方的 syslog hook example

package main

import (
	"log/syslog"

	"github.com/sirupsen/logrus"
	lSyslog "github.com/sirupsen/logrus/hooks/syslog"
)

func main() {
	log := logrus.New()
	hook, err := lSyslog.NewSyslogHook("", "", syslog.LOG_INFO, "")
	if err != nil {
		log.Hooks.Add(hook)
	}
}

參考

相關文章