• Gin框架使用Zap日志库


    使用zap接收gin框架默认的日志并配置日志归档

    基于gin框架开发的项目中如何配置并使用zap来接收并记录gin框架默认的日志和如何配置日志归档。

    我们在基于gin框架开发项目时通常都会选择使用专业的日志库来记录项目中的日志,go语言常用的日志库有zaplogrus等。网上也有很多类似的教程

    但是我们该如何在日志中记录gin框架本身输出的那些日志呢?

    一、gin默认的中间件

    首先我们来看一个最简单的gin项目:

    func main() {
    	r := gin.Default()
    	r.GET("/hello", func(c *gin.Context) {
    		c.String("hello gin!")
    	})
    	r.Run(
    }
    

    接下来我们看一下gin.Default()的源码:

    func Default() *Engine {
    	debugPrintWARNINGDefault()
    	engine := New()
    	engine.Use(Logger(), Recovery())
    	return engine
    }
    

    也就是我们在使用gin.Default()的同时是用到了gin框架内的两个默认中间件Logger()Recovery()

    其中Logger()是把gin框架本身的日志输出到标准输出(我们本地开发调试时在终端输出的那些日志就是它的功劳),而Recovery()是在程序出现panic的时候恢复现场并写入500响应的。

    二、基于zap的中间件

    我们可以模仿Logger()Recovery()的实现,使用我们的日志库来接收gin框架默认输出的日志。

    这里以zap为例,我们实现两个中间件如下:

    // GinLogger 接收gin框架默认的日志
    func GinLogger(logger *zap.Logger) 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)
    		logger.Info(path,
    			zap.Int("status", c.Writer.Status()),                                 // 状态码
    			zap.String("method", c.Request.Method),                               // 请求的方法
    			zap.String("path", path),                                             // 请求的路径
    			zap.String("query", query),                                           // 请求的参数
    			zap.String("ip", c.ClientIP()),                                       // 请求的IP
    			zap.String("user-agent", c.Request.UserAgent()),                      // 请求头
    			zap.String("errors", c.Errors.ByType(gin.ErrorTypePrivate).String()), // 错误信息
    			zap.Duration("cost", cost),                                           // 请求时间
    		)
    	}
    }
    
    // GinRecovery recover掉项目可能出现的panic stack是否记录堆栈信息
    func GinRecovery(logger *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 {
    					logger.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: errcheck
    					c.Abort()
    					return
    				}
    
    				if stack {
    					logger.Error("[Recovery from panic]",
    						zap.Any("error", err),
    						zap.String("request", string(httpRequest)),
    						zap.String("stack", string(debug.Stack())),
    					)
    				} else {
    					logger.Error("[Recovery from panic]",
    						zap.Any("error", err),
    						zap.String("request", string(httpRequest)),
    					)
    				}
    				c.AbortWithStatus(http.StatusInternalServerError)
    			}
    		}()
    		c.Next()
    	}
    }
    
    

    如果不想自己实现,可以使用github上有别人封装好的https://github.com/gin-contrib/zap。

    这样我们就可以在gin框架中使用我们上面定义好的两个中间件来代替gin框架默认的Logger()Recovery()了。

    r := gin.New()
    r.Use(GinLogger(), GinRecovery())
    

    三、在gin项目中使用zap

    最后我们再加入我们项目中常用的日志切割,完整版的logger.go代码如下:

    package logger
    
    import (
    	"gin_zap_demo/config"
    	"net"
    	"net/http"
    	"net/http/httputil"
    	"os"
    	"runtime/debug"
    	"strings"
    	"time"
    
    	"github.com/gin-gonic/gin"
    	"github.com/natefinch/lumberjack"
    	"go.uber.org/zap"
    	"go.uber.org/zap/zapcore"
    )
    
    var lg *zap.Logger
    
    // InitLogger 初始化Logger
    func InitLogger(cfg *config.LogConfig) (err error) {
    	writeSyncer := getLogWriter(cfg.Filename, cfg.MaxSize, cfg.MaxBackups, cfg.MaxAge)
    	encoder := getEncoder()
    	var l = new(zapcore.Level)
    	err = l.UnmarshalText([]byte(cfg.Level))
    	if err != nil {
    		return
    	}
    	core := zapcore.NewCore(encoder, writeSyncer, l)
    
    	lg = zap.New(core, zap.AddCaller())
    	zap.ReplaceGlobals(lg) // 替换zap包中全局的logger实例,后续在其他包中只需使用zap.L()调用即可
    	return
    }
    
    func getEncoder() zapcore.Encoder {
    	encoderConfig := zap.NewProductionEncoderConfig()
    	encoderConfig.EncodeTime = zapcore.ISO8601TimeEncoder
    	encoderConfig.TimeKey = "time"
    	encoderConfig.EncodeLevel = zapcore.CapitalLevelEncoder
    	encoderConfig.EncodeDuration = zapcore.SecondsDurationEncoder
    	encoderConfig.EncodeCaller = zapcore.ShortCallerEncoder
    	return zapcore.NewJSONEncoder(encoderConfig)
    }
    
    func getLogWriter(filename string, maxSize, maxBackup, maxAge int) zapcore.WriteSyncer {
    	lumberJackLogger := &lumberjack.Logger{
    		Filename:   filename,
    		MaxSize:    maxSize,
    		MaxBackups: maxBackup,
    		MaxAge:     maxAge,
    	}
    	return zapcore.AddSync(lumberJackLogger)
    }
    
    // GinLogger 接收gin框架默认的日志
    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)
    		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("ip", c.ClientIP()),
    			zap.String("user-agent", c.Request.UserAgent()),
    			zap.String("errors", c.Errors.ByType(gin.ErrorTypePrivate).String()),
    			zap.Duration("cost", cost),
    		)
    	}
    }
    
    // GinRecovery recover掉项目可能出现的panic,并使用zap记录相关日志
    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 {
    					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: errcheck
    					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()
    	}
    }
    

    然后定义日志相关配置:

    type LogConfig struct {
    	Level string `json:"level"`
    	Filename string `json:"filename"`
    	MaxSize int `json:"maxsize"`
    	MaxAge int `json:"max_age"`
    	MaxBackups int `json:"max_backups"`
    }
    

    在项目中先从配置文件加载配置信息,再调用logger.InitLogger(config.Conf.LogConfig)即可完成logger实例的初识化。其中,通过r.Use(logger.GinLogger(), logger.GinRecovery(true))注册我们的中间件来使用zap接收gin框架自身的日志,在项目中需要的地方通过使用zap.L().Xxx()方法来记录自定义日志信息。

    package main
    
    import (
    	"fmt"
    	"gin_zap_demo/config"
    	"gin_zap_demo/logger"
    	"net/http"
    	"os"
    
    	"go.uber.org/zap"
    
    	"github.com/gin-gonic/gin"
    )
    
    func main() {
    	// load config from config.json
    	if len(os.Args) < 1 {
    		return
    	}
    
    	if err := config.Init(os.Args[1]); err != nil {
    		panic(err)
    	}
    	// init logger
    	if err := logger.InitLogger(config.Conf.LogConfig); err != nil {
    		fmt.Printf("init logger failed, err:%v\n", err)
    		return
    	}
    
    	gin.SetMode(config.Conf.Mode)
    
    	r := gin.Default()
    	// 注册zap相关中间件
    	r.Use(logger.GinLogger(), logger.GinRecovery(true))
    
    	r.GET("/hello", func(c *gin.Context) {
    		// 假设你有一些数据需要记录到日志中
    		var (
    			name = "r"
    			age  = 18
    		)
    		// 记录日志并使用zap.Xxx(key, val)记录相关字段
    		zap.L().Debug("this is hello func", zap.String("user", name), zap.Int("age", age))
    
    		c.String(http.StatusOK, "hello!")
    	})
    
    	addr := fmt.Sprintf(":%v", config.Conf.Port)
    	r.Run(addr)
    }
    
  • 相关阅读:
    从远程库克隆(转载)
    添加远程库(转载)
    远程仓库(转载)
    maven+hudson构建集成测试平台
    maven_基本配置
    crawler_基础之_httpclient 访问网络资源
    crawler_jsoup HTML解析器_使用选择器语法来查找元素
    oracle_job 清空冗余数据 ,每一分钟执行一次
    oracle_根据ID(字符型)建立分区表
    crawler_基础之_java.net.HttpURLConnection 访问网络资源
  • 原文地址:https://www.cnblogs.com/randysun/p/15841410.html
Copyright © 2020-2023  润新知