new string 默认编码_使用 zap 接收 gin 框架默认的日志并配置日志切割

本文介绍了在基于gin框架开发的项目中如何配置并使用zap来接收gin框架默认的日志以及如何配置日志切割。

我们在基于gin框架开发项目时通常都会选择使用专业的日志库来记录项目中的日志,go语言常用的日志库有zaplogrus等。网上也有很多类似的教程,我之前也翻译过一篇《在Go语言项目中使用Zap日志库》(https://www.liwenzhou.com/posts/Go/zap/)。

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

gin默认的中间件

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

func main() {    r := gin.Default()    r.GET("/hello", func(c *gin.Context) {        c.String("hello q1mi!")    })    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掉项目可能出现的panicfunc 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 loggerimport (    "github.com/gin-gonic/gin"    "github.com/natefinch/lumberjack"    "go.uber.org/zap"    "go.uber.org/zap/zapcore"    "net"    "net/http"    "net/http/httputil"    "os"    "runtime/debug"    "scheduler/config"    "strings"    "time")var Logger *zap.Logger// InitLogger 初始化Loggerfunc 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)    Logger = zap.New(core, zap.AddCaller())    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(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,并使用zap记录相关日志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()    }}

然后定义日志相关配置:

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(cfg.LogConfig)即可完成日志的初始化。

func main() {    // load config from conf/conf.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    }    r := routes.SetupRouter()     addr := fmt.Sprintf(":%v", config.Conf.ServerConfig.Port)    r.Run(addr)}

注册中间件的操作在routes.SetupRouter()中:

func SetupRouter() *gin.Engine {    //gin.SetMode(gin.ReleaseMode)    r := gin.New()    r.Use(logger.GinLogger(logger.Logger), logger.GinRecovery(logger.Logger, true))    mainGroup := r.Group("/api")    {        ...    }    r.GET("/ping", func(c *gin.Context) {        c.String(200, "pong")    })    return r}

推荐阅读

  • 所谓高性能框架是怎么做到的?详解 Gin 的路由算法


喜欢本文的朋友,欢迎关注“Go语言中文网”:

f6c0a4f1eb3a04b03b18fb719c676852.png

Go语言中文网启用微信学习交流群,欢迎加微信:274768166,投稿亦欢迎

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值