You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何在Gin框架中传递Zap Logger至上下文,避免全局实例?

在Gin中集成Zap日志并通过Context传递(非全局实例)

问题描述

我正在使用Gin框架开发项目,近期为项目实现日志功能,对比多款库后选择了Uber的Zap。现有代码结构如下:

控制器代码

package controllers

func ListAllArtists(c *gin.Context) {
    db := c.MustGet("db").(*gorm.DB)
    service := services.NewArtistService(db)
    pagination := entities.PaginationFromRequest(c)
    err := service.FindAll(pagination)
    if err != nil {
        err := c.AbortWithError(http.StatusBadRequest, err)
        if err != nil {
            fmt.Print(err)
        }
        return
    }

    responses.RespondPaginated(c, pagination, service.Artists)
}

路由定义

func ArtistRoutes(router *gin.RouterGroup) {
    artistRouter := router.Group("/artists")
    {
        artistRouter.POST("", controllers.CreateNewArtist)
        artistRouter.GET("", controllers.ListAllArtists)
        artistRouter.GET("/search", controllers.SearchArtists)
        artistRouter.GET("/slug/:slug", controllers.GetArtistBySlug)
        artistRouter.GET("/:id", controllers.GetArtistById)
        artistRouter.DELETE("/:id", controllers.DeleteArtistById)
        artistRouter.PATCH("/:id", controllers.UpdateArtist)
    }
}

Main文件代码

app := core.NewApp()
if app == nil {
    log.Fatal("app could not be initialized. Shutting down")
}

// initialize middlewares.
app.Router.Use(func(c *gin.Context) {
    c.Set("db", app.Db)
})

// serve static files. All static files (images, videos, css, js, etc) will be served from this location.
app.Router.Static("/assets", "./assets")

// Initialize all api routes
routes.InitializeRoutes(app.Router.Group("/api/v1"))

我的问题是:是否有办法传递Logger?能否将其加入c *gin.Context中以实现全局可用?已知Zap提供全局日志实例方案,但不推荐,因此不想采用。


解决方案:通过Gin中间件将Zap Logger注入Context

1. 在Main中初始化Zap并添加全局中间件

先在项目中完成Zap Logger的初始化(可以放在core.NewApp()里,也可以直接在main中实现),然后通过中间件将Logger存入Gin Context:

app := core.NewApp()
if app == nil {
    log.Fatal("app could not be initialized. Shutting down")
}

// 初始化Zap Logger(替换为你实际的初始化逻辑,比如zap.NewProduction())
logger := app.Logger 

// 注入DB和Logger到Context
app.Router.Use(func(c *gin.Context) {
    c.Set("db", app.Db)
    // 使用自定义key避免冲突,比如"app.logger"
    c.Set("app.logger", logger)
})

// 其他原有代码
app.Router.Static("/assets", "./assets")
routes.InitializeRoutes(app.Router.Group("/api/v1"))

2. 在控制器中获取并使用Logger

修改控制器代码,从Context取出Logger,还可以传递给Service层复用:

package controllers

import "go.uber.org/zap"

func ListAllArtists(c *gin.Context) {
    db := c.MustGet("db").(*gorm.DB)
    // 从Context获取Logger,MustGet会在key不存在时panic,也可以用Get做容错处理
    logger := c.MustGet("app.logger").(*zap.Logger)
    
    // 将Logger传递给Service层
    service := services.NewArtistService(db, logger)
    pagination := entities.PaginationFromRequest(c)
    
    err := service.FindAll(pagination)
    if err != nil {
        // 使用Zap打印结构化错误日志
        logger.Error("获取艺术家列表失败", zap.Error(err), zap.Any("分页参数", pagination))
        _ = c.AbortWithError(http.StatusBadRequest, err)
        return
    }

    responses.RespondPaginated(c, pagination, service.Artists)
    // 记录成功日志
    logger.Info("成功获取艺术家列表", zap.Int("总数量", pagination.Total))
}

3. 修改Service层接收Logger参数

更新Service的构造方法,让它接收Logger参数,方便在Service层直接打印日志:

package services

import "go.uber.org/zap"

type ArtistService struct {
    db     *gorm.DB
    logger *zap.Logger
}

func NewArtistService(db *gorm.DB, logger *zap.Logger) *ArtistService {
    return &ArtistService{
        db:     db,
        logger: logger,
    }
}

// 示例:在Service方法中使用日志
func (s *ArtistService) FindAll(pagination *entities.Pagination) error {
    s.logger.Debug("开始查询艺术家列表", zap.Int("页码", pagination.Page), zap.Int("页大小", pagination.PageSize))
    
    // 数据库操作逻辑
    result := s.db.Scopes(entities.Paginate(pagination)).Find(&s.Artists)
    if result.Error != nil {
        s.logger.Error("数据库查询失败", zap.Error(result.Error))
        return result.Error
    }
    
    s.logger.Debug("查询艺术家列表成功", zap.Int("返回数量", len(s.Artists)))
    return nil
}

4. 可选:封装Context获取Logger的工具函数

为了避免重复编写类型断言代码,可以封装一个工具函数:

package utils

import (
    "github.com/gin-gonic/gin"
    "go.uber.org/zap"
)

func GetLogger(c *gin.Context) *zap.Logger {
    return c.MustGet("app.logger").(*zap.Logger)
}

之后在控制器中直接调用:

logger := utils.GetLogger(c)

注意事项

  • 推荐使用自定义key存储Logger(比如"app.logger"),避免与第三方库的Context key冲突
  • 如果需要容错,可以使用Get方法替代MustGet,并做降级处理:
    rawLogger, ok := c.Get("app.logger")
    if !ok {
        // 降级为无操作日志实例
        rawLogger = zap.NewNop()
    }
    logger := rawLogger.(*zap.Logger)
    
  • Zap Logger是并发安全的,可在多goroutine中安全使用,无需额外加锁

内容的提问来源于stack exchange,提问作者MrCujo

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.26 07:54:54