選擇golang日志庫時, 使用logrus的主要原因就是因為star比較多, 而且社區(qū)活躍度非常高. 在項目使用過程中, 發(fā)現(xiàn)logrus的調(diào)用入口, 性能, 插件, 自定義插件, 輸出格式等都非常優(yōu)秀, 值得學(xué)習(xí)一下
整體結(jié)構(gòu)圖
整體來看總共提供兩種方式調(diào)用:
- logrus.Info("hello logrus")
- logrus.WithField(logruns.Fields{"key1":"v1"}).Info("hello logrus")
這些函數(shù)都在exported.go
文件中. 當(dāng)然為了提供不同級別輸出日志的功能, 里面實現(xiàn)了各種各樣的print
函數(shù), 如: Infof
, Error
, Errorf
, Panic
等等
在直接使用logrus等情況下exported.go
是唯一入口, 但是我們可以簡單封裝一下, 跟項目框架更加貼合, 這個留在后面用具體例子來解釋這么做的原因和好處
簡單介紹使用方法
普通用法
package main
import (
log "github.com/sirupsen/logrus"
)
func main() {
log.Info("A walrus appears")
}
time="2019-07-16T22:51:31+08:00" level=info msg="hello logrus"
注意log "github.com/sirupsen/logrus"
這里將logrus的別名設(shè)置為log, 然后就直接調(diào)用了log.Info. 假如你的項目現(xiàn)在使用的標準庫log, 則可以無縫遷移到logrus上, 因為標準庫實現(xiàn)的print函數(shù)較少, logrus全部已經(jīng)實現(xiàn), 只需要簡單引入這個別名即可
WithFields
由于logrus不建議下面的用法:
log.Fatalf("Failed to send event %s to topic %s with key %d")
因為logrus鼓勵結(jié)構(gòu)化的日志輸出, 上面的用法就非常的不人性化, 不美觀. 應(yīng)該改成下面的方式:
log.WithFields(log.Fields{
"event": event,
"topic": topic,
"key": key,
}).Fatal("Failed to send event")
但是根據(jù)實際使用過程中發(fā)現(xiàn), 一般要使用WithFields輸出日志字段時, 那些字段一般都是公共字段, 比如: request_id, token等等, 程序里到處打印WithFields也不是個優(yōu)美的辦法(后續(xù)會解釋怎么做)
所以盡管logrus
不建議我們使用Printf
, 但是程序該需要用到Printf
的地方還是需要的
設(shè)置打印格式
logrus自帶兩種方式的輸出格式: 純文本和JSON格式的.
JSONFormatter
func main() {
log.SetFormatter(&log.JSONFormatter{})
log.Info("hello logrus")
}
{"level":"info","msg":"hello logrus","time":"2019-07-17T22:47:14+08:00"}
TextFormatter
默認情況下就是TextFormatter
, 默認情況下是帶顏色輸出的. 當(dāng)然也不是任何時候都輸出帶顏色的結(jié)果, 取決于在終端輸出并且不是運行在windows系統(tǒng), 或者是否設(shè)置過ForceColors=true
, 如果設(shè)置了就會按照有顏色的方式輸出.
程序會在啟動的時候檢測是否是終端運行, 具體的實現(xiàn)就是terminal_check_(OS).go
, 具體實現(xiàn)后續(xù)關(guān)于TextFormatter的具體實現(xiàn)再看
func main() {
log.SetFormatter(&log.TextFormatter{})
log.Info("hello logrus")
}
INFO[0000] hello logrus
也可以禁用
func main() {
log.SetFormatter(&log.TextFormatter{
DisableColors: true,
})
log.Info("hello logrus")
}
time="2019-07-17T23:44:42+08:00" level=info msg="hello logrus"
同時, 你可以根據(jù)自己的實際需求, 去定制自己的Formater
, 只需要實現(xiàn)Format
方法即可
設(shè)置調(diào)用log的位置
func main() {
log.SetFormatter(&log.TextFormatter{
DisableColors: true,
})
log.SetReportCaller(true)
log.Info("hello logrus")
}
time="2019-07-18T10:40:21+08:00" level=info msg="hello logrus" func=main.main file="/Users/haohongfan/goproject/test/logrus_test/main.go:33"
但是請注意:
Note that this does add measurable overhead - the cost will depend on the version of Go,
but is between 20 and 40% in recent tests with 1.6 and 1.7.You can validate this in your environment
via benchmarks: go test -bench=.*CallerTracing
也就設(shè)置這個是有性能問題的, 生產(chǎn)環(huán)境是一定不能啟動用, 其實也沒有必要, 我們并不關(guān)心是哪一行打印的(如果你的日志確實需要靠這個來確定的話, 那你的日志是需要優(yōu)化一下的)
設(shè)置日志級別
logrus日志一共7級別, 從高到低: panic, fatal, error, warn, info, debug, trace.
在生產(chǎn)環(huán)境時選擇打印Info以上級別的日志, 就可以log.SetLevel(log.InfoLevel), 那么Debug, Trace就不會打印出來. 源碼實現(xiàn)這個功能很簡單, 就是判斷Print函數(shù)的級別是否大于SetLevel的值
log.SetLevel(log.ErrorLevel)這個函數(shù)要求傳入的參數(shù)是Level
類型的值(其實也就是uint32, type Level uint32), 我們在封裝我們代碼時, 肯定要定義panic等這些級別. logrus本身提供將panic
轉(zhuǎn)換成PanicLevel
的函數(shù)和獲取xxLevel
對應(yīng)的字符串. 這些都封裝在logrus.go
里面
func main() {
// log.SetLevel(log.ErrorLevel)
level, _ := log.ParseLevel("info")
log.SetLevel(level)
log.Info("hello logrus")
fmt.Println(log.ErrorLevel)
}
error
time="2019-07-18T11:41:02+08:00" level=info msg="hello logrus"
Hook
Hook
是一大特色, 也給logrus留下各種各樣的擴展機會. 比如: lfshook
, dingrus
等
你可以根據(jù)自己的特殊需求擴展自己的Hook, 只需要簡單實現(xiàn)Levels() []Level
, Fire(*Entry) error
即可. logrus提供一個syslog
, test
的插件, 同時github上可以找到很多
后面說源碼的時候, 我會選擇lfshook作為例子進行分析其實現(xiàn)細節(jié), 同時我們也會選擇一個功能實現(xiàn)一個
日志的文件輸出, 切分, 刪過期文件
logrus本身不提供這樣的功能, 需要借助第三方插件lfshook進行
相對高級的用法
前面說到程序里到處log.WithFields{log.Field{xxx}}是一種比較不好的用法, 故我們開發(fā)的框架在集成logrus的時候要簡單封裝一下. logrus README也有提到
requestLogger := log.WithFields(log.Fields{"request_id": request_id, "user_ip": user_ip})
requestLogger.Info("something happened on that request") # will log request_id and user_ip
requestLogger.Warn("something not great happened")
下面說具體如何操作, 可以參考bilibili sniper
比如跟gin
的結(jié)合使用, 這是我的項目的一段實際的代碼. 在log目錄下創(chuàng)建log.go
// Entry ling-nest log Entry
func Entry(ctx *gin.Context) *logrus.Entry {
return logrus.WithFields(logrus.Fields{
"device_type": ctx.Value("device_type"),
"channel": ctx.Value("channel"),
"license": ctx.Value("license"),
"v4": ctx.Value("V4"),
})
}
實際使用時: log.Entry(context).Info("xxxxxx")
總結(jié)
第一篇關(guān)于logrus源碼閱讀主要是為了介紹相關(guān)的用法. 從下面開始將正式進入源碼階段. 下一篇主要根據(jù)源碼介紹logrus的整個生命周期