首頁 > 軟體

golang預設Logger紀錄檔庫在專案中使用Zap紀錄檔庫

2022-04-16 13:00:39

在Go語言專案中使用Zap紀錄檔庫介紹

在許多Go語言專案中,我們需要一個好的紀錄檔記錄器能夠提供下面這些功能:

  • 能夠將事件記錄到檔案中,而不是應用程式控制臺。
  • 紀錄檔切割-能夠根據檔案大小、時間或間隔等來切割紀錄檔檔案。
  • 支援不同的紀錄檔級別。例如INFO,DEBUG,ERROR等。
  • 能夠列印基本資訊,如呼叫檔案/函數名和行號,紀錄檔時間等。

預設的Go Logger紀錄檔庫

在介紹Uber-go的zap包之前,讓我們先看看Go語言提供的基本紀錄檔功能。Go語言提供的預設紀錄檔包是https://golang.org/pkg/log/。

實現Go Logger

實現一個Go語言中的紀錄檔記錄器非常簡單——建立一個新的紀錄檔檔案,然後設定它為紀錄檔的輸出位置。

設定Logger

我們可以像下面的程式碼一樣設定紀錄檔記錄器

func SetupLogger() {
	logFileLocation, _ := os.OpenFile("/Users/q1mi/test.log", os.O_CREATE|os.O_APPEND|os.O_RDWR, 0744)
	log.SetOutput(logFileLocation)
}

使用Logger

讓我們來寫一些虛擬的程式碼來使用這個紀錄檔記錄器。

在當前的範例中,我們將建立一個到URL的HTTP連線,並將狀態程式碼/錯誤記錄到紀錄檔檔案中。

func simpleHttpGet(url string) {
	resp, err := http.Get(url)
	if err != nil {
		log.Printf("Error fetching url %s : %s", url, err.Error())
	} else {
		log.Printf("Status Code for %s : %s", url, resp.Status)
		resp.Body.Close()
	}
}

Logger的執行

現在讓我們執行上面的程式碼並檢視紀錄檔記錄器的執行情況。

func main() {
	SetupLogger()
	simpleHttpGet("www.google.com")
	simpleHttpGet("http://www.google.com")
}

當我們執行上面的程式碼,我們能看到一個test.log檔案被建立,下面的內容會被新增到這個紀錄檔檔案中。

2019/05/24 01:14:13 Error fetching url www.google.com : Get www.google.com: unsupported protocol scheme ""
2019/05/24 01:14:14 Status Code for http://www.google.com : 200 OK

Go Logger的優勢和劣勢

優勢

它最大的優點是使用非常簡單。我們可以設定任何io.Writer作為紀錄檔記錄輸出並向其傳送要寫入的紀錄檔。

劣勢

僅限基本的紀錄檔級別

只有一個Print選項。不支援INFO/DEBUG等多個級別。

對於錯誤紀錄檔,它有fatal和Panic

  • Fatal紀錄檔通過呼叫os.Exit(1)來結束程式
  • Panic紀錄檔在寫入紀錄檔訊息之後丟擲一個panic
  • 但是它缺少一個ERROR紀錄檔級別,這個級別可以在不丟擲panic或退出程式的情況下記錄錯誤

缺乏紀錄檔格式化的能力——例如記錄呼叫者的函數名和行號,格式化日期和時間格式。等等。

不提供紀錄檔切割的能力。

Uber-go Zap紀錄檔庫

Zap是非常快的、結構化的,分紀錄檔級別的Go紀錄檔庫。

為什麼選擇Uber-go zap

  • 它同時提供了結構化紀錄檔記錄和printf風格的紀錄檔記錄
  • 它非常的快

根據Uber-go Zap的檔案,它的效能比類似的結構化紀錄檔包更好——也比標準庫更快。 以下是Zap釋出的基準測試資訊

記錄一條訊息和10個欄位:

PackageTimeTime % to zapObjects Allocated
zap862 ns/op+0%5 allocs/op
zap (sugared)1250 ns/op+45%11 allocs/op
zerolog4021 ns/op+366%76 allocs/op
go-kit4542 ns/op+427%105 allocs/op
apex/log26785 ns/op+3007%115 allocs/op
logrus29501 ns/op+3322%125 allocs/op
log1529906 ns/op+3369%122 allocs/op

記錄一個靜態字串,沒有任何上下文或printf風格的模板:

PackageTimeTime % to zapObjects Allocated
zap118 ns/op+0%0 allocs/op
zap (sugared)191 ns/op+62%2 allocs/op
zerolog93 ns/op-21%0 allocs/op
go-kit280 ns/op+137%11 allocs/op
standard library499 ns/op+323%2 allocs/op
apex/log1990 ns/op+1586%10 allocs/op
logrus3129 ns/op+2552%24 allocs/op
log153887 ns/op+3194%23 allocs/op

