Go语言使用zap接收Gin框架默认日志并配置归档

Owen
Owen
发布时间:2026-10-16 | 3人浏览过
原创

在构建 Go 微服务或 Web 应用时,通常推荐使用高性能的 zap 日志库替代标准库。本文将演示如何通过编写自定义中间件,拦截 Gin 框架的默认日志输出,并结合 lumberjack 实现日志文件的自动轮转与归档,确保生产环境下的日志管理既规范又节省存储。

获取 Gin 框架底层日志原理

Gin 框架初始化引擎时默认挂载了两个关键中间件:Logger 与 Recovery。理解这两者的运作逻辑是实现日志接管的基础。


// gin.Default() 内部源码简览
func Default() *Engine {
    engine := New()
    // 核心:注入 Logger(打印HTTP请求)和 Recovery(捕获panic)
    engine.Use(Logger(), Recovery())
    return engine
}
  • Logger 中间件:负责在终端或控制台输出 HTTP 请求的方法、路径、状态码、耗时等基础信息。
  • Recovery 中间件:用于捕获程序运行时的 Panic 错误,防止服务直接崩溃退出。

若要实现日志归档,需要新建一套逻辑来取代默认的 Logger() 和 Recovery(),转而利用 zap.Logger 实例进行记录。

创建 zap 日志中间件

首先定义用于处理正常 HTTP 请求的 GinLogger 中间件,其次定义用于处理异常的 GinRecovery 中间件。

第1步:实现 GinLogger 中间件

目的:在请求结束后,收集关键信息并发送至 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),
        )
    }
}

第2步:实现 GinRecovery 中间件

目的:捕获业务代码中的未知 Panic,区分普通断开连接与严重错误,避免堆栈污染,并进行安全恢复。


// GinRecovery recover掉项目可能出现的panic
func GinRecovery(logger *zap.Logger, stack bool) gin.HandlerFunc {
    return func(c *gin.Context) {
        defer func() {
            if err := recover(); err != nil {
                // 检查是否为断开的连接等非致命错误
                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)),
                    )
                    c.Error(err.(error))
                    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()
    }
}

完成上述两个函数后,在项目入口需替换默认的中间件调用方式:


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

配置日志文件归档 (Lumberjack)

为了便于排查问题,需要将本地输出重定向到文件,并进行定期切割。这里我们引入第三方库 github.com/natefinch/lumberjack。

第3步:完善 Logger.go 模块

以下代码封装了初始化、编码器配置以及日志写入器的设置,实现了完整的日志生命周期管理。


package logger

import (
	"gin_zap_demo/config"
	"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) // 替换全局logger
	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,      // MB为单位,达到此大小触发分割
		MaxBackups: maxBackup,    // 保留旧日志的最大数量
		MaxAge:     maxAge,       // 保留旧日志的最大天数
	}
	return zapcore.AddSync(lumberJackLogger)
}

// 复用上面的 GinLogger 和 GinRecovery 逻辑... 
// ... (此处省略重复代码,引用上文定义的函数结构)

第4步:定义配置文件与启动

创建 config/log_config.go 存放配置参数:


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"`
}

最后在 main 函数中加载配置并启动 Gin 引擎:


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() {
    // 1. 加载配置文件
    config.Init(os.Args[1])
    
    // 2. 初始化自定义 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()
    
    // 3. 注册新的中间件,覆盖默认的 GinLogger/GinRecovery
    r.Use(logger.GinLogger(), logger.GinRecovery(true))

    r.GET("/hello", func(c *gin.Context) {
        // 4. 使用全局zap记录自定义日志
        name := "q1mi"
        age := 18
        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)
}

通过以上步骤,即可实现在 Gin 开发中全面接管日志流,并利用 lumberjack 实现自动化的磁盘空间管理与日志归档。以上就是 zap 接收 Gin 框架默认日志并配置日志归档示例的详细内容,更多关于 Go 语言后端开发实践的资料请关注本站其它相关文章!

相关标签:

本站声明

本文内容仅供参考,不代表本站观点。转载或引用请注明出处。如发现内容问题,请通过联系我们页面反馈。

Python 相关产品和应用

全部下载

专题列表