Go语言使用zap接收Gin框架默认日志并配置归档
在构建 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 语言后端开发实践的资料请关注本站其它相关文章!
本站声明
本文内容仅供参考,不代表本站观点。转载或引用请注明出处。如发现内容问题,请通过联系我们页面反馈。