安裝

執行下面的命令安裝zap

go get -u go.uber.org/zap

設定Zap Logger

Zap提供了兩種型別的紀錄檔記錄器—Sugared LoggerLogger

在效能很好但不是很關鍵的上下文中,使用SugaredLogger。它比其他結構化紀錄檔記錄包快4-10倍,並且支援結構化和printf風格的紀錄檔記錄。

在每一微秒和每一次記憶體分配都很重要的上下文中,使用Logger。它甚至比SugaredLogger更快,記憶體分配次數也更少,但它只支援強型別的結構化紀錄檔記錄。

Logger

  • 通過呼叫zap.NewProduction()/zap.NewDevelopment()或者zap.Example()建立一個Logger。
  • 上面的每一個函數都將建立一個logger。唯一的區別在於它將記錄的資訊不同。例如production logger預設記錄呼叫函數資訊、日期和時間等。
  • 通過Logger呼叫Info/Error等。
  • 預設情況下紀錄檔都會列印到應用程式的console介面。
var logger *zap.Logger
func main() {
	InitLogger()
  defer logger.Sync()
	simpleHttpGet("www.google.com")
	simpleHttpGet("http://www.google.com")
}
func InitLogger() {
	logger, _ = zap.NewProduction()
}
func simpleHttpGet(url string) {
	resp, err := http.Get(url)
	if err != nil {
		logger.Error(
			"Error fetching url..",
			zap.String("url", url),
			zap.Error(err))
	} else {
		logger.Info("Success..",
			zap.String("statusCode", resp.Status),
			zap.String("url", url))
		resp.Body.Close()
	}
}

在上面的程式碼中,我們首先建立了一個Logger,然後使用Info/ Error等Logger方法記錄訊息。

紀錄檔記錄器方法的語法是這樣的:

func (log *Logger) MethodXXX(msg string, fields ...Field) 

其中MethodXXX是一個可變引數函數,可以是Info / Error/ Debug / Panic等。每個方法都接受一個訊息字串和任意數量的zapcore.Field場引數。

每個zapcore.Field其實就是一組鍵值對引數。

我們執行上面的程式碼會得到如下輸出結果:

{"level":"error","ts":1572159218.912792,"caller":"zap_demo/temp.go:25","msg":"Error fetching url..","url":"www.sogo.com","error":"Get www.sogo.com: unsupported protocol scheme ""","stacktrace":"main.simpleHttpGetnt/Users/q1mi/zap_demo/temp.go:25nmain.mainnt/Users/q1mi/zap_demo/temp.go:14nruntime.mainnt/usr/local/go/src/runtime/proc.go:203"}
{"level":"info","ts":1572159219.1227388,"caller":"zap_demo/temp.go:30","msg":"Success..","statusCode":"200 OK","url":"http://www.sogo.com"}

Sugared Logger

現在讓我們使用Sugared Logger來實現相同的功能。

  • 大部分的實現基本都相同。
  • 惟一的區別是,我們通過呼叫主logger的. Sugar()方法來獲取一個SugaredLogger
  • 然後使用SugaredLoggerprintf格式記錄語句

下面是修改過後使用SugaredLogger代替Logger的程式碼:

var sugarLogger *zap.SugaredLogger
func main() {
	InitLogger()
	defer sugarLogger.Sync()
	simpleHttpGet("www.google.com")
	simpleHttpGet("http://www.google.com")
}
func InitLogger() {
  logger, _ := zap.NewProduction()
	sugarLogger = logger.Sugar()
}
func simpleHttpGet(url string) {
	sugarLogger.Debugf("Trying to hit GET request for %s", url)
	resp, err := http.Get(url)
	if err != nil {
		sugarLogger.Errorf("Error fetching URL %s : Error = %s", url, err)
	} else {
		sugarLogger.Infof("Success! statusCode = %s for URL %s", resp.Status, url)
		resp.Body.Close()
	}
}

當你執行上面的程式碼會得到如下輸出:

{"level":"error","ts":1572159149.923002,"caller":"logic/temp2.go:27","msg":"Error fetching URL www.sogo.com : Error = Get www.sogo.com: unsupported protocol scheme ""","stacktrace":"main.simpleHttpGetnt/Users/q1mi/zap_demo/logic/temp2.go:27nmain.mainnt/Users/q1mi/zap_demo/logic/temp2.go:14nruntime.mainnt/usr/local/go/src/runtime/proc.go:203"}
{"level":"info","ts":1572159150.192585,"caller":"logic/temp2.go:29","msg":"Success! statusCode = 200 OK for URL http://www.sogo.com"}

你應該注意到的了,到目前為止這兩個logger都列印輸出JSON結構格式。

在本部落格的後面部分,我們將更詳細地討論SugaredLogger,並瞭解如何進一步設定它。

客製化logger

將紀錄檔寫入檔案而不是終端

我們要做的第一個更改是把紀錄檔寫入檔案,而不是列印到應用程式控制臺。

我們將使用zap.New(…)方法來手動傳遞所有設定,而不是使用像zap.NewProduction()這樣的預置方法來建立logger。

func New(core zapcore.Core, options ...Option) *Logger

zapcore.Core需要三個設定

1.Encoder:編碼器(如何寫入紀錄檔)。我們將使用開箱即用的NewJSONEncoder(),並使用預先設定的ProductionEncoderConfig()。

   zapcore.NewJSONEncoder(zap.NewProductionEncoderConfig())

2.WriterSyncer :指定紀錄檔將寫到哪裡去。我們使用zapcore.AddSync()函數並且將開啟的檔案控制程式碼傳進去。

   file, _ := os.Create("./test.log")
   writeSyncer := zapcore.AddSync(file)

3.Log Level:哪種級別的紀錄檔將被寫入。

我們將修改上述部分中的Logger程式碼,並重寫InitLogger()方法。其餘方法—main() /SimpleHttpGet()保持不變。

func InitLogger() {
	writeSyncer := getLogWriter()
	encoder := getEncoder()
	core := zapcore.NewCore(encoder, writeSyncer, zapcore.DebugLevel)
	logger := zap.New(core)
	sugarLogger = logger.Sugar()
}
func getEncoder() zapcore.Encoder {
	return zapcore.NewJSONEncoder(zap.NewProductionEncoderConfig())
}
func getLogWriter() zapcore.WriteSyncer {
	file, _ := os.Create("./test.log")
	return zapcore.AddSync(file)
}

當使用這些修改過的logger設定呼叫上述部分的main()函數時,以下輸出將列印在檔案——test.log中。

{"level":"debug","ts":1572160754.994731,"msg":"Trying to hit GET request for www.sogo.com"}
{"level":"error","ts":1572160754.994982,"msg":"Error fetching URL www.sogo.com : Error = Get www.sogo.com: unsupported protocol scheme """}
{"level":"debug","ts":1572160754.994996,"msg":"Trying to hit GET request for http://www.sogo.com"}
{"level":"info","ts":1572160757.3755069,"msg":"Success! statusCode = 200 OK for URL http://www.sogo.com"}

將JSON Encoder更改為普通的Log Encoder

現在,我們希望將編碼器從JSON Encoder更改為普通Encoder。為此,我們需要將NewJSONEncoder()更改為NewConsoleEncoder()

return zapcore.NewConsoleEncoder(zap.NewProductionEncoderConfig())

當使用這些修改過的logger設定呼叫上述部分的main()函數時,以下輸出將列印在檔案——test.log中。

1.572161051846623e+09	debug	Trying to hit GET request for www.sogo.com
1.572161051846828e+09	error	Error fetching URL www.sogo.com : Error = Get www.sogo.com: unsupported protocol scheme ""
1.5721610518468401e+09	debug	Trying to hit GET request for http://www.sogo.com
1.572161052068744e+09	info	Success! statusCode = 200 OK for URL http://www.sogo.com

更改時間編碼並新增呼叫者詳細資訊

鑑於我們對設定所做的更改,有下面兩個問題:

  • 時間是以非人類可讀的方式展示,例如1.572161051846623e+09
  • 呼叫方函數的詳細資訊沒有顯示在紀錄檔中

我們要做的第一件事是覆蓋預設的ProductionConfig(),並進行以下更改:

  • 修改時間編碼器
  • 在紀錄檔檔案中使用大寫字母記錄紀錄檔級別
func getEncoder() zapcore.Encoder {
	encoderConfig := zap.NewProductionEncoderConfig()
	encoderConfig.EncodeTime = zapcore.ISO8601TimeEncoder
	encoderConfig.EncodeLevel = zapcore.CapitalLevelEncoder
	return zapcore.NewConsoleEncoder(encoderConfig)
}

接下來,我們將修改zap logger程式碼,新增將呼叫函數資訊記錄到紀錄檔中的功能。為此,我們將在zap.New(..)函數中新增一個Option

logger := zap.New(core, zap.AddCaller())

當使用這些修改過的logger設定呼叫上述部分的main()函數時,以下輸出將列印在檔案——test.log中。

2019-10-27T15:33:29.855+0800	DEBUG	logic/temp2.go:47	Trying to hit GET request for www.sogo.com
2019-10-27T15:33:29.855+0800	ERROR	logic/temp2.go:50	Error fetching URL www.sogo.com : Error = Get www.sogo.com: unsupported protocol scheme ""
2019-10-27T15:33:29.856+0800	DEBUG	logic/temp2.go:47	Trying to hit GET request for http://www.sogo.com
2019-10-27T15:33:30.125+0800	INFO	logic/temp2.go:52	Success! statusCode = 200 OK for URL http://www.sogo.com

使用Lumberjack進行紀錄檔切割歸檔

這個紀錄檔程式中唯一缺少的就是紀錄檔切割歸檔功能。

Zap本身不支援切割歸檔紀錄檔檔案

為了新增紀錄檔切割歸檔功能,我們將使用第三方庫Lumberjack來實現。

安裝

執行下面的命令安裝Lumberjack

go get -u github.com/natefinch/lumberjack

zap logger中加入Lumberjack

要在zap中加入Lumberjack支援,我們需要修改WriteSyncer程式碼。我們將按照下面的程式碼修改getLogWriter()函數:

func getLogWriter() zapcore.WriteSyncer {
	lumberJackLogger := &lumberjack.Logger{
		Filename:   "./test.log",
		MaxSize:    10,
		MaxBackups: 5,
		MaxAge:     30,
		Compress:   false,
	}
	return zapcore.AddSync(lumberJackLogger)
}

Lumberjack Logger採用以下屬性作為輸入:

Filename: 紀錄檔檔案的位置

MaxSize:在進行切割之前,紀錄檔檔案的最大大小(以MB為單位)

MaxBackups:保留舊檔案的最大個數

MaxAges:保留舊檔案的最大天數

Compress:是否壓縮/歸檔舊檔案

測試所有功能

最終,使用Zap/Lumberjack logger的完整範例程式碼如下:

package main
import (
	"net/http"

	"github.com/natefinch/lumberjack"
	"go.uber.org/zap"
	"go.uber.org/zap/zapcore"
)
var sugarLogger *zap.SugaredLogger
func main() {
	InitLogger()
	defer sugarLogger.Sync()
	simpleHttpGet("www.sogo.com")
	simpleHttpGet("http://www.sogo.com")
}
func InitLogger() {
	writeSyncer := getLogWriter()
	encoder := getEncoder()
	core := zapcore.NewCore(encoder, writeSyncer, zapcore.DebugLevel)

	logger := zap.New(core, zap.AddCaller())
	sugarLogger = logger.Sugar()
}
func getEncoder() zapcore.Encoder {
	encoderConfig := zap.NewProductionEncoderConfig()
	encoderConfig.EncodeTime = zapcore.ISO8601TimeEncoder
	encoderConfig.EncodeLevel = zapcore.CapitalLevelEncoder
	return zapcore.NewConsoleEncoder(encoderConfig)
}
func getLogWriter() zapcore.WriteSyncer {
	lumberJackLogger := &lumberjack.Logger{
		Filename:   "./test.log",
		MaxSize:    1,
		MaxBackups: 5,
		MaxAge:     30,
		Compress:   false,
	}
	return zapcore.AddSync(lumberJackLogger)
}
func simpleHttpGet(url string) {
	sugarLogger.Debugf("Trying to hit GET request for %s", url)
	resp, err := http.Get(url)
	if err != nil {
		sugarLogger.Errorf("Error fetching URL %s : Error = %s", url, err)
	} else {
		sugarLogger.Infof("Success! statusCode = %s for URL %s", resp.Status, url)
		resp.Body.Close()
	}
}

執行上述程式碼,下面的內容會輸出到檔案——test.log中。

2019-10-27T15:50:32.944+0800	DEBUG	logic/temp2.go:48	Trying to hit GET request for www.sogo.com
2019-10-27T15:50:32.944+0800	ERROR	logic/temp2.go:51	Error fetching URL www.sogo.com : Error = Get www.sogo.com: unsupported protocol scheme ""
2019-10-27T15:50:32.944+0800	DEBUG	logic/temp2.go:48	Trying to hit GET request for http://www.sogo.com
2019-10-27T15:50:33.165+0800	INFO	logic/temp2.go:53	Success! statusCode = 200 OK for URL http://www.sogo.com

同時,可以在main函數中迴圈記錄紀錄檔,測試紀錄檔檔案是否會自動切割和歸檔(紀錄檔檔案每1MB會切割並且在當前目錄下最多儲存5個備份)。

至此,我們總結了如何將Zap紀錄檔程式整合到Go應用程式專案中。

以上就是golang預設Logger紀錄檔庫在專案中使用Zap紀錄檔庫的詳細內容,更多關於golang Zap和Logger紀錄檔庫的資料請關注it145.com其它相關文章!


IT145.com E-mail:sddin#qq.com