Gin框架使用Zap接收日志的实现

Written by

in

文章目录
  • 日志对于项目重要性不言而喻,如果用过Gin框架大家都知道,Gin框架中自带日志logger;可以文件和控制台输出,但性能和功能远不如Zap。
  • 下面简单写个例子 func main() { r := gin.New() // 创建一个不包含中间件的路由器 r.Use(gin.Logger(), gin.Recovery()) // 使用 Logger 中间件、Recovery 中间件 r.GET(“/hello”, func(c *gin.Context) { // 路由添加 c.String(200, “hello!”) }) r.Run(“:9090”) } 浏览器访问http://127.0.0.1:9090/hello,控制台会输出一下日志内容:
  • 目录
    • 前言
    • 一、Gin使用默认日志中间件
    • 二、Zap日志中间件
      • 1.封装Zap日志包
      • 2.封装logger和recover的Zap日志

    日志对于项目重要性不言而喻,如果用过Gin框架大家都知道,Gin框架中自带日志logger;可以文件和控制台输出,但性能和功能远不如Zap。

    下面简单写个例子

    func main() {
    	r := gin.New()                         // 创建一个不包含中间件的路由器
    	r.Use(gin.Logger(), gin.Recovery())    // 使用 Logger 中间件、Recovery 中间件
    	r.GET("/hello", func(c *gin.Context) { // 路由添加
    		c.String(200, "hello!")
    	})
    	r.Run(":9090")
    }

    浏览器访问http://127.0.0.1:9090/hello,控制台会输出一下日志内容:

    我在Gin框架下创建了一个pkg目录,创建了一个glog包,包含三个文件zap.go、sugar.go、logger.go。里面涉及的global的配置大家可以下载项目看看(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打印时将整个调用的stack链路会存放到内存中,默认打印调用处的caller信息。所以需要再初始化zap时额外增加AddCallerSkip跳过指定层级的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,                                 // 进行切割之前,日志文件的最大大小(MB为单位)
    		MaxBackups: global.Config.Log.MaxBackups,                              // 保留旧文件的最大个数
    		MaxAge:     global.Config.Log.MaxAge,                                  // 保留旧文件的最大天数
    		Compress:   global.Config.Log.Compress,                                // 是否压缩/归档旧文件
    	}
    
    	return zapcore.AddSync(lumberJackLogger)
    }
    
    // CustomTimeEncoder 自定义日志输出时间格式
    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...)
    }
    

    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框架默认的日志
    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的内容读取出来
    			bodyBytes, _ := ioutil.ReadAll(c.Request.Body)
    			c.Request.Body.Close()
    			// 把刚刚读出来的再写进去
    			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项目可能出现的panic,并使用zap记录相关日志
    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)) 替换默认的Logger()、Recovery()的中间件,运行项目中注册接口,会看到,日志文件中输出记录

    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}

    到此这篇关于Gin框架使用Zap接收日志的实现的文章就介绍到这了,更多相关Gin Zap接收日志内容请搜索风君子博客以前的文章或继续浏览下面的相关文章希望大家以后多多支持风君子博客!

    您可能感兴趣的文章:

    • zap接收gin框架默认的日志并配置日志归档示例
    • golang框架gin的日志处理和zap lumberjack日志使用方式

    站内搜索