最新国产好看的视频,伊人天堂AV在线,国产Aaaaaa视频,蜜臀视频在线观看一区,人妻av色图,密臀久久久精品影片,青青视频免费观看毛片,久草在线观看视,国产三级精品色情在线

Golang定制化zap日志庫使用過程分析

 更新時間:2023年05月18日 17:31:16   作者:捶捶自己  
Zap是我個人比較喜歡的日志庫,是uber開源的,有較好的性能,在項目開發(fā)中,經(jīng)常需要把程序運行過程中各種信息記錄下來,有了詳細的日志有助于問題排查和功能優(yōu)化,但如何選擇和使用性能好功能強大的日志庫,這個就需要我們從多角度考慮

前言

本文主要介紹Go語言日志庫如何簡易定制化,以及如何在開發(fā)中使用。

為什么需要日志

一個產(chǎn)品的誕生一定是因為有需求!新技術(shù)大部分都是為了更加便利和實用而誕生的,日志也不例外。日志顧名思義就是對整個項目的事件進行記錄。日志可以幫助我們查看某一天中某一時刻項目的運轉(zhuǎn)情況等等。

日志的好處

在日常開發(fā)過程中難免會遇到BUG出現(xiàn)的情況,日志可以記錄這些BUG出現(xiàn)的地點從而方便進行快速定位和排查。可以根據(jù)需求對日志進行自定義的輸出,比如輸出到控制臺、文件等。日志也可以幫助我們在開發(fā)過程中檢測到程序潛在的問題和程序運行的流程,能夠有效的提高我們的開發(fā)效率。

日志都有什么

要讓程序記錄有效的,便利的日志。** Logger (日志記錄器) 應(yīng)該具備以下特點**:

  • 可以將日志信息輸出到控制臺、文件等地方,輸出到文件便于項目長久運行,輸出到控制臺有助于開發(fā)過程中檢錯的效率。
  • 一個日志應(yīng)該具有多個基本的級別,比如infodebug,warn,error,fatal等,他們可以對日志進行分類。
  • 可以對日志進行切割,按照日志大小、日期、時間間隔等因素分割。
  • 可以手動或自動記錄一些開發(fā)信息。如前端傳入的數(shù)據(jù),異常錯誤信息,程序運行結(jié)果,錯誤行數(shù),日志打印位置等等信息進行打印。

Go中默認的日志

Go語言中默認集成了一個log日志庫

func New(out io.Writer, prefix string, flag int) *Logger {
   l := &Logger{out: out, prefix: prefix, flag: flag}
   if out == io.Discard {
      l.isDiscard = 1
   }
   return l
}

使用New可以獲取到該日志對象。第一個參數(shù)為實現(xiàn)了Writer接口的對象。可以使用os.OpenFile()選擇一個文件,然后將該文件對象作為輸出,也可以使用os.Stdoutos.Stderr輸出到控制臺。第二個參數(shù)需要傳入一個日志信息每一行的前綴(如果輸出到控制臺該處可以填空字符串)。第三個參數(shù)是設(shè)置打印默認信息的能力,比如打印時間等。

測試日志

var l *log.Logger
func main() {
	l.Printf("main method exec fail, err: %v", errors.New("nil Pointer error"))
	l.Println("test go log status")
	l.Fatal("wait five seconds")
	time.Sleep(time.Second * 5)
	l.Println("five seconds after!")
}
func init() {
	l = log.New(os.Stdout, "[我是一個前綴]", log.LstdFlags)
}

打印信息

[我是一個前綴]2023/02/10 21:15:22 main method exec fail, err: nil Pointer error

[我是一個前綴]2023/02/10 21:15:22 test go log status

[我是一個前綴]2023/02/10 21:15:22 wait five seconds

// Fatal is equivalent to l.Print() followed by a call to os.Exit(1).
func (l *Logger) Fatal(v ...any) {
   l.Output(2, fmt.Sprint(v...))
   os.Exit(1)
}

Fatal之后的程序均不會執(zhí)行,因為Fatal執(zhí)行后會在內(nèi)部調(diào)用os.Exit(1),從而在打印結(jié)束后退出進程。

goLogger的不足

  • 日志級別只支持Fatal,只有一個Print函數(shù),沒有其他級別
  • 日志自定義參數(shù)過少,無法打印棧信息,無法確定請求位置等
  • FatalPainc都是執(zhí)行后退出,無法容忍錯誤情況的出現(xiàn)就會退出程序
  • 無法指定輸出格式,只能以文本形式進行輸出,沒有根據(jù)日志大小、時間間隔、日期進行分割的能力

雖然gologger支持并發(fā),但也只限于簡單用著還行,實際開發(fā)用起來并不舒服的情況。

Zap日志庫

引入日志庫依賴

go get -u go.uber.org/zap

zap日志庫是Uber開源的。性能很好,因為不用反射實現(xiàn),但需要自己去手動指明打印信息的類型(下面會有示例)。個人覺得自己指定打印還是挺舒服的。zap的使用率非常高,不僅支持日志庫的基本功能,而且很靈活的支持你去進一步的封裝或者定制化。zap支持異步打印。

如何使用zap

格式化配置

func NewDevelopmentEncoderConfig() zapcore.EncoderConfig
func NewProductionEncoderConfig() zapcore.EncoderConfig
func NewProductionConfig() Config
func NewDevelopmentConfig() Config

這里可以根據(jù)實際生產(chǎn)和測試環(huán)境需求進行選擇,也可以直接使用其他初始化方式。

// NewProductionEncoderConfig returns an opinionated EncoderConfig for
// production environments.
func NewProductionEncoderConfig() zapcore.EncoderConfig {
   return zapcore.EncoderConfig{
       // 設(shè)置log內(nèi)容里的一些屬性的key
      TimeKey:        "ts",//時間對應(yīng)的key名
      LevelKey:       "level",//日志級別對應(yīng)的key名
      NameKey:        "logger",//logger名對應(yīng)的key名
      CallerKey:      "caller",//調(diào)用者對應(yīng)的key名
      FunctionKey:    zapcore.OmitKey,
      MessageKey:     "msg",//日志內(nèi)容對應(yīng)的key名,此參數(shù)必須不為空,否則日志主體不處理
      StacktraceKey:  "stacktrace",//棧追蹤的key名
       // const DefaultLineEnding = "\n" 行末輸出格式
      LineEnding:     zapcore.DefaultLineEnding,
       // 日志編碼級別
      EncodeLevel:    zapcore.LowercaseLevelEncoder,
       // 日志時間解析
      EncodeTime:     zapcore.EpochTimeEncoder,
       // 日志日期解析
      EncodeDuration: zapcore.SecondsDurationEncoder,
       // 日志調(diào)用路徑
      EncodeCaller:   zapcore.ShortCallerEncoder,
   }
}

使用NewProductionEncoderConfig()創(chuàng)建的 Logger 在記錄日志時會自動記錄調(diào)用函數(shù)的信息、打日志的時間,日志級別等信息。

EncodeLevel

// A LevelEncoder serializes a Level to a primitive type.
type LevelEncoder func(Level, PrimitiveArrayEncoder)
// 將日志級別進行大寫并帶上顏色
func CapitalColorLevelEncoder(l Level, enc PrimitiveArrayEncoder)
// 將日志級別大寫不帶顏色
func CapitalLevelEncoder(l Level, enc PrimitiveArrayEncoder)
// 將日志級別小寫帶上顏色
func LowercaseColorLevelEncoder(l Level, enc PrimitiveArrayEncoder)
// 將日志級別小寫不帶顏色
func LowercaseLevelEncoder(l Level, enc PrimitiveArrayEncoder)

需要實現(xiàn)LevelEncoder接口??梢哉{(diào)整日志編碼級別,并且選擇帶上或者不帶輸出顏色。

EncodeTime

// A TimeEncoder serializes a time.Time to a primitive type.
type TimeEncoder func(time.Time, PrimitiveArrayEncoder)
// 根據(jù)不同時間進行格式化
func EpochTimeEncoder(t time.Time, enc PrimitiveArrayEncoder)
func EpochMillisTimeEncoder(t time.Time, enc PrimitiveArrayEncoder)
func EpochNanosTimeEncoder(t time.Time, enc PrimitiveArrayEncoder)

定制化時間格式解析,需要實現(xiàn)TimeEncoder接口。

EncodeDruation

// A DurationEncoder serializes a time.Duration to a primitive type.
type DurationEncoder func(time.Duration, PrimitiveArrayEncoder)
// 將日期根據(jù)不同時間進行格式化
func SecondsDurationEncoder(d time.Duration, enc PrimitiveArrayEncoder)
func NanosDurationEncoder(d time.Duration, enc PrimitiveArrayEncoder)
func MillisDurationEncoder(d time.Duration, enc PrimitiveArrayEncoder)
func StringDurationEncoder(d time.Duration, enc PrimitiveArrayEncoder)

定制日期格式解析。需要實現(xiàn)DruationEncoder接口

定制化zap

編碼格式

encoderConfig := zap.NewProductionEncoderConfig()
// 打印級別為大寫 & 彩色
encoderConfig.EncodeLevel = zapcore.CapitalColorLevelEncoder
// 時間編碼進行指定格式解析 layout -> "[2006-01-02 15:04:05]"
encoderConfig.EncodeTime = parseTime(settings.Conf.Layout)

修改日志打印級別和時間編碼格式

// parseTime 進行時間格式處理
func parseTime(layout string) zapcore.TimeEncoder {
   return func(t time.Time, enc zapcore.PrimitiveArrayEncoder) {
      type appendTimeEncoder interface {
         AppendTimeLayout(time.Time, string)
      }
      if enc, ok := enc.(appendTimeEncoder); ok {
         enc.AppendTimeLayout(t, layout)
         return
      }
      enc.AppendString(t.Format(layout))
   }
}

實現(xiàn)zapcore.TimeEncoder接口,將指定的Layout參數(shù)進行傳入實現(xiàn)閉包即可。

日志分割

// 日志輸出配置, 借助另外一個庫 lumberjack 協(xié)助完成日志切割。
lumberjackLogger := &lumberjack.Logger{
   Filename:   settings.Conf.Filename,   // -- 日志文件名
   MaxSize:    settings.Conf.MaxSize,    // -- 最大日志數(shù) M為單位!!!
   MaxAge:     settings.Conf.MaxAge,     // -- 最大存在天數(shù)
   MaxBackups: settings.Conf.MaxBackups, // -- 最大備份數(shù)量
   Compress:   false,                    // --是否壓縮
}
syncer := zapcore.AddSync(lumberjackLogger)

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

// -- 用于開發(fā)者模式和生產(chǎn)模式之間的切換
var core zapcore.Core
if settings.Conf.AppConfig.Mode == "debug" {
   encoder := zapcore.NewConsoleEncoder(encoderConfig) // 輸出控制臺編碼格式
   core = zapcore.NewTee(
      zapcore.NewCore(encoder, syncer, zapcore.DebugLevel), // debug級別打印到日志文件
      zapcore.NewCore(encoder, zapcore.Lock(os.Stdout), zapcore.DebugLevel), // debug級別打印到控制臺
   )
} else {
   encoder := zapcore.NewJSONEncoder(encoderConfig)// 輸出Json格式,便于日志檢索
   core = zapcore.NewCore(encoder, syncer, zapcore.InfoLevel)// info級別打印到日志文件
}
lg := zap.New(core, zap.AddCaller()) // --添加函數(shù)調(diào)用信息

根據(jù)配置信息去選擇具體打印需求。

zap.ReplaceGlobals(lg)               // 替換該日志為全局日志
var (
	_globalMu sync.RWMutex
	_globalL  = NewNop()
)
// L returns the global Logger, which can be reconfigured with ReplaceGlobals.
// It's safe for concurrent use.
func L() *Logger {
	_globalMu.RLock()
	l := _globalL
	_globalMu.RUnlock()
	return l
}

設(shè)置該日志為全局日志,將原日志進行替換,即可在任意位置使用zap.L()調(diào)用該日志。

完整代碼

// init 初始化日志庫
func init() {
   encoderConfig := zap.NewProductionEncoderConfig()
   // 打印級別為大寫 & 彩色
   encoderConfig.EncodeLevel = zapcore.CapitalColorLevelEncoder
   // 時間編碼進行指定格式解析
   encoderConfig.EncodeTime = parseTime(settings.Conf.Layout)
   // 日志輸出配置, 借助另外一個庫 lumberjack 協(xié)助完成日志切割。
   lumberjackLogger := &lumberjack.Logger{
      Filename:   settings.Conf.Filename,   // -- 日志文件名
      MaxSize:    settings.Conf.MaxSize,    // -- 最大日志數(shù) M為單位!!!
      MaxAge:     settings.Conf.MaxAge,     // -- 最大存在天數(shù)
      MaxBackups: settings.Conf.MaxBackups, // -- 最大備份數(shù)量
      Compress:   false,                    // --是否壓縮
   }
   syncer := zapcore.AddSync(lumberjackLogger)
   // -- 用于開發(fā)者模式和生產(chǎn)模式之間的切換
   var core zapcore.Core
   if settings.Conf.AppConfig.Mode == "debug" {
      encoder := zapcore.NewConsoleEncoder(encoderConfig)
      core = zapcore.NewTee(
         zapcore.NewCore(encoder, syncer, zapcore.DebugLevel),
         zapcore.NewCore(encoder, zapcore.Lock(os.Stdout), zapcore.DebugLevel),
      )
   } else {
      encoder := zapcore.NewJSONEncoder(encoderConfig)
      core = zapcore.NewCore(encoder, syncer, zapcore.InfoLevel)
   }
   lg := zap.New(core, zap.AddCaller()) // --添加函數(shù)調(diào)用信息
   zap.ReplaceGlobals(lg)               // 替換該日志為全局日志
}
// parseTime 進行時間格式處理
func parseTime(layout string) zapcore.TimeEncoder {
   return func(t time.Time, enc zapcore.PrimitiveArrayEncoder) {
      type appendTimeEncoder interface {
         AppendTimeLayout(time.Time, string)
      }
      if enc, ok := enc.(appendTimeEncoder); ok {
         enc.AppendTimeLayout(t, layout)
         return
      }
      enc.AppendString(t.Format(layout))
   }
}

測試日志打印情況

zap.L().Info("test info", zap.String("test String", "ok"), zap.Int("test cnt", 1))
zap.L().Debug("test debug", zap.String("test String", "ok"), zap.Int("test cnt", 2))
zap.L().Error("test error", zap.String("test String", "ok"), zap.Int("test cnt", 3))

[2023-02-10 22:22:17] INFO xxxxx/main.go:22 test info {“test String”: “ok”, “test cnt”: 1}

[2023-02-10 22:22:17] DEBUG xxxxx/main.go:23 test debug {“test String”: “ok”, “test cnt”: 2}

[2023-02-10 22:22:17] ERROR xxxxx/main.go:24 test error {“test String”: “ok”, “test cnt”: 3}

這里就是上述所說的自指定類型進行輸出的情況。

結(jié)合gin框架進行使用

雖然gin框架有自帶的logger中間件,但我們可以根據(jù)gin框架實現(xiàn)的原生日志和異?;謴?fù)中間件進行改造并進行替換。

Loger

// GinLogger 替換gin中默認的logger
func GinLogger() gin.HandlerFunc {
	return func(c *gin.Context) {
		start := time.Now()
		path := c.Request.URL.Path
		query := c.Request.URL.RawQuery
		c.Next()
	cost := time.Since(start)
	if c.Writer.Status() != http.StatusOK {
		// 記錄異常信息
		zap.L().Error(query,
			zap.Int("status", c.Writer.Status()),
			zap.String("method", c.Request.Method),
			zap.String("path", path),
			zap.String("ip", c.ClientIP()),
			zap.String("user-agent", c.Request.UserAgent()),
			zap.String("errors", c.Errors.ByType(gin.ErrorTypePrivate).String()),
			zap.Duration("cost", cost),
		)
	}
}

如果有錯誤請求,只要不是狀態(tài)碼為200的全部進行打印->狀態(tài)碼、請求方法(get、post…)、路徑、ip、用戶授權(quán)方、錯誤信息、請求花費時間。

// GinRecovery recover掉項目可能出現(xiàn)的panic
func GinRecovery(stack bool) gin.HandlerFunc {
	return func(c *gin.Context) {
		defer func() {
			if err := recover(); err != nil {
				// Check for a broken connection, as it is not really a
				// condition that warrants a panic stack trace.
				var brokenPipe bool
				if ne, ok := err.(*net.OpError); ok {
					if se, ok := ne.Err.(*os.SyscallError); ok {
						if strings.Contains(strings.ToLower(se.Error()), "broken pipe") ||
							strings.Contains(strings.ToLower(se.Error()), "connection reset by peer") {
							brokenPipe = true
						}
					}
				}
				httpRequest, _ := httputil.DumpRequest(c.Request, false)
				if brokenPipe {
					zap.L().Error(c.Request.URL.Path,
						zap.Any("error", err),
						zap.String("httpRequest", string(httpRequest)),
					)
					// If the connection is dead, we can't write a status to it.
					c.Error(err.(error)) // nolint: errcheck
					c.Abort()
					return
				}
                // 這里可以選擇全部打印出來不必要分割然后循環(huán)輸出
				request := strings.Split(string(httpRequest), "\r\n")
				split := strings.Split(string(debug.Stack()), "\n\t")
				if stack {
					zap.L().Error("[Recovery from panic]",
						zap.Any("error", err))
					for _, str := range request {
						zap.L().Error("[Recovery from request panic]", zap.String("request", str))
					}
					for _, str := range split {
						zap.L().Error("[Recovery from Stack panic]", zap.String("stack", str))
					}
				} else {
					zap.L().Error("[Recovery from panic]",
						zap.Any("error", err))
					for _, str := range request {
						zap.L().Error("[Recovery from request panic]", zap.String("request", str))
					}
				}
				c.AbortWithStatus(http.StatusInternalServerError)
			}
		}()
		c.Next()
	}
}

這里在Panic的時候我采用了分割循環(huán)打印的方法,也可以全部輸出,但是一堆異常情況,不容易看清楚。也可以選擇不打印棧軌跡輸出,只需要在使用recover中間件時傳入false參數(shù)即可。

小結(jié)

zap日志可以靈活的定制時間、編碼輸出格式、顏色等信息。

zap日志級別豐富,不利用反射,效率高,但需要手動對類型進行定義。

到此這篇關(guān)于Golang定制化zap日志庫使用過程分析的文章就介紹到這了,更多相關(guān)Go定制化zap日志庫內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!

相關(guān)文章

  • 淺談Go連接池的設(shè)計與實現(xiàn)

    淺談Go連接池的設(shè)計與實現(xiàn)

    本文主要介紹了淺談Go連接池的設(shè)計與實現(xiàn),文中通過示例代碼介紹的非常詳細,對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧
    2023-04-04
  • gRPC的發(fā)布訂閱模式及REST接口和超時控制

    gRPC的發(fā)布訂閱模式及REST接口和超時控制

    這篇文章主要為大家介紹了gRPC的發(fā)布訂閱模式及REST接口和超時控制,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進步,早日升職加薪
    2022-06-06
  • Go語言數(shù)據(jù)結(jié)構(gòu)之插入排序示例詳解

    Go語言數(shù)據(jù)結(jié)構(gòu)之插入排序示例詳解

    這篇文章主要為大家介紹了Go語言數(shù)據(jù)結(jié)構(gòu)之插入排序示例詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進步,早日升職加薪
    2022-08-08
  • GoLang的sync.WaitGroup與sync.Once簡單使用講解

    GoLang的sync.WaitGroup與sync.Once簡單使用講解

    sync.WaitGroup類型,它比通道更加適合實現(xiàn)這種一對多的goroutine協(xié)作流程。WaitGroup是開箱即用的,也是并發(fā)安全的。同時,與之前提到的同步工具一樣,它一旦被真正的使用就不能被復(fù)制了
    2023-01-01
  • go json轉(zhuǎn)換實踐中遇到的坑

    go json轉(zhuǎn)換實踐中遇到的坑

    在使用 go 語言開發(fā)過程中,經(jīng)常需要使用到 json 包來進行 json 和 struct 的互相轉(zhuǎn)換,這篇文章主要介紹了go json轉(zhuǎn)換實踐中遇到的坑,非常具有實用價值,需要的朋友可以參考下
    2018-12-12
  • Golang Defer基礎(chǔ)操作詳解

    Golang Defer基礎(chǔ)操作詳解

    在golang當(dāng)中,defer代碼塊會在函數(shù)調(diào)用鏈表中增加一個函數(shù)調(diào)用。這個函數(shù)調(diào)用不是普通的函數(shù)調(diào)用,而是會在函數(shù)正常返回,也就是return之后添加一個函數(shù)調(diào)用。因此,defer通常用來釋放函數(shù)內(nèi)部變量
    2022-10-10
  • 一文深入探討Go語言中的if-else語句

    一文深入探討Go語言中的if-else語句

    在Go語言中,條件語句的使用方式相對簡潔明了,所以本文將探討一下如何在Go程序中有效地進行條件判斷和邏輯控制,感興趣的小伙伴可以了解下
    2023-08-08
  • 使用Go語言實現(xiàn)谷歌翻譯功能

    使用Go語言實現(xiàn)谷歌翻譯功能

    這篇文章主要為大家詳細介紹了如何使用Go語言實現(xiàn)谷歌翻譯功能,文中的示例代碼講解詳細,具有一定的借鑒價值,有需要的小伙伴可以參考下
    2024-02-02
  • Go語言實現(xiàn)的樹形結(jié)構(gòu)數(shù)據(jù)比較算法實例

    Go語言實現(xiàn)的樹形結(jié)構(gòu)數(shù)據(jù)比較算法實例

    這篇文章主要介紹了Go語言實現(xiàn)的樹形結(jié)構(gòu)數(shù)據(jù)比較算法,實例分析了樹形結(jié)構(gòu)數(shù)據(jù)比較算法的實現(xiàn)技巧,具有一定參考借鑒價值,需要的朋友可以參考下
    2015-02-02
  • Go中的字典Map增刪改查、排序及其值類型

    Go中的字典Map增刪改查、排序及其值類型

    本文詳細介紹了Go語言中Map的基本概念、聲明初始化、增刪改查操作、反轉(zhuǎn)、排序以及如何判斷鍵是否存在等操作,Map是一種基于鍵值對的無序數(shù)據(jù)結(jié)構(gòu),鍵必須是支持相等運算符的類型,值可以是任意類型,初始化Map時推薦指定容量以提高性能
    2024-09-09

最新評論

牡丹江市| 久治县| 吴川市| 浦北县| 锡林郭勒盟| 宝应县| 潞西市| 曲沃县| 乌鲁木齐县| 洮南市| 浏阳市| 淮滨县| 隆回县| 松滋市| 乐至县| 九江县| 无棣县| 武汉市| 修武县| 湘阴县| 南通市| 清河县| 蒙自县| 高雄县| 鄄城县| 太保市| 罗源县| 永胜县| 正蓝旗| 囊谦县| 宁远县| 日喀则市| 休宁县| 西和县| 静安区| 新乡县| 萨迦县| 固阳县| 遂平县| 巧家县| 合山市|