跳转到主内容
极星编程网:以代码为星,赴技术山海!

Golang Gin怎么做日志记录中间件_Golang Gin日志教程【通俗】

Gin默认日志中间件无法打印请求体和响应体,因c.Request.Body为单次读取流且ResponseWriter被封装;需重置Body或包装ResponseWriter,并按Cors→Auth→Logger→Recovery顺序注册中间件。 为什么 Gin 默认日志中间件打不出请求体和响应体 因为
c.Request.Body
是单次读取的
io.ReadCloser
流,日志中间件一读就空了,后续业务代码调用
c.ShouldBindJSON()
会报
invalid character
;而
http.ResponseWriter
被 Gin 封装过,直接读会 panic 或丢内容。 别在日志中间件里调用
c.ShouldBindJSON()
或
c.PostForm
—— 它们会消费 Body 要记录请求体,得先用
ioutil.ReadAll(c.Request.Body)
拿到原始字节,再用
bytes.NewBuffer(bodyBytes)
和
ioutil.NopCloser
把 Body 重置回去 记录响应体必须包装
ResponseWriter
(比如自定义
responseWriter
结构体),拦截
Write()
和
WriteHeader()
,并在
c.Next()
后读缓冲区 生产环境建议只开 debug 模式才记录 body:
if gin.Mode() == gin.DebugMode
怎么用 Zap 替换默认 logger 实现高性能结构化日志 默认
gin.Logger()
只输出文本,不支持字段、级别过滤、日志切割,Zap 的
SugaredLogger
更适合开发调试,
Logger
更适合高吞吐服务。 安装:
go get -u go.uber.org/zap
初始化时选对类型:性能敏感用
zap.NewProduction()
,开发调试用
logger.Sugar()
中间件中记录字段必须显式传参:
logger.Info("request", zap.String("path", c.Request.URL.Path), zap.Int("status", c.Writer.Status()))
别忘了
defer logger.Sync()
,否则进程退出时缓冲区日志会丢失 日志中间件顺序放错会导致 panic 不留痕迹或鉴权失效 中间件执行是链式入栈 + 出栈返回,注册顺序决定逻辑依赖关系。日志中间件如果放在
Recovery
前面,panic 发生时日志根本没刷出来;如果放在鉴权后面,未授权请求也会被记日志,还可能因
c.Get("user")
返回 nil 导致 panic。 正确顺序(从左到右):
Cors()
→
Auth()
→
Logger()
→
Recovery()
Auth
中必须用
c.Set("user", user)
显式存值,下游用
user, ok := c.Get("user")
判断,不能只靠非空断言 避免全局
r.Use()
,改用路由组控制范围:
api := r.Group("/api").Use(auth, logger)
怎么写一个带耗时统计和 IP 标识的轻量日志中间件 不需要引入 Zap 也能快速写出实用日志,关键是把时间戳、状态码、路径、IP、耗时这五项打全,并控制好 body 读取时机。 立即学习 “ go语言免费学习笔记(深入) ”; 用
c.ClientIP()
拿真实 IP(注意配置
TrustedProxies
防伪造) 计时用
start := time.Now()
+
c.Next()
+
cost := time.Since(start)
状态码必须从
c.Writer.Status()
读(不是
c.Writer.Written()
),因为
c.AbortWithStatus()
不会触发 WriteHeader 简单示例:
func Logger() gin.HandlerFunc {
return func(c *gin.Context) {
start := time.Now()
c.Next()
cost := time.Since(start)
log.Printf("[GIN] %s %s %d %v %s",
c.Request.Method,
c.Request.URL.Path,
c.Writer.Status(),
cost,
c.ClientIP())
}
}
实际项目里最常漏掉的是
c.Writer.Status()
的时机判断和
ClientIP()
的可信代理配置,这两处一错,日志就失去可观测性。

相关文章