golang常用庫:日誌記錄庫-logrus使用
介紹 logrus
它是一個結構化、插件化的日誌記錄庫。完全兼容 golang 標準庫中的日誌模組。它還內置了 2 種日誌輸出格式 JSONFormatter 和 TextFormatter,來定義輸出的日誌格式。
github地址://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設置日誌格式
FluentdFormatter
. Formats entries that can be parsed by Kubernetes and Google Container Engine.logstash
. Logs fields as Logstash Events.caption-json-formatter
. logrus’s message json formatter with human-readable caption added.powerful-logrus-formatter
. get fileName, log’s line number and the latest function’s name when print log; Sava log to files.
等等
設置日誌級別
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 里找到了這個 //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)
}
}