生产环境不应使用 gin.Logger(),应替换为自定义中间件:清理敏感参数、精确计时(c.Next()前后记录时间)、正确获取状态码(c.Writer.Status())、结构化JSON输出、采样控制及异步写入。

怎么用 gin.Logger() 但又不暴露敏感参数
直接用 gin.Logger() 默认会把完整 URL(含 query)和请求体都打出来,比如 /api/user?id=123&token=abc,这在生产环境是安全隐患。它不区分是否含敏感字段,也不支持按路径过滤。
- 用
gin.LoggerWithConfig()替代,默认行为一致,但能自定义Formatter - 在
Formatter函数里手动清理 query:用url.Parse()解析c.Request.URL.String(),再删掉token、auth、password等键名 - 别碰
c.Request.Body—— 它只能读一次,中间件读了后续 handler 就收不到数据;如需记录 body,得用c.Request.Body = io.NopCloser()配合缓存,但代价高,一般只对调试接口开
如何精确记录每个请求的真实耗时(不是从中间件入口开始算)
gin.Logger() 的耗时是从中间件执行开始计,但真正响应时间应从路由匹配后、handler 执行前开始,否则会把日志写入、鉴权等前置逻辑也计入,导致统计失真。
- 自己写中间件,在
c.Next()前调用time.Now(),记到c.Set("start_time", t) - 在
c.Next()后取c.Get("start_time")计算差值,避免用闭包变量(并发下可能错乱) - 注意时区:用
time.Since()而非手动减法,它自动处理纳秒精度和 monotonic clock - 别用
log.Printf直接打,它锁全局 stdout,压测时成瓶颈;改用结构化日志库如zerolog或异步写入
为什么状态码总是记录成 0 或 200,明明返回了 404/500
因为 Gin 的 c.Status() 只设置响应头里的状态码,实际发送由 WriteHeader() 触发,而默认 Logger 是在 c.Next() 返回后立刻读 c.Writer.Status() —— 如果 handler panic 或没显式调用 c.AbortWithStatus(),Status() 还是初始值 0 或 200。
- 必须用
c.Writer.Status(),不是c.Writer.Status()的 getter(Gin v1.9+ 已修复,旧版要升级) - panic 场景下,Gin 默认用
recovery()中间件兜底,但它的 status 是 500,且发生在 logger 之后;所以要把自定义 logger 放在recovery()之后 - 检查是否漏了
c.Abort():如果某个中间件提前 abort,后续 handler 不执行,Status()不会被更新,此时应主动设为 401/403
生产环境要不要用 gin.Logger() 自带的中间件
不要。它没做采样控制、没字段裁剪、没异步缓冲,单机 QPS 上千时 I/O 成瓶颈,日志内容还容易被注入干扰(比如 URL 里塞换行符)。
立即学习“go语言免费学习笔记(深入)”;
- 替换为轻量级自定义中间件,核心逻辑 20 行内搞定,只写必要字段:
method、path、status、latency、user-agent(可选) - 加简单采样:比如
rand.Intn(100) == 0只记录 1% 请求,或对 4xx/5xx 全量记录 - 输出格式统一用 JSON(不是文本),方便后续用 Filebeat/Loki 解析;字段名别用中文或空格,比如用
resp_status不用响应状态码
最麻烦的其实是日志落盘时机和磁盘 IO 抖动,不是代码怎么写。上线前务必用 ab 或 hey 压测对比吞吐变化,有时候关掉日志,QPS 能翻倍。










