Go語言Zap庫Logger的定制化和封裝詳解_第1頁
Go語言Zap庫Logger的定制化和封裝詳解_第2頁
Go語言Zap庫Logger的定制化和封裝詳解_第3頁
Go語言Zap庫Logger的定制化和封裝詳解_第4頁
Go語言Zap庫Logger的定制化和封裝詳解_第5頁
已閱讀5頁,還剩6頁未讀, 繼續(xù)免費閱讀

下載本文檔

版權說明:本文檔由用戶提供并上傳,收益歸屬內容提供方,若內容存在侵權,請進行舉報或認領

文檔簡介

第Go語言Zap庫Logger的定制化和封裝詳解目錄前言Go語言原生的LoggerGo語言原生Logger的缺點Zap日志庫Zap的使用方法安裝zap設置Logger定制Zap的Logger日志切割封裝Logger總結

前言

日志無論對于程序還是程序員都非常重要,有多重要呢,想要長期在公司健健康康的干下去就得學會階段性劃水,階段性劃水的一大關鍵的就是干活快過預期但是裝作。。。不對,這個開頭不對勁,下面重來。

日志無論對于程序還是程序員都非常重要,程序員解決問題的快慢除了經驗外,就是看日志能不能有效地記錄問題發(fā)生的現(xiàn)場以及上下文等等。

那么讓讓程序記錄有效的日志,除了程序內記日志的點位盡量精準外,還需要有一個稱手的Logger。一個好的Logger(日志記錄器)要能提供以下這些能力:

支持把日志寫入到多個輸出流中,比如可以選擇性的讓測試、開發(fā)環(huán)境同時向控制臺和日志文件輸出日志,生產環(huán)境只輸出到文件中。支持多級別的日志等級,比如常見的有:TRACE,DEBUG,INFO,WARN,ERROR等。支持結構化輸出,結構化輸出現(xiàn)在常用的就是JSON形式的,這樣可以讓統(tǒng)一日志平臺,通過logstash之類的組件直接把日志聚合到日志平臺上去。需要支持日志切割--logrotation,按照日期、時間間隔或者文件大小對日志進行切割。在LogEntry中(就是每行記錄)除了主動記錄的信息外,還要包括如打印日志的函數(shù)、所在的文件、行號、記錄時間等。

今天我?guī)Т蠹乙黄鹂纯丛趺丛谑褂肎o語言開發(fā)的項目里打造一個稱手的Logger,在這之前讓我們先回到2009年,看看Go語言自誕生之初就提供給我們的內置Logger。

Go語言原生的Logger

Go語言自帶log內置包,為我們提供了一個默認的Logger,可以直接使用。這個庫的詳細用法可以在官方的文檔里找到:pkg.go.dev/log

使用log記錄日志,默認會輸出到控制臺中。比如下面這個例子:

packagemain

