• 35-gin框架集成zap日志库


    本质上是编写两个中间件,在中间件中使用zap日志库去记录日志。

    Logger() :记录每一次请求相关信息的日志

    Recovery():recover 程序中可能出现的panic,并且记录日志.。

    gin默认的中间件

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

    func main(){
      r:= gin.Default()
      r.GET("/hello",func(c *gin.Context){
        c.String("hello 爱写代码的小男孩")
      })
      r.Run()
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7

    看下gin.Default()的源码:

    func Default() *Engine {
      debugPrintWARINGDefault()
      engine : =New()
      engine.Use(Logger(),Recovery())
      return engine
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

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

    其中Logger()是把 gin框架本身的日志输出到标准输出,而 Recover()是在程序出现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()),
    			zap.String("user-agent", c.Request.UserAgent()),
    			zap.String("errors", c.Errors.ByType(gin.ErrorTypePrivate).String()),
    			zap.Duration("cost", cost),
    		)
    	}
    }
    
    // GinRecovery recover掉项目可能出现的panic
    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()
    	}
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68

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

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

    在gin项目中使用zap

    首先定义一个logger的模块,在logger/logger.go

    package logger
    
    import (
    	"os"
    
    	"go.uber.org/zap"
    	"go.uber.org/zap/zapcore"
    )
    
    // initlogger 初始化日志
    func Init() {
    	// 1、encoder
    	encndercfg := zap.NewProductionEncoderConfig()
    	encndercfg.TimeKey = "time"                          // 改变时间的key
    	encndercfg.EncodeTime = zapcore.ISO8601TimeEncoder   // 更改时间格式
    	encndercfg.EncodeLevel = zapcore.CapitalLevelEncoder //将日志级别大写并带有颜色
    	enconder := zapcore.NewJSONEncoder(encndercfg)
    
    	// 2、writerSyncer 将日志写到文件和终端
    	file, _ := os.OpenFile("./app.log", os.O_CREATE|os.O_APPEND|os.O_WRONLY, 0644)
    	fileWS := zapcore.AddSync(file)
    	consoleWS := zapcore.AddSync(os.Stdout)
    
    	// 3、设置loglevel
    	level := zapcore.DebugLevel
    
    	// 创建zapcore
    	core := zapcore.NewCore(enconder, zapcore.NewMultiWriteSyncer(fileWS, consoleWS), level)
    	// 创建logger
    	logger := zap.New(core)
    
    	// 替换zap全局的logger
    	zap.ReplaceGlobals(logger)
    	zap.L().Info(" logger init success")
    
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36

    在main函数中测试:

    func main() {
    
    	logger.Init()
    	// gin.SetMode(gin.ReleaseMode) // 生产模式下,设置该选项,将不会记录debug的日志
    	r := gin.Default()
    	r.GET("/littleboy", func(c *gin.Context) {
    		zap.L().Info("返回一条日志")
    	})
    
    	r.Run()
    }
    
    // 访问,可以获得自定义的zap日志格式
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13

    但是看到gin框架本身的日志还没有用到zap的日志格式,所以还是要修改默认中间件

    在middleware/middleware.go

    // 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()),
    			zap.String("user-agent", c.Request.UserAgent()),
    			zap.String("errors", c.Errors.ByType(gin.ErrorTypePrivate).String()),
    			zap.Duration("cost", cost),
    		)
    	}
    }
    
    // GinRecovery recover掉项目可能出现的panic
    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()
    	}
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69

    最后在main函数中引用中间件

    func main() {
    
    	logger.Init()
    	// gin.SetMode(gin.ReleaseMode) // 生产模式下,设置该选项,将不会记录debug的日志
    	r := gin.New()
    	r.Use(middleware.GinLogger(zap.L()), middleware.GinRecovery(zap.L(), true))
    	r.GET("/littleboy", func(c *gin.Context) {
    		zap.L().Info("返回一条日志")
    	})
    
    	r.Run()
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13

    测试:

    // app.log中有gin本身的日志还有业务日志
    {"level":"INFO","time":"2022-05-10T18:05:11.527+0800","msg":"/","status":404,"method":"GET","path":"/","query":"","ip":"127.0.0.1","user-agent":"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/101.0.4951.54 Safari/537.36","errors":"","cost":0.000000366}
    {"level":"INFO","time":"2022-05-10T18:05:11.669+0800","msg":"/","status":404,"method":"GET","path":"/","query":"","ip":"127.0.0.1","user-agent":"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/101.0.4951.54 Safari/537.36","errors":"","cost":0.000000596}
    {"level":"INFO","time":"2022-05-10T18:05:11.861+0800","msg":"/","status":404,"method":"GET","path":"/","query":"","ip":"127.0.0.1","user-agent":"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/101.0.4951.54 Safari/537.36","errors":"","cost":0.000000315}
    {"level":"INFO","time":"2022-05-10T18:05:17.971+0800","msg":"返回一条日志"}
    {"level":"INFO","time":"2022-05-10T18:05:17.986+0800","msg":"/littleboy","status":200,"method":"GET","path":"/littleboy","query":"","ip":"127.0.0.1","user-agent":"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/101.0.4951.54 Safari/537.36","errors":"","cost":0.014202597}
    {"level":"INFO","time":"2022-05-10T18:06:05.313+0800","msg":" logger init success"}
    {"level":"INFO","time":"2022-05-10T18:06:10.293+0800","msg":"返回一条日志"}
    {"level":"INFO","time":"2022-05-10T18:06:10.293+0800","msg":"/littleboy","status":200,"method":"GET","path":"/littleboy","query":"","ip":"127.0.0.1","user-agent":"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/101.0.4951.54 Safari/537.36","errors":"","cost":0.000059696}
    {"level":"INFO","time":"2022-05-10T18:06:10.433+0800","msg":"返回一条日志"}
    {"level":"INFO","time":"2022-05-10T18:06:10.433+0800","msg":"/littleboy","status":200,"method":"GET","path":"/littleboy","query":"","ip":"127.0.0.1","user-agent":"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/101.0.4951.54 Safari/537.36","errors":"","cost":0.000064284}
    {"level":"INFO","time":"2022-05-10T18:06:10.588+0800","msg":"返回一条日志"}
    {"level":"INFO","time":"2022-05-10T18:06:10.588+0800","msg":"/littleboy","status":200,"method":"GET","path":"/littleboy","query":"","ip":"127.0.0.1","user-agent":"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/101.0.4951.54 Safari/537.36","errors":"","cost":0.000057682}
    {"level":"INFO","time":"2022-05-10T18:06:13.664+0800","msg":"/","status":404,"method":"GET","path":"/","query":"","ip":"127.0.0.1","user-agent":"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/101.0.4951.54 Safari/537.36","errors":"","cost":0.000000565}
    {"level":"INFO","time":"2022-05-10T18:06:18.079+0800","msg":"/hello","status":404,"method":"GET","path":"/hello","query":"","ip":"127.0.0.1","user-agent":"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/101.0.4951.54 Safari/537.36","errors":"","cost":0.000000949}
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
  • 相关阅读:
    《杨澜访谈录》逐风者|对话智慧医疗风口上的先行者
    OceanBase开源TB级别分布式关系型数据库OceanBase理论详解
    时隔10年谷歌计划重启谷歌实验室,聚焦AR、VR项目
    【用户画像】在ClickHouse中将宽表转换为bitmap表(源码实现)、用户分群架构设计、SpringBoot概述及使用
    Swagger(5):Swagger2常用注解
    备战数学建模41-蒙特卡罗模拟(攻坚战5)
    模型压缩部署概述
    buildroot中C语言使用libconfig的实例
    GaussDB T 0 1级备份 类似rman
    Python利器:os与chardet读取多编码文件
  • 原文地址:https://blog.csdn.net/weixin_38753143/article/details/126227898