因为Gin默认Logger中间件专为开发调试设计,输出可读性优先的固定文本格式,不支持结构化输出,无法被ELK等工具解析,且未暴露响应状态码等底层字段供JSON组装。

为什么 Gin 默认 Logger 中间件不输出 JSON?
Gin 自带的 gin.Logger() 是为开发调试设计的,输出的是可读性优先的文本格式(比如 [GIN] 2024/04/01 - 10:23:41 | 200 | 1.234ms | 127.0.0.1 | GET /api/users),没有结构化字段,无法被 ELK、Loki 或 Datadog 直接解析。它也不暴露底层 gin.ResponseWriter 的状态码和响应体长度获取逻辑供你自由组装 JSON。
用 gin.LoggerWithConfig + 自定义 gin.LogFormatter 实现 JSON 日志
核心是替换日志格式器(LogFormatter),而非重写整个中间件。Gin 支持传入自定义格式函数,返回字符串 —— 你只需在这个函数里拼 JSON 字符串即可。
注意:JSON 字符串必须是单行(不能换行或缩进),否则日志采集工具会按行切分失败。
-
gin.LogFormatter函数签名固定:func(param gin.LogFormatterParams) string -
param.StatusCode、param.Latency、param.ClientIP等字段可直接用,但param.ErrorMessage只在 panic 时非空,正常错误需靠其他方式捕获 - 别用
json.Marshal直接序列化param—— 它含函数和未导出字段,会 panic
示例(精简版):
logJSON := func(param gin.LogFormatterParams) string {
return fmt.Sprintf(`{"time":"%s","status":%d,"method":"%s","path":"%s","ip":"%s","latency_ms":%.3f,"user_agent":"%s"}`+"\n",
param.TimeStamp.Format(time.RFC3339),
param.StatusCode,
param.Method,
param.Path,
param.ClientIP,
float64(param.Latency.Nanoseconds())/1e6,
param.Request.UserAgent(),
)
}
<p>r.Use(gin.LoggerWithConfig(gin.LoggerConfig{
Formatter: logJSON,
}))
如何记录请求体和响应体?
Gin 默认不读取请求体(避免重复读取破坏流),响应体更不提供访问接口。强行记录会影响性能,且可能破坏 http.ResponseWriter 行为。
如果真需要,必须自己封装 ResponseWriter 拦截写入,并用 io.TeeReader 或临时 buffer 读取 *http.Request.Body —— 但仅限调试,生产环境禁用。
- 记录请求体:调用
param.Request.Body前先param.Request.Body = ioutil.NopCloser(bytes.NewBuffer(bodyBytes))(已读过一次后不可再读) - 记录响应体:需实现
responseWriter结构体,包装原http.ResponseWriter,在Write和WriteHeader中缓存数据 - 这两步都会显著增加内存分配和延迟,QPS 高时容易触发 GC 压力
生产环境 JSON 日志的几个硬性提醒
JSON 日志不是加个 Formatter 就完事。你得确保下游能正确消费:
- 日志输出到 stdout 时,确保容器运行时(如 Docker)没做行缓冲干扰 —— 加
os.Stdout.Sync()或设GIN_MODE=release启用默认 sync - 字段名统一用小写+下划线(如
client_ip),避免不同服务字段命名冲突 - 时间字段必须用 ISO8601(如
2024-04-01T10:23:41Z),别用本地时区或自定义格式 - 如果用了 Zap、Zerolog 等结构化日志库,不要混用 Gin 的 Logger 中间件 —— 直接写一个兼容
gin.HandlerFunc的中间件,用你自己的 logger 实例打日志
最常被跳过的点:没处理 param.ErrorMessage 的空值安全,导致 JSON 字段缺失或格式错乱;还有把 param.Latency 当毫秒用,实际是 time.Duration,直接 %v 会输出 "123456789ns" 而不是数字。


















