首页 > 其他分享 >使用zap接收gin框架默认的日志并配置日志归档

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

时间:2022-08-24 16:56:23浏览次数:88  
标签:String err func gin 日志 logger zap

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

gin默认的中间件

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

func main() {
    r := gin.Default()
    r.GET("/hello", func(c *gin.Context) {
        c.String("hello liwenzhou.com!")
    })
    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()),
            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()
    }
}

如果不想自己实现,可以使用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 = "q1mi"
            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 liwenzhou.com!")
    })

    addr := fmt.Sprintf(":%v", config.Conf.Port)
    r.Run(addr)
}

 

标签:String,err,func,gin,日志,logger,zap
From: https://www.cnblogs.com/dogHuang/p/16620628.html

相关文章

  • 日志切面类
    packagecn.yto.stl.freight.web.api.common.aspect;importcn.yto.base.exception.IException;importcn.yto.stl.freight.web.api.common.domain.DataResult;importlomb......
  • Nginx 安装篇-yum安装
    yum安装教程引用:https://www.cnblogs.com/AprilBlank/p/11388990.html#1-yum安装推荐 避坑事项:开始安装前检查是否已安装有nginx,注意检查/etc/profile文件配置,下方为......
  • nginx启动访问
      1.双击nginx2.浏览器访问localhost:80(出现以下界面nginx启动成功) ......
  • nginx配置文件语法高亮
    下载nginx.vim支持文件[root@uat-sh-bs-3-b5-nginx-0-11vim74]#cd/usr/share/vim/vim74/syntax/[root@uat-sh-bs-3-b5-nginx-0-11vim74]#wget--no-check-certif......
  • linux查询进程被kill的日志 oom killer
    OOMKilledLinux内核有个机制叫OOMkiller(OutOfMemorykiller),该机制会监控那些占用内存过大,尤其是瞬间占用内存很快的进程,然后防止内存耗尽而自动把该进程杀掉,内核检......
  • Nginx 安装及启动避坑流程
    系统环境:CentOS8.564位【开始安装】(此步骤引自网络教程)https://www.cnblogs.com/torchstar/p/16027538.html教程比较详细,一步步操作就没问题,不过用上面的教程尝试安装......
  • python 二次封装logging,打印日志文件名正确,且正确写入/结合pytest执行,日志不输出的问
    基于之前日志问题,二次封装日志后,导致日志输出的文件名不对,取到的文件一直都是当前二次封装的log的文件名,基于这个问题,做了优化,详细看https://www.cnblogs.com/cuitang/p/1......
  • AtCoder Beginner Contest 263(Java)
    A题桶排序1importjava.util.*;2publicclassMain{3publicstaticvoidmain(String[]args){4Scannersc=newScanner(System.in);5......
  • Kubernetes 平台中的日志收集
    准备关于容器日志Docker的日志分为两类,一类是Docker引擎日志;另一类是容器日志。引擎日志一般都交给了系统日志,不同的操作系统会放在不同的位置。本文主要介绍容器日......
  • 为Nginx服务添加ServiceEntry和workloadEntry对象,治理目标为网格外部服务的出向流量
    测试在网格内部访问网格外部服务我是开了三台云主机每台都安装下docker和docker-compose第一台[root@VM-0-12-centos~]#catDeploy-Nginx/docker-compose.ymlversio......