如何在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
相关产品推荐
相关产品推荐