import(

"log"

"net/http"

funcmain(){

simpleHttpGet("")

simpleHttpGet("")

funcsimpleHttpGet(urlstring){

resp,err:=http.Get(url)

iferr!=nil{

log.Printf("Errorfetchingurl%s:%s",url,err.Error())

}else{

log.Printf("StatusCodefor%s:%s",url,resp.Status)

resp.Body.Close()

return

這個例程中,分別向兩個網址進行GET請求,然后記錄了一下返回狀態(tài)碼/請求錯誤。執(zhí)行程序后會有類似輸出:

2025/05/1515:15:26Errorfetchingurl:Get:unsupportedprotocolscheme2025/05/1515:15:26StatusCodefor:200OK

因為第一次請求的URL中協(xié)議頭缺失,所以不能成功發(fā)起請求,日志也很好的記錄了錯誤信息。

Go內置的log包當然也支持把日志輸出到文件中,通過log.SetOutput可以把任何io.Writer的實現(xiàn)設置成日志的輸出。下面我們把上面那個例程修改成向文件輸出日志。

大家可以自己試一下運行效果,這里不再做過多演示。

Go語言原生Logger的缺點

原生Logger的優(yōu)點,顯而易見,簡單、開箱即用,不用引用外部的三方庫。我們可以按照開頭處提出的對于一個Logger的五個標準再看一下默認Logger是否能在項目里使用。

僅限基本的日志級別只有一個Print選項。不支持INFO/DEBUG等多個級別。對于錯誤日志,它有Fatal和PanicFatal日志通過調用os.Exit(1)來結束程序Panic日志在寫入日志消息之后拋出一個panic但是它缺少一個ERROR日志級別,這個級別可以在不拋出panic或退出程序的情況下記錄錯誤缺乏結構化日志格式的能力只支持簡單文本輸出,不能把日志記錄格式化成JSON格式。不提供日志切割的能力。

Zap日志庫

在Go的生態(tài)中,有不少可以選擇的日志庫,之前我們簡單介紹過logrus這個庫的使用:點我查看,它與Go的內置log庫在api層面兼容,直接實現(xiàn)了log.Logger接口,支持把程序的系統(tǒng)級Logger切換成它。

不過logrus在性能敏感的場景下就顯得不香了,用的更多的是Uber開源的zap日志庫。由于Uber在當今Go生態(tài)中的貢獻度很高,加之它本身業(yè)務網約車的性能敏感場景,所以Uber開源的庫很受歡迎?,F(xiàn)在做項目,使用Zap做日志Logger的非常多。程序員的內心OS應該是,不管我這并發(fā)高不高,上就完事了,萬一哪天能從2個并發(fā)突然干成2W并發(fā)呢。

Zap性能高的一大原因是:不用反射,日志里每個要寫入的字段都得攜帶著類型

logger.Info(

"Success..",

zap.String("statusCode",resp.Status),

zap.String("url",url))

上面向日志里寫入了一條記錄,Message是Success..另外寫入了兩個字符串鍵值對。Zap針對日志里要寫入的字段,每個類型都有一個對應的方法把字段轉成zap.Field類型。比如:

zap.Int('key',123)

zap.Bool('key',true)

zap.Error('err',err)

zap.Any('arbitraryType',User{})

還有很多中這種類型方法,就不一一列舉啦。這種記錄日志的方式造成在使用體驗上稍稍有點差,不過考慮到性能上收益這點使用體驗上的損失也能接受。

下面我們先來學習一下Zap的使用方法,再對項目中使用Zap時做些自定義的配置和封裝,讓它變得更好用,最重要的是匹配上我們開頭提出的關于好的Logger的五條標準。

Zap的使用方法

安裝zap

首先說一下,zap的安裝方式,直接運行以下命令下載zap到本地的依賴庫中。

goget-u/zap

設置Logger

我們先說zap提供的配置好的Logger,稍后會對它進行自定義。

通過調用zap.NewProduction()、zap.NewDevelopment()、zap.Example()這三個方法,都可以創(chuàng)建Logger。上面三個方法都可以創(chuàng)建Logger,他們都對Logger進行了不同的配置,比如zap.NewProduction()創(chuàng)建的Logger在記錄日志時會自動記錄調用函數(shù)的信息、打日志的時間等,這三個不用糾結,直接都用zap.NewProduction(),且在項目中使用的時候,我們不會直接用zap配置好的Logger,需要再做更細致的定制。

zap的Logger提供了記錄不同等級的日志的方法,像從低到高的日志等級一般有:Debug、Info、Warn、Error這些級別都有對應的方法。他們的使用方式都一樣,下面是Info方法的方法簽名。

func(log*Logger)Info(msgstring,fields...Field){

ifce:=log.check(InfoLevel,msg);ce!=nil{

ce.Write(fields...)

方法的第一個參數(shù)是日志里msg字段要記錄的信息,msg是日志行記錄里一個固定的字段,要再添加其他字段到日志,直接傳遞zap.Field類型的參數(shù)即可,上面我們已經說過zap.Field類型的字段,就是由zap.String(key,value)這類方法創(chuàng)建出來的。由于Info方法簽名里fileds參數(shù)聲明是可變參數(shù),所以支持添加任意多個字段到日志行記錄里,比如例程里的:

logger.Info("Success..",zap.String("statusCode",resp.Status),zap.String("url",url))

即日志行記錄里,除了msg字段,還添加了statusCode,url兩個自定義字段。上面例程里使用的zap.NewProduction()創(chuàng)建的Logger會向控制臺輸出JSON格式的日志行,比如上面使用Info方法后,控制臺會有類似下面的輸出。

{level:info,ts:1558882294.665447,caller:basiclogger/UberGoLogger.go:31,msg:Success..,statusCode:200OK,url:}

定制Zap的Logger

下面我們把zap做進一步的自定義配置,讓日志不光能輸出到控制臺,也能輸出到文件,再把日志時間由時間戳格式,換成更容易被人類看懂的DateTime時間格式。

下面少說話,直接上代碼,必要的解釋放在了注釋里。

varlogger*zap.Logger

funcinit(){

encoderConfig:=zap.NewProductionEncoderConfig()

//設置日志記錄中時間的格式

encoderConfig.EncodeTime=zapcore.ISO8601TimeEncoder

//日志Encoder還是JSONEncoder,把日志行格式化成JSON格式的

encoder:=zapcore.NewJSONEncoder(encoderConfig)

file,_:=os.OpenFile("/tmp/test.log",os.O_CREATE|os.O_APPEND|os.O_WRONLY,644)

fileWriteSyncer=zapcore.AddSync(file)

core:=zapcore.NewTee(

//同時向控制臺和文件寫日志,生產環(huán)境記得把控制臺寫入去掉,日志記錄的基本是Debug及以上,生產環(huán)境記得改成Info

zapcore.NewCore(encoder,zapcore.AddSync(os.Stdout),zapcore.DebugLevel),

zapcore.NewCore(encoder,fileWriteSyncer,zapcore.DebugLevel),

logger=zap.New(core)

日志切割

Zap本身不支持日志切割,可以借助另外一個庫lumberjack協(xié)助完成切割。

funcgetFileLogWriter()(writeSyncerzapcore.WriteSyncer){

//使用lumberjack實現(xiàn)logrotate

lumberJackLogger:=lumberjack.Logger{

Filename:"/tmp/test.log",

MaxSize:100,//單個文件最大100M

MaxBackups:60,//多于60個日志文件后,清理較舊的日志

MaxAge:1,//一天一切割

Compress:false,

returnzapcore.AddSync(lumberJackLogger)

封裝Logger

我們不能每次使用日志,都這么設置一番,所以最好的還是把這些配置初始化放在一個單獨的包里,這樣在項目中初始化一次即可。

除了上面的那些配置外,我們的配置里還少了些日志調用方的信息,比如函數(shù)名、文件位置、行號等,這樣在排查問題看日志的時候,定位問題的時效會提高不少。

我們對Logger再做一下封裝。

//發(fā)送私信go-logger給公眾號「網管叨bi叨」

//可獲得完整代碼和使用Demo

packagezlog

//簡單封裝一下對zap日志庫的使用

//使用方式:

//zlog.Debug("hello",zap.String("name","Kevin"),zap.Any("arbitraryObj",dummyObject))

//zlog.Info("hello",zap.String("name","Kevin"),zap.Any("arbitraryObj",dummyObject))

//zlog.Warn("hello",zap.String("name","Kevin"),zap.Any("arbitraryObj",dummyObject))

varlogger*zap.Logger

funcinit(){

......

funcgetFileLogWriter()(writeSyncerzapcore.WriteSyncer){

......

funcInfo(messagestring,fields...zap.Field){

callerFields:=getCallerInfoForLog()

fields=append(fields,callerFields...)

logger.Info(message,fields...)

funcDebug(messagestring,fields...zap.Field){

callerFields:=getCallerInfoForLog()

fields=append(fields,callerFields...)

logger.Debug(message,fields...)

funcError(messagestring,fields...zap.Field){

callerFields:=getCallerInfoForLog()

fields=append(fields,callerFields...)

logger.Error(message,fields...)

funcWarn(messagestring,fields...zap.Field){

callerFields:=getCallerInfoForLog()

fields=append(fields,callerFields...)

logger.Warn(message,fields...)

funcgetCallerInfoForLog()(callerFields[]zap.Field){

pc,file,line,ok:=runtime.Caller(2)//回溯兩層,拿到寫日志的調用方的函數(shù)信息

if!ok{

return

funcName:=runtime.FuncForPC(pc).Name()

funcName=path.Base(funcName)//Base函數(shù)返回路徑的最后一個元素,只保留函數(shù)名

callerFields=append(callerFields,zap.String("func",funcName),zap.String("file",file),

溫馨提示

  • 1. 本站所有資源如無特殊說明,都需要本地電腦安裝OFFICE2007和PDF閱讀器。圖紙軟件為CAD,CAXA,PROE,UG,SolidWorks等.壓縮文件請下載最新的WinRAR軟件解壓。
  • 2. 本站的文檔不包含任何第三方提供的附件圖紙等,如果需要附件,請聯(lián)系上傳者。文件的所有權益歸上傳用戶所有。
  • 3. 本站RAR壓縮包中若帶圖紙,網頁內容里面會有圖紙預覽,若沒有圖紙預覽就沒有圖紙。
  • 4. 未經權益所有人同意不得將文件中的內容挪作商業(yè)或盈利用途。
  • 5. 人人文庫網僅提供信息存儲空間,僅對用戶上傳內容的表現(xiàn)方式做保護處理,對用戶上傳分享的文檔內容本身不做任何修改或編輯,并不能對任何下載內容負責。
  • 6. 下載文件中如有侵權或不適當內容,請與我們聯(lián)系,我們立即糾正。
  • 7. 本站不保證下載資源的準確性、安全性和完整性, 同時也不承擔用戶因使用這些下載資源對自己和他人造成任何形式的傷害或損失。

評論

0/150

提交評論