日誌按照天自動輸出_每日一庫之 logrus 日誌使用教程
技術標籤:日誌按照天自動輸出
golang 日誌庫
golang標準庫的日誌框架非常簡單,僅僅提供了print,panic和fatal三個函式對於更精細的日誌級別、日誌檔案分割以及日誌分發等方面並沒有提供支援. 所以催生了很多第三方的日誌庫,但是在 golang 的世界裡,沒有一個日誌庫像 slf4j 那樣在 Java 中具有絕對統治地位.golang 中,流行的日誌框架包括 logrus、zap、zerolog、seelog 等.
logrus是目前 Github 上 star 數量最多的日誌庫,目前(2018.12,下同)star 數量為 8119,fork 數為 1031. logrus功能強大,效能高效,而且具有高度靈活性,提供了自定義外掛的功能.很多開源專案,如docker,prometheus,dejavuzhou/ginbro[1]等,都是用了 logrus 來記錄其日誌.
zap 是 Uber 推出的一個快速、結構化的分級日誌庫.具有強大的 ad-hoc 分析功能,並且具有靈活的儀表盤.zap 目前在 GitHub 上的 star 數量約為 4.3k. seelog 提供了靈活的非同步排程、格式化和過濾功能.目前在 GitHub 上也有約 1.1k.
logrus 特性
- 完全相容 golang 標準庫日誌模組:logrus 擁有六種日誌級別:debug、info、warn、error、fatal 和 panic,這是 golang 標準庫日誌模組的 API 的超集.如果您的專案使用標準庫日誌模組,完全可以以最低的代價遷移到 logrus 上。
- logrus.Debug("Useful debugging information.")
- logrus.Info("Something noteworthy happened!")
- logrus.Warn("You should probably take a look at this.")
- logrus.Error("Something failed but I'm not quitting.")
- logrus.Fatal("Bye.") // log 之後會呼叫 os.Exit(1)
- logrus.Panic("I'm bailing.") // log 之後會 panic()
- 可擴充套件的 Hook 機制:允許使用者通過 hook 的方式將日誌分發到任意地方,如本地檔案系統、標準輸出、logstash、elasticsearch或者mq等,或者通過 hook 定義日誌內容和格式等.
- 可選的日誌輸出格式:logrus 內建了兩種日誌格式,JSONFormatter和TextFormatter,如果這兩個格式不滿足需求,可以自己動手實現介面 Formatter,來定義自己的日誌格式.
- Field機制:logrus鼓勵通過 Field 機制進行精細化的、結構化的日誌記錄,而不是通過冗長的訊息來記錄日誌.
- logrus是一個可插拔的、結構化的日誌框架.
- Entry: logrus.WithFields 會自動返回一個 *Entry,Entry 裡面的有些變數會被自動加上
- time:entry被建立時的時間戳
- msg:在呼叫.Info()等方法時被新增
- level
logrus 的使用
1.基本用法
packagemainimport(log"github.com/sirupsen/logrus")funcmain(){log.WithFields(log.Fields{"animal":"walrus",}).Info("Awalrusappears")}
上面程式碼執行後,標準輸出上輸出如下:
time="2018-08-11T15:42:22+08:00"level=infomsg="Awalrusappears"animal=walrus
logrus與 golang 標準庫日誌模組完全相容,因此您可以使用log“github.com/sirupsen/logrus”替換所有日誌匯入. logrus可以通過簡單的配置,來定義輸出、格式或者日誌級別等.
packagemainimport("os"log"github.com/sirupsen/logrus")funcinit(){//設定日誌格式為json格式log.SetFormatter(&log.JSONFormatter{})//設定將日誌輸出到標準輸出(預設的輸出為stderr,標準錯誤)//日誌訊息輸出可以是任意的io.writer型別log.SetOutput(os.Stdout)//設定日誌級別為warn以上log.SetLevel(log.WarnLevel)}funcmain(){log.WithFields(log.Fields{"animal":"walrus","size":10,}).Info("Agroupofwalrusemergesfromtheocean")log.WithFields(log.Fields{"omg":true,"number":122,}).Warn("Thegroup'snumberincreasedtremendously!")log.WithFields(log.Fields{"omg":true,"number":100,}).Fatal("Theicebreaks!")}
2.自定義 Logger
如果想在一個應用裡面向多個地方log,可以建立 Logger 例項. logger是一種相對高階的用法, 對於一個大型專案, 往往需要一個全域性的logrus例項,即logger物件來記錄專案所有的日誌.如:
packagemainimport("github.com/sirupsen/logrus""os")//logrus提供了New()函式來建立一個logrus的例項.//專案中,可以建立任意數量的logrus例項.varlog=logrus.New()funcmain(){//為當前logrus例項設定訊息的輸出,同樣地,//可以設定logrus例項的輸出到任意io.writerlog.Out=os.Stdout//為當前logrus例項設定訊息輸出格式為json格式.//同樣地,也可以單獨為某個logrus例項設定日誌級別和hook,這裡不詳細敘述.log.Formatter=&logrus.JSONFormatter{}log.WithFields(logrus.Fields{"animal":"walrus","size":10,}).Info("Agroupofwalrusemergesfromtheocean")}
3.Fields 用法
前一章提到過,logrus不推薦使用冗長的訊息來記錄執行資訊,它推薦使用Fields來進行精細化的、結構化的資訊記錄. 例如下面的記錄日誌的方式:
log.Fatalf("Failedtosendevent%stotopic%swithkey%d",event,topic,key)
在logrus中不太提倡,logrus鼓勵使用以下方式替代之:
log.WithFields(log.Fields{"event":event,"topic":topic,"key":key,}).Fatal("Failedtosendevent")
前面的 WithFields API 可以規範使用者按照其提倡的方式記錄日誌.但是 WithFields 依然是可選的,因為某些場景下,使用者確實只需要記錄儀一條簡單的訊息.
通常,在一個應用中、或者應用的一部分中,都有一些固定的 Field.比如在處理使用者 http 請求時,上下文中,所有的日誌都會有request_id和user_ip.為了避免每次記錄日誌都要使用 log.WithFields(log.Fields{“request_id”: request_id, “user_ip”: user_ip}),我們可以建立一個 logrus.Entry 例項,為這個例項設定預設 Fields,在上下文中使用這個 logrus.Entry 例項記錄日誌即可.
requestLogger:=log.WithFields(log.Fields{"request_id":request_id,"user_ip":user_ip})requestLogger.Info("somethinghappenedonthatrequest")#willlogrequest_idanduser_iprequestLogger.Warn("somethingnotgreathappened")
4.Hook 介面用法
logrus 最令人心動的功能就是其可擴充套件的 HOOK 機制了,通過在初始化時為 logrus 新增 hook,logrus 可以實現各種擴充套件功能.
logrus 的 hook 介面定義如下,其原理是每此寫入日誌時攔截,修改 logrus.Entry.
//logrus在記錄Levels()返回的日誌級別的訊息時會觸發HOOK,//按照Fire方法定義的內容修改logrus.Entry.typeHookinterface{Levels()[]LevelFire(*Entry)error}
一個簡單自定義 hook 如下,DefaultFieldHook 定義會在所有級別的日誌訊息中加入預設欄位 appName=”myAppName”.
typeDefaultFieldHookstruct{}func(hook*DefaultFieldHook)Fire(entry*log.Entry)error{entry.Data["appName"]="MyAppName"returnnil}func(hook*DefaultFieldHook)Levels()[]log.Level{returnlog.AllLevels}
hook的使用也很簡單,在初始化前呼叫log.AddHook(hook)新增相應的hook即可.
logrus官方僅僅內建了syslog的hook. 此外,但 Github 也有很多第三方的hook可供使用,文末將提供一些第三方HOOK的連線.
4.1 Logrus-Hook-Email
email這裡只需用NewMailAuthHook方法得到hook,再新增即可
funcEmail(){logger:=logrus.New()//parameter"APPLICATION_NAME","HOST",PORT,"FROM","TO"//首先開啟smtp服務,最後兩個引數是smtp的使用者名稱和密碼hook,err:=logrus_mail.NewMailAuthHook("testapp","smtp.163.com",25,"[email protected]","[email protected]","smtp_name","smtp_password")iferr==nil{logger.Hooks.Add(hook)}//生成*Entryvarfilename="123.txt"contextLogger:=logger.WithFields(logrus.Fields{"file":filename,"content":"GG",})//設定時間戳和messagecontextLogger.Time=time.Now()contextLogger.Message="這是一個hook發來的郵件"//只能傳送Error,Fatal,Panic級別的logcontextLogger.Level=logrus.FatalLevel//使用Fire傳送,包含時間戳,messagehook.Fire(contextLogger)}
4.2 Logrus-Hook-Slack
安裝 slackrus github.com/johntdyer/slackrus
packagemainimport(logrus"github.com/sirupsen/logrus""github.com/johntdyer/slackrus""os")funcmain(){logrus.SetFormatter(&logrus.JSONFormatter{})logrus.SetOutput(os.Stderr)logrus.SetLevel(logrus.DebugLevel)logrus.AddHook(&slackrus.SlackrusHook{HookURL:"https://hooks.slack.com/services/abc123/defghijklmnopqrstuvwxyz",AcceptedLevels:slackrus.LevelThreshold(logrus.DebugLevel),Channel:"#slack-testing",IconEmoji:":ghost:",Username:"foobot",})logrus.Warn("warn")logrus.Info("info")logrus.Debug("debug")}
- HookURL: 填寫 slack web-hook 地址
- AcceptedLevels: 設定日誌輸出級別
- Channel: 設定日誌頻道
- Username: 設定需要@的使用者名稱
4.3 Logrus-Hook 日誌分隔
logrus 本身不帶日誌本地檔案分割功能,但是我們可以通過 file-rotatelogs 進行日誌本地檔案分割. 每次當我們寫入日誌的時候,logrus 都會呼叫 file-rotatelogs 來判斷日誌是否要進行切分.關於本地日誌檔案分割的例子網上很多,這裡不再詳細介紹,奉上程式碼:
import("github.com/lestrrat-go/file-rotatelogs""github.com/rifflock/lfshook"log"github.com/sirupsen/logrus""time")funcnewLfsHook(logLevel*string,maxRemainCntuint)log.Hook{writer,err:=rotatelogs.New(logName+".%Y%m%d%H",//WithLinkName為最新的日誌建立軟連線,以方便隨著找到當前日誌檔案rotatelogs.WithLinkName(logName),//WithRotationTime設定日誌分割的時間,這裡設定為一小時分割一次rotatelogs.WithRotationTime(time.Hour),//WithMaxAge和WithRotationCount二者只能設定一個,//WithMaxAge設定檔案清理前的最長儲存時間,//WithRotationCount設定檔案清理前最多儲存的個數.//rotatelogs.WithMaxAge(time.Hour*24),rotatelogs.WithRotationCount(maxRemainCnt),)iferr!=nil{log.Errorf("configlocalfilesystemforloggererror:%v",err)}level,ok:=logLevels[*logLevel]ifok{log.SetLevel(level)}else{log.SetLevel(log.WarnLevel)}lfsHook:=lfshook.NewHook(lfshook.WriterMap{log.DebugLevel:writer,log.InfoLevel:writer,log.WarnLevel:writer,log.ErrorLevel:writer,log.FatalLevel:writer,log.PanicLevel:writer,},&log.TextFormatter{DisableColors:true})returnlfsHook}
使用上述本地日誌檔案切割的效果如下:
img
4.4 Logrus-Dingding-Hook 阿里釘釘群機器人
釘釘開發文件自定義機器人[2]
自定義 hook 程式碼 `utils/logrus_hook_dingding.go`[3]
可以無視的方法(非同步傳送 http json body 到釘釘 api 加快響應速度)
- hook.jsonBodies
- hook.closeChan
- func (dh *dingHook) startDingHookQueueJob()
- func (dh *dingHook) Fire2(e *logrus.Entry) error
packageutilsimport("bytes""encoding/json""fmt""log""net/http""github.com/sirupsen/logrus")varallLvls=[]logrus.Level{logrus.DebugLevel,logrus.InfoLevel,logrus.WarnLevel,logrus.ErrorLevel,logrus.FatalLevel,logrus.PanicLevel,}funcNewDingHook(url,appstring,thresholdLevellogrus.Level)*dingHook{temp:=[]logrus.Level{}for_,v:=rangeallLvls{ifv<=thresholdLevel{temp=append(temp,v)}}hook:=&dingHook{apiUrl:url,levels:temp,appName:app}hook.jsonBodies=make(chan[]byte)hook.closeChan=make(chanbool)//開啟chan佇列執行postdingdinghookapigohook.startDingHookQueueJob()returnhook}func(dh*dingHook)startDingHookQueueJob(){for{select{case
使用釘釘 hook `cmd/root.go`[4]
funcinitSlackLogrus(){lvl:=logrus.InfoLevel//釘釘群機器人API地址apiUrl:=viper.GetString("logrus.dingHookUrl")dingHook:=utils.NewDingHook(apiUrl,"Felix",lvl)logrus.SetLevel(lvl)logrus.SetFormatter(&logrus.JSONFormatter{TimestampFormat:"06-01-02T15:04:05"})logrus.SetReportCaller(true)logrus.AddHook(dingHook)}
logrus 執行緒安全
預設的 logger 在併發寫的時候是被 mutex 保護的,比如當同時呼叫 hook 和寫 log 時 mutex 就會被請求,有另外一種情況,檔案是以 appending mode 開啟的, 此時的併發操作就是安全的,可以用 logger.SetNoLock()來關閉它
致謝
- logrus[5]
- logrus-hook-email[6]
- logrus-hook-slack[7]
- logrus-hook-釘釘[8]
原文作者:Eric Zhou 原文連結:https://mojotv.cn/2018/12/27/golang-logrus-tutorial參考資料
[1]
dejavuzhou/ginbro: https://github.com/libragen/ginbro/blob/master/readme_zh.md
[2]
釘釘開發文件自定義機器人: https://open-doc.dingtalk.com/microapp/serverapi2/qf2nxq
[3]
utils/logrus_hook_dingding.go: https://github.com/libragen/felix/blob/master/utils/logrus_hook_dingding.go
[4]
cmd/root.go: https://github.com/libragen/felix/blob/master/cmd/root.go
[5]
logrus: https://github.com/sirupsen/logrus
[6]
logrus-hook-email: https://github.com/zbindenren/logrus_mail
[7]
logrus-hook-slack: https://github.com/johntdyer/slackrus
[8]
logrus-hook-釘釘: https://github.com/dandans-dan/dingrus