Gin框架使用Zap接收日志的實(shí)現(xiàn)
前言
日志對(duì)于項(xiàng)目重要性不言而喻,如果用過(guò)Gin框架大家都知道,Gin框架中自帶日志logger;可以文件和控制臺(tái)輸出,但性能和功能遠(yuǎn)不如Zap。
一、Gin使用默認(rèn)日志中間件
下面簡(jiǎn)單寫(xiě)個(gè)例子
func main() { r := gin.New() // 創(chuàng)建一個(gè)不包含中間件的路由器 r.Use(gin.Logger(), gin.Recovery()) // 使用 Logger 中間件、Recovery 中間件 r.GET("/hello", func(c *gin.Context) { // 路由添加 c.String(200, "hello!") }) r.Run(":9090") }
瀏覽器訪問(wèn)http://127.0.0.1:9090/hello,控制臺(tái)會(huì)輸出一下日志內(nèi)容:
二、Zap日志中間件
1.封裝Zap日志包
我在Gin框架下創(chuàng)建了一個(gè)pkg目錄,創(chuàng)建了一個(gè)glog包,包含三個(gè)文件zap.go、sugar.go、logger.go。里面涉及的global的配置大家可以下載項(xiàng)目看看(https://gitee.com/tfc2016/gino)
zap.go
package glog import ( "gino/global" "github.com/natefinch/lumberjack" "go.uber.org/zap" "go.uber.org/zap/zapcore" "time" ) var ( logger *zap.Logger sugar *zap.SugaredLogger Logger *zap.Logger ) func NewZapLogger() { writeSyncer := getLogWriter() encoder := getEncoder() core := zapcore.NewCore(encoder, writeSyncer, zapcore.DebugLevel) // zap打印時(shí)將整個(gè)調(diào)用的stack鏈路會(huì)存放到內(nèi)存中,默認(rèn)打印調(diào)用處的caller信息。所以需要再初始化zap時(shí)額外增加AddCallerSkip跳過(guò)指定層級(jí)的caller logger = zap.New(core, zap.AddCaller(), zap.AddCallerSkip(1)) defer logger.Sync() Logger = logger sugar = Logger.Sugar() } // getEncoder zapcore.Encoder func getEncoder() zapcore.Encoder { encoderConfig := zap.NewProductionEncoderConfig() encoderConfig.EncodeTime = CustomTimeEncoder if global.Config.Log.StacktraceKey != "" { encoderConfig.StacktraceKey = global.Config.Log.StacktraceKey } if global.Config.Log.Format == "json" { return zapcore.NewJSONEncoder(encoderConfig) } return zapcore.NewConsoleEncoder(encoderConfig) } func getLogWriter() zapcore.WriteSyncer { lumberJackLogger := &lumberjack.Logger{ Filename: global.Config.Log.Path + "/" + global.Config.Log.Filename, // 日志文件位置 MaxSize: global.Config.Log.MaxSize, // 進(jìn)行切割之前,日志文件的最大大小(MB為單位) MaxBackups: global.Config.Log.MaxBackups, // 保留舊文件的最大個(gè)數(shù) MaxAge: global.Config.Log.MaxAge, // 保留舊文件的最大天數(shù) Compress: global.Config.Log.Compress, // 是否壓縮/歸檔舊文件 } return zapcore.AddSync(lumberJackLogger) } // CustomTimeEncoder 自定義日志輸出時(shí)間格式 func CustomTimeEncoder(t time.Time, enc zapcore.PrimitiveArrayEncoder) { enc.AppendString(t.Format("2006-01-02 15:04:05.000")) }
sugar.go
package glog // Debug uses fmt.Sprint to construct and log a message. func Debug(args ...interface{}) { sugar.Debug(args) } // Info uses fmt.Sprint to construct and log a message. func Info(args ...interface{}) { sugar.Info(args) } // Warn uses fmt.Sprint to construct and log a message. func Warn(args ...interface{}) { sugar.Warn(args) } // Error uses fmt.Sprint to construct and log a message. func Error(args ...interface{}) { sugar.Error(args) } // DPanic uses fmt.Sprint to construct and log a message. In development, the // logger then panics. (See DPanicLevel for details.) func DPanic(args ...interface{}) { sugar.DPanic(args) } // Panic uses fmt.Sprint to construct and log a message, then panics. func Panic(args ...interface{}) { sugar.Panic(args) } // Fatal uses fmt.Sprint to construct and log a message, then calls os.Exit. func Fatal(args ...interface{}) { sugar.Fatal(args) } // Debugf uses fmt.Sprintf to log a templated message. func Debugf(template string, args ...interface{}) { sugar.Debugf(template, args) } // Infof uses fmt.Sprintf to log a templated message. func Infof(template string, args ...interface{}) { sugar.Infof(template, args, nil) } // Warnf uses fmt.Sprintf to log a templated message. func Warnf(template string, args ...interface{}) { sugar.Warnf(template, args, nil) } // Errorf uses fmt.Sprintf to log a templated message. func Errorf(template string, args ...interface{}) { sugar.Errorf(template, args, nil) } // DPanicf uses fmt.Sprintf to log a templated message. In development, the // logger then panics. (See DPanicLevel for details.) func DPanicf(template string, args ...interface{}) { sugar.DPanicf(template, args, nil) } // Panicf uses fmt.Sprintf to log a templated message, then panics. func Panicf(template string, args ...interface{}) { sugar.Panicf(template, args, nil) } // Fatalf uses fmt.Sprintf to log a templated message, then calls os.Exit. func Fatalf(template string, args ...interface{}) { sugar.Fatalf(template, args, nil) }
logger.go(此文件主要是Gorm輸出日志采用Zap)
package glog import ( "go.uber.org/zap" ) func ZapDebug(msg string, fields ...zap.Field) { logger.Debug(msg, fields...) } func ZapInfo(msg string, fields ...zap.Field) { logger.Info(msg, fields...) } func ZapWarn(msg string, fields ...zap.Field) { logger.Warn(msg, fields...) } func ZapError(msg string, fields ...zap.Field) { logger.Error(msg, fields...) } func ZapDPanic(msg string, fields ...zap.Field) { logger.DPanic(msg, fields...) } func ZapPanic(msg string, fields ...zap.Field) { logger.Panic(msg, fields...) } func ZapFatal(msg string, fields ...zap.Field) { logger.Fatal(msg, fields...) }
2.封裝logger和recover的Zap日志
package middleware import ( "bytes" "github.com/gin-gonic/gin" jsoniter "github.com/json-iterator/go" "go.uber.org/zap" "io/ioutil" "net" "net/http" "net/http/httputil" "os" "runtime/debug" "strings" "time" ) // ZapLogger 接收gin框架默認(rèn)的日志 func ZapLogger(lg *zap.Logger) gin.HandlerFunc { return func(c *gin.Context) { start := time.Now() path := c.Request.URL.Path query := c.Request.URL.RawQuery post := "" if c.Request.Method == "POST" { // 把request的內(nèi)容讀取出來(lái) bodyBytes, _ := ioutil.ReadAll(c.Request.Body) c.Request.Body.Close() // 把剛剛讀出來(lái)的再寫(xiě)進(jìn)去 c.Request.Body = ioutil.NopCloser(bytes.NewBuffer(bodyBytes)) switch c.ContentType() { case "application/json": var result map[string]interface{} d := jsoniter.NewDecoder(bytes.NewReader(bodyBytes)) d.UseNumber() if err := d.Decode(&result); err == nil { bt, _ := jsoniter.Marshal(result) post = string(bt) } default: post = string(bodyBytes) } } c.Next() cost := time.Since(start) lg.Info(path, zap.Int("status", c.Writer.Status()), zap.String("method", c.Request.Method), zap.String("path", path), zap.String("query", query), zap.String("post", post), 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), ) } } // ZapRecovery recover項(xiàng)目可能出現(xiàn)的panic,并使用zap記錄相關(guān)日志 func ZapRecovery(lg *zap.Logger, 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 { lg.Error(c.Request.URL.Path, zap.Any("error", err), zap.String("request", string(httpRequest)), ) // If the connection is dead, we can't write a status to it. c.Error(err.(error)) // nolint: err check c.Abort() return } if stack { lg.Error("[Recovery from panic]", zap.Any("error", err), zap.String("request", string(httpRequest)), zap.String("stack", string(debug.Stack())), ) } else { lg.Error("[Recovery from panic]", zap.Any("error", err), zap.String("request", string(httpRequest)), ) } c.AbortWithStatus(http.StatusInternalServerError) } }() c.Next() } }
使用 Use(middleware.ZapLogger(glog.Logger), middleware.ZapRecovery(glog.Logger, true)) 替換默認(rèn)的Logger()、Recovery()的中間件,運(yùn)行項(xiàng)目中注冊(cè)接口,會(huì)看到,日志文件中輸出記錄
18:21:24.044","caller":"gin@v1.7.7/context.go:168","msg":"/api/v1/register","status":400,"method":"POST","path":"/api/v1/register","query":"","post":"{\"username\":\"ceshi\",\"password\":\"123456\"}","ip":"127.0.0.1","user-agent":"PostmanRuntime/7.29.0","errors":"","cost":0.0129476}
到此這篇關(guān)于Gin框架使用Zap接收日志的實(shí)現(xiàn)的文章就介紹到這了,更多相關(guān)Gin Zap接收日志內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
Go?數(shù)據(jù)結(jié)構(gòu)之二叉樹(shù)詳情
這篇文章主要介紹了?Go?數(shù)據(jù)結(jié)構(gòu)之二叉樹(shù)詳情,二叉樹(shù)是一種數(shù)據(jù)結(jié)構(gòu),在每個(gè)節(jié)點(diǎn)下面最多存在兩個(gè)其他節(jié)點(diǎn)。即一個(gè)節(jié)點(diǎn)要么連接至一個(gè)、兩個(gè)節(jié)點(diǎn)或不連接其他節(jié)點(diǎn),下文基于GO語(yǔ)言展開(kāi)二叉樹(shù)結(jié)構(gòu)詳情,需要的朋友可以參考一下2022-05-05GO語(yǔ)言文件的創(chuàng)建與打開(kāi)實(shí)例分析
這篇文章主要介紹了GO語(yǔ)言文件的創(chuàng)建與打開(kāi)的具體用法,實(shí)例分析了GO語(yǔ)言文件創(chuàng)建與打開(kāi)操作中所涉及的函數(shù)具體用法,具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2014-12-12Go語(yǔ)言面向?qū)ο笾械亩鄳B(tài)你學(xué)會(huì)了嗎
面向?qū)ο笾械亩鄳B(tài)(Polymorphism)是指一個(gè)對(duì)象可以具有多種不同的形態(tài)或表現(xiàn)方式,本文將通過(guò)一些簡(jiǎn)單的示例為大家講解一下多態(tài)的實(shí)現(xiàn),需要的可以參考下2023-07-07Golang?gRPC?HTTP協(xié)議轉(zhuǎn)換示例
這篇文章主要為大家介紹了Golang?gRPC?HTTP協(xié)議轉(zhuǎn)換示例,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2022-06-06詳解Go語(yǔ)言的context包從放棄到入門(mén)
這篇文章主要介紹了Go語(yǔ)言的context包從放棄到入門(mén),本文通過(guò)實(shí)例演示給大家介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2020-12-12