Gin:自定义日志、验证器与中间件全指南

张开发
2026/4/20 21:19:25 15 分钟阅读

分享文章

Gin:自定义日志、验证器与中间件全指南
前言在使用 Gin 开发 Web 服务时默认的功能已经能覆盖大部分场景但在生产环境中我们往往需要更精细的控制——比如定制日志格式以便于 ELK 采集、增加业务专属的参数校验规则、或者编写通用的请求拦截中间件。Gin 本身提供了非常优雅的扩展机制本文将结合四个实际示例带你彻底掌握 Gin 中四个常用自定义能力的实现方法。自定义HTTP访问日志格式Gin默认的访问日志长这样[GIN] 2023/04/19 - 10:41:52 | 200 | 122.767µs | ::1 | GET /ping虽然信息完整但格式较为固定。如果我们需要与现有日志平台对接或希望记录更多字段例如 User-Agent、错误信息等可以使用gin.LoggerWithFormatter自定义日志输出格式。func main() { router : gin.New() // 使用自定义格式的日志中间件 router.Use(gin.LoggerWithFormatter(func(param gin.LogFormatterParams) string { // 返回你想要的日志行格式 return fmt.Sprintf(%s - [%s] \%s %s %s %d %s \%s\ %s\\n, param.ClientIP, //用户ipv6地址 param.TimeStamp.Format(time.RFC1123), //访问时间 param.Method, //访问方法 param.Path, //访问路径 param.Request.Proto, //http版本 param.StatusCode, //状态码 param.Latency, //请求处理耗时 param.Request.UserAgent(), //客户端User-Agent头 param.ErrorMessage, //错误信息 ) })) router.Use(gin.Recovery()) router.GET(/ping, func(c *gin.Context) { c.String(200, pong) }) router.Run(:8080) }如果想输出到文件中可以这样做f, _ : os.Create(gin.log) gin.DefaultWriter f自定义路由日志在启动 Gin 服务时控制台会打印所有注册的路由信息[GIN-debug] POST /foo -- main.main.func1 (3 handlers) [GIN-debug] GET /bar -- main.main.func2 (3 handlers) [GIN-debug] GET /status -- main.main.func3 (3 handlers)这些信息对调试很有帮助但格式无法变更。Gin 提供了gin.DebugPrintRouteFunc变量允许我们接管路由日志的输出。func main() { r : gin.Default() gin.DebugPrintRouteFunc func(httpMethod, absolutePath, handlerName string, nuHandlers int) { // 方法资源路径函数名函数个数 log.Printf(endpoint %v %v %v %v\n, httpMethod, absolutePath, handlerName, nuHandlers) } r.POST(/foo, func(c *gin.Context) { c.JSON(http.StatusOK, foo) }) r.GET(/bar, func(c *gin.Context) { c.JSON(http.StatusOK, bar) }) r.GET(/status, func(c *gin.Context) { c.JSON(http.StatusOK, status) }) r.Run() }示例输出[GIN-debug] [WARNING] Creating an Engine instance with the Logger and Recovery middleware already attached. [GIN-debug] [WARNING] Running in debug mode. Switch to release mode in production. - using env: export GIN_MODErelease - using code: gin.SetMode(gin.ReleaseMode) [GIN-debug] [WARNING] You trusted all proxies, this is NOT safe. We recommend you to set a value. Please check https://github.com/gin-gonic/gin/blob/master/docs/doc.md#dont-trust-all-proxies for details. [GIN-debug] Environment variable PORT is undefined. Using port :8080 by default [GIN-debug] Listening and serving HTTP on :8080 2026/04/19 10:41:52 endpoint POST /foo main.main.func2 3 2026/04/19 10:41:52 endpoint GET /bar main.main.func3 3 2026/04/19 10:41:52 endpoint GET /status main.main.func4 3可以看到最后三行就是我们自己设置的路由日志了自定义参数验证器Gin 内置的binding标签提供了常用验证规则如required、email、gt等但在实际业务中我们常常需要定制校验逻辑比如判断日期是否晚于今天、字段值是否在指定范围内等。下面我们为Booking结构体的日期字段实现一个bookabledate验证规则要求日期不能早于今天。func main() { r : gin.Default() if v, ok : binding.Validator.Engine().(*validator.Validate); ok { v.RegisterValidation(bookabledate, bookleDate) } r.GET(/bookdate, getBookable) r.Run(:8080) } // 验证输入输出 // required:字段不能缺失bookabledate:自定义规则由后续函数实现 // gtfieldCheckIn:CheckOut必须大于CheckIntime_format:指定日期格式2006-01-02 type booking struct { CheckIn time.Time form:check_in binding:required,bookabledate time_format:2006-01-02 CheckOut time.Time form:check_out binding:required,gtfieldCheckIn,bookabledate time_format:2006-01-02 } // 验证实现函数,日期要晚于今日 var bookleDate validator.Func func(f validator.FieldLevel) bool { date, ok : f.Field().Interface().(time.Time) if ok { today : time.Now() // 验证日期是否在date之后 if today.After(date) { return false } } return true } func getBookable(c *gin.Context) { var b booking if err : c.ShouldBindWith(b, binding.Query); err nil { c.JSON(http.StatusOK, gin.H{message: 时间验证通过}) } else { c.JSON(http.StatusBadRequest, gin.H{error: 时间要在今天之后}) } }自定义中间件中间件是 Gin 最强大的功能之一。通过编写自定义中间件我们可以实现请求计时、鉴权、日志记录、请求体修改等通用逻辑。中间件执行流程:当一个请求到达 Gin 时它会按顺序执行所有已注册的中间件。如果在中间件中调用了c.Next()则会先执行后续的中间件和最终的路由处理函数待它们全部返回后再继续执行c.Next()后面的代码。func main() { // 创建一个没有中间件的gin引擎 r : gin.New() // 添加自定义的中间件 r.Use(Logger()) r.Use(SetDate()) // 设置响应路由 r.GET(/:name, func(c *gin.Context) { // 从上下文获取值 say : c.MustGet(say).(string) // 解析uri中的参数 name : c.Param(name) // 由于处理太快输出日志总是0s所以sleep 1纳秒 time.Sleep(1) c.String(http.StatusOK, Hello %s,%s, name, say) }) r.Run(:8080) } // 自定义中间件自定义一个计算其他所有中间件总耗时的中间件 func Logger() gin.HandlerFunc { return func(c *gin.Context) { // 记录请求的时间 t : time.Now() // 暂停当前函数调用后续所有中间件和路由处理函数 c.Next() // 计算总耗时 latency : time.Since(t) // 输出所用时间 log.Print(latency) } } // 自定义中间件用于在上下文写入数据供后续使用 func SetDate() gin.HandlerFunc { return func(c *gin.Context) { c.Set(say, 欢迎来到本站) } }输出结果2026/04/19 11:26:45 508.6µs可以看到通过自定义各种中间件可以得到自己想要的数据或完成想要的功能比如看请求到响应完成的总耗时。总结Gin 的扩展性设计非常优秀通过上述四个方向的自定义能力开发者可以轻松实现自定义方向核心 API / 方法主要用途访问日志格式gin.LoggerWithFormatter定制 HTTP 请求日志样式和输出目标路由注册日志gin.DebugPrintRouteFunc修改启动时路由信息的打印方式参数验证规则binding.Validator.Engine().RegisterValidation添加业务专属的参数校验逻辑中间件gin.HandlerFuncc.Next()实现请求拦截、预处理、后处理掌握这些自定义技巧能够让你的 Gin 应用更加健壮、可观测、易维护更好地适应生产环境的复杂需求。

更多文章