欧美bbbwbbbw肥妇,免费乱码人妻系列日韩,一级黄片

Go語(yǔ)言Zap庫(kù)Logger的定制化和封裝使用詳解

 更新時(shí)間:2022年06月25日 11:29:55   作者:kevinyan  
這篇文章主要介紹了Go語(yǔ)言Zap庫(kù)Logger的定制化和封裝使用詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪

前言

日志無(wú)論對(duì)于程序還是程序員都非常重要,有多重要呢,想要長(zhǎng)期在公司健健康康的干下去就得學(xué)會(huì)階段性劃水,階段性劃水的一大關(guān)鍵的就是干活快過(guò)預(yù)期但是裝作。。。不對(duì),這個(gè)開(kāi)頭不對(duì)勁,下面重來(lái)。

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

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

  • 支持把日志寫(xiě)入到多個(gè)輸出流中,比如可以選擇性的讓測(cè)試、開(kāi)發(fā)環(huán)境同時(shí)向控制臺(tái)和日志文件輸出日志,生產(chǎn)環(huán)境只輸出到文件中。
  • 支持多級(jí)別的日志等級(jí),比如常見(jiàn)的有:TRACE,DEBUGINFO,WARNERROR 等。
  • 支持結(jié)構(gòu)化輸出,結(jié)構(gòu)化輸出現(xiàn)在常用的就是JSON形式的,這樣可以讓統(tǒng)一日志平臺(tái),通過(guò) logstash 之類(lèi)的組件直接把日志聚合到日志平臺(tái)上去。
  • 需要支持日志切割 -- log rotation, 按照日期、時(shí)間間隔或者文件大小對(duì)日志進(jìn)行切割。
  • 在 Log Entry 中(就是每行記錄)除了主動(dòng)記錄的信息外,還要包括如打印日志的函數(shù)、所在的文件、行號(hào)、記錄時(shí)間等。

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

Go 語(yǔ)言原生的Logger

Go 語(yǔ)言自帶 log 內(nèi)置包,為我們提供了一個(gè)默認(rèn)的 Logger,可以直接使用。 這個(gè)庫(kù)的詳細(xì)用法可以在官方的文檔里找到:pkg.go.dev/log

使用 log 記錄日志,默認(rèn)會(huì)輸出到控制臺(tái)中。比如下面這個(gè)例子:

package main
import (
	"log"
	"net/http"
)
func main() {
	simpleHttpGet("www.google.com")
	simpleHttpGet("https://www.baidu.com")
}
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()
	}
	return
}

這個(gè)例程中,分別向兩個(gè)網(wǎng)址進(jìn)行 GET 請(qǐng)求,然后記錄了一下返回狀態(tài)碼 / 請(qǐng)求錯(cuò)誤。 執(zhí)行程序后會(huì)有類(lèi)似輸出:

2022/05/15 15:15:26 Error fetching url www.baidu.com : Get "www.baidu.com": unsupported protocol scheme "" 2022/05/15 15:15:26 Status Code for https://www.baidu.com : 200 OK

因?yàn)榈谝淮握?qǐng)求的 URL 中協(xié)議頭缺失, 所以不能成功發(fā)起請(qǐng)求,日志也很好的記錄了錯(cuò)誤信息。

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

大家可以自己試一下運(yùn)行效果,這里不再做過(guò)多演示。

Go 語(yǔ)言原生Logger的缺點(diǎn)

原生 Logger 的優(yōu)點(diǎn),顯而易見(jiàn),簡(jiǎn)單、開(kāi)箱即用,不用引用外部的三方庫(kù)。我們可以按照開(kāi)頭處提出的對(duì)于一個(gè) Logger 的五個(gè)標(biāo)準(zhǔn)再看一下默認(rèn)Logger 是否能在項(xiàng)目里使用。

  • 僅限基本的日志級(jí)別
    • 只有一個(gè)Print選項(xiàng)。不支持INFO/DEBUG等多個(gè)級(jí)別。
  • 對(duì)于錯(cuò)誤日志,它有FatalPanic
    • Fatal日志通過(guò)調(diào)用os.Exit(1)來(lái)結(jié)束程序
    • Panic日志在寫(xiě)入日志消息之后拋出一個(gè)panic
    • 但是它缺少一個(gè)ERROR日志級(jí)別,這個(gè)級(jí)別可以在不拋出panic或退出程序的情況下記錄錯(cuò)誤
  • 缺乏結(jié)構(gòu)化日志格式的能力——只支持簡(jiǎn)單文本輸出,不能把日志記錄格式化成 JSON 格式。
  • 不提供日志切割的能力。

Zap 日志庫(kù)

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

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

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

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

上面向日志里寫(xiě)入了一條記錄,Message 是 "Success.." 另外寫(xiě)入了兩個(gè)字符串鍵值對(duì)。 Zap 針對(duì)日志里要寫(xiě)入的字段,每個(gè)類(lèi)型都有一個(gè)對(duì)應(yīng)的方法把字段轉(zhuǎn)成 zap.Field 類(lèi)型 。比如:

zap.Int('key', 123)
zap.Bool('key', true)
zap.Error('err', err)
zap.Any('arbitraryType', &User{})

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

下面我們先來(lái)學(xué)習(xí)一下 Zap 的使用方法,再對(duì)項(xiàng)目中使用 Zap 時(shí)做些自定義的配置和封裝,讓它變得更好用,最重要的是匹配上我們開(kāi)頭提出的關(guān)于好的 Logger 的五條標(biāo)準(zhǔn)。

Zap 的使用方法

安裝zap

首先說(shuō)一下,zap 的安裝方式,直接運(yùn)行以下命令下載 zap 到本地的依賴(lài)庫(kù)中。

go get -u go.uber.org/zap

設(shè)置 Logger

我們先說(shuō) zap 提供的配置好的 Logger ,稍后會(huì)對(duì)它進(jìn)行自定義。

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

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

func (log *Logger) Info(msg string, fields ...Field) {
	if ce := log.check(InfoLevel, msg); ce != nil {
		ce.Write(fields...)
	}
}

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

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

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

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

定制 Zap 的 Logger

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

下面少說(shuō)話(huà),直接上代碼,必要的解釋放在了注釋里。

var logger *zap.Logger
func init() {
	encoderConfig := zap.NewProductionEncoderConfig()
  // 設(shè)置日志記錄中時(shí)間的格式
	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(
		// 同時(shí)向控制臺(tái)和文件寫(xiě)日志, 生產(chǎn)環(huán)境記得把控制臺(tái)寫(xiě)入去掉,日志記錄的基本是Debug 及以上,生產(chǎn)環(huán)境記得改成Info
		zapcore.NewCore(encoder, zapcore.AddSync(os.Stdout), zapcore.DebugLevel),
		zapcore.NewCore(encoder, fileWriteSyncer, zapcore.DebugLevel),
	)
	logger = zap.New(core)
}

日志切割

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

func getFileLogWriter() (writeSyncer zapcore.WriteSyncer) {
	// 使用 lumberjack 實(shí)現(xiàn) log rotate
	lumberJackLogger := &lumberjack.Logger{
		Filename:   "/tmp/test.log",
		MaxSize:    100, // 單個(gè)文件最大100M
		MaxBackups: 60, // 多于 60 個(gè)日志文件后,清理較舊的日志
		MaxAge:     1, // 一天一切割
		Compress:   false,
	}
	return zapcore.AddSync(lumberJackLogger)
}

封裝 Logger

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

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

我們對(duì) Logger 再做一下封裝。

// 發(fā)送私信 go-logger 給公眾號(hào)「網(wǎng)管叨bi叨」
// 可獲得完整代碼和使用Demo
package zlog
// 簡(jiǎn)單封裝一下對(duì) zap 日志庫(kù)的使用
// 使用方式:
// 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))
var logger *zap.Logger
func init() {
	......
}
func getFileLogWriter() (writeSyncer zapcore.WriteSyncer) {
	......
}
func Info(message string, fields ...zap.Field) {
	callerFields := getCallerInfoForLog()
	fields = append(fields, callerFields...)
	logger.Info(message, fields...)
}
func Debug(message string, fields ...zap.Field) {
	callerFields := getCallerInfoForLog()
	fields = append(fields, callerFields...)
	logger.Debug(message, fields...)
}
func Error(message string, fields ...zap.Field) {
	callerFields := getCallerInfoForLog()
	fields = append(fields, callerFields...)
	logger.Error(message, fields...)
}
func Warn(message string, fields ...zap.Field) {
	callerFields := getCallerInfoForLog()
	fields = append(fields, callerFields...)
	logger.Warn(message, fields...)
}
func getCallerInfoForLog() (callerFields []zap.Field) {
	pc, file, line, ok := runtime.Caller(2) // 回溯兩層,拿到寫(xiě)日志的調(diào)用方的函數(shù)信息
	if !ok {
		return
	}
	funcName := runtime.FuncForPC(pc).Name()
	funcName = path.Base(funcName) //Base函數(shù)返回路徑的最后一個(gè)元素,只保留函數(shù)名
	callerFields = append(callerFields, zap.String("func", funcName), zap.String("file", file), zap.Int("line", line))
	return
}

為啥不用 zap.New(core, zap.AddCaller())這種方式,在日志行里添加調(diào)用方的信息呢?主要還是想更靈活點(diǎn),能自己制定對(duì)應(yīng)的日志字段,所以把 Caller的幾個(gè)信息放到單獨(dú)的字段里,等把日志收集到日志平臺(tái)上去后,查詢(xún)?nèi)罩镜臅r(shí)候也更利于檢索。

在下面的例程中嘗試使用我們封裝好的日志 Logger 做個(gè)簡(jiǎn)單的測(cè)試。

package main
import (
	"example.com/utils/zlog"
)
type User strunct {
  Name  stirng
}
func main() {
  user := &User{
    "Name": "Kevin"
  }
  zlog.Info("test log", zap.Any("user", user))
}

輸出類(lèi)似下面的輸出。

{"level":"info","ts":"2022-05-15T21:22:22.687+0800","msg":"test log","res":{"Name":"Kevin"},"func":"main.Main","file":"/Users/Kevin/go/src/example.com/demo/zap.go","line":84}

總結(jié)

關(guān)于 Zap Logger 的定制化和封裝,這里只是舉了一些基本又必要的入門(mén)級(jí)定制化,等大家掌握后,可以參照官方文檔提供的接口進(jìn)行更多定制化。

源碼鏈接 https://github.com/go-study-lab/go-http-server/blob/master/utils/zlog/log.go

更多關(guān)于Go語(yǔ)言Zap庫(kù)Logger定制化封裝的資料請(qǐng)關(guān)注腳本之家其它相關(guān)文章!

相關(guān)文章

  • Golang?WebSocket創(chuàng)建單獨(dú)會(huì)話(huà)詳細(xì)實(shí)例

    Golang?WebSocket創(chuàng)建單獨(dú)會(huì)話(huà)詳細(xì)實(shí)例

    這篇文章主要給大家介紹了關(guān)于Golang?WebSocket創(chuàng)建單獨(dú)會(huì)話(huà)的相關(guān)資料,WebSocket 協(xié)議主要為了解決基于 HTTP/1.x 的 Web 應(yīng)用無(wú)法實(shí)現(xiàn)服務(wù)端向客戶(hù)端主動(dòng)推送的問(wèn)題,文中通過(guò)代碼介紹的非常詳細(xì),需要的朋友可以參考下
    2023-11-11
  • Go語(yǔ)言中的字符串拼接方法詳情

    Go語(yǔ)言中的字符串拼接方法詳情

    本文介紹Go語(yǔ)言中的string類(lèi)型、strings包和bytes.Buffer類(lèi)型,介紹幾種字符串拼接方法的相關(guān)資料,需要的朋友可以參考一下,希望對(duì)你有所幫助
    2021-10-10
  • 詳解如何用Golang處理每分鐘100萬(wàn)個(gè)請(qǐng)求

    詳解如何用Golang處理每分鐘100萬(wàn)個(gè)請(qǐng)求

    在項(xiàng)目開(kāi)發(fā)中,我們常常會(huì)遇到處理來(lái)自數(shù)百萬(wàn)個(gè)端點(diǎn)的大量POST請(qǐng)求,本文主要介紹了Golang實(shí)現(xiàn)處理每分鐘100萬(wàn)個(gè)請(qǐng)求的方法,希望對(duì)大家有所幫助
    2023-04-04
  • Go語(yǔ)言的常量、枚舉、作用域示例詳解

    Go語(yǔ)言的常量、枚舉、作用域示例詳解

    這篇文章主要介紹了Go語(yǔ)言的常量、枚舉、作用域,接下來(lái),我們將詳細(xì)了解 Go 的變量作用域規(guī)則以及這些規(guī)則如何影響代碼編寫(xiě),需要的朋友可以參考下
    2024-07-07
  • 詳解Go中指針的原理與引用

    詳解Go中指針的原理與引用

    在?Go?中,指針是強(qiáng)大而重要的功能,它允許開(kāi)發(fā)人員直接處理內(nèi)存地址并實(shí)現(xiàn)高效的數(shù)據(jù)操作,本文主要帶大家了解下指針在?Go?中的工作原理以及對(duì)于編寫(xiě)高效、高性能代碼的重要性,希望對(duì)大家有所幫助
    2023-09-09
  • go各種import的使用方法講解

    go各種import的使用方法講解

    今天小編就為大家分享一篇關(guān)于go各種import的使用方法講解,小編覺(jué)得內(nèi)容挺不錯(cuò)的,現(xiàn)在分享給大家,具有很好的參考價(jià)值,需要的朋友一起跟隨小編來(lái)看看吧
    2019-04-04
  • Go語(yǔ)言性能監(jiān)控和調(diào)優(yōu)的工具和方法

    Go語(yǔ)言性能監(jiān)控和調(diào)優(yōu)的工具和方法

    本文介紹了Go語(yǔ)言性能監(jiān)控和調(diào)優(yōu)的工具和方法,包括?pprof、expvar?和?trace?等工具的使用方法和注意事項(xiàng),以及性能調(diào)優(yōu)的一些常見(jiàn)方法,如減少內(nèi)存分配、避免頻繁的垃圾回收、避免過(guò)度查詢(xún)數(shù)據(jù)庫(kù)等,針對(duì)不同的程序,應(yīng)該根據(jù)實(shí)際情況采用不同的優(yōu)化方法
    2024-01-01
  • Go?gRPC服務(wù)進(jìn)階middleware使用教程

    Go?gRPC服務(wù)進(jìn)階middleware使用教程

    這篇文章主要為大家介紹了Go?gRPC服務(wù)進(jìn)階middleware的使用教程,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪
    2022-06-06
  • Gin框架中參數(shù)校驗(yàn)優(yōu)化詳解

    Gin框架中參數(shù)校驗(yàn)優(yōu)化詳解

    這篇文章主要為大家詳細(xì)介紹了Gin框架中參數(shù)校驗(yàn)優(yōu)化的相關(guān)知識(shí),文中的示例代碼講解詳細(xì),具有一定的學(xué)習(xí)價(jià)值,感興趣的小伙伴可以了解下
    2023-08-08
  • Go結(jié)構(gòu)體指針引發(fā)的值傳遞思考分析

    Go結(jié)構(gòu)體指針引發(fā)的值傳遞思考分析

    這篇文章主要為大家介紹了Go結(jié)構(gòu)體指針引發(fā)的值傳遞思考分析,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪
    2023-12-12

最新評(píng)論