用 gin.LoggerWithConfig() 替换默认 gin.Logger() 即可完全控制日志格式,因其通过 Formatter 函数接收 gin.LogFormatterParams 并返回自定义字符串,支持 JSON、加字段、着色等,而默认 Logger 格式硬编码不可修改。

直接结论:用 gin.LoggerWithConfig() 替换默认 gin.Logger(),就能完全控制日志格式,无需重写中间件或引入第三方库。
为什么不能直接改 gin.Logger() 的输出
因为 gin.Logger() 是硬编码实现的——格式字符串写死在源码里,不接受任何参数,也不支持结构化、加字段、跳路径。你调它一百次,输出永远是:[GIN] 2026/08/12 - 23:11:00 | 200 | 1.234ms | 127.0.0.1 | GET "/api"。想加 trace_id?改不了。想输出 JSON?做不到。
gin.LoggerWithConfig() 怎么用
它接收一个 gin.LoggerConfig 结构体,其中 Formatter 字段就是你定制日志格式的入口。关键点:
-
Formatter是个函数,入参是gin.LogFormatterParams,里面包含时间、状态码、耗时、IP、方法、路径等所有原始数据 - 返回值是你想要的任意字符串,比如 JSON、带颜色的文本、或带上下文字段的混合格式
- 别在
Formatter里做耗时操作(如网络请求、文件 IO),它在每次请求响应后同步执行,会拖慢整个 HTTP 流程
示例(输出精简 JSON):
loggerConfig := gin.LoggerConfig{
Formatter: func(param gin.LogFormatterParams) string {
return fmt.Sprintf(`{"time":"%s","status":%d,"latency":"%s","ip":"%s","method":"%s","path":"%s"}\n`,
param.TimeStamp.Format(time.RFC3339),
param.StatusCode,
param.Latency.String(),
param.ClientIP,
param.Method,
param.Path,
)
},
}
r.Use(gin.LoggerWithConfig(loggerConfig))
容易被忽略的三个坑
gin.LoggerWithConfig() 看似简单,但实际踩坑集中在三处:
-
SkipPaths不生效?确认你用的是gin.LoggerWithConfig(),不是gin.Logger()—— 后者压根不支持该字段 - 日志里看不到
trace_id?LogFormatterParams不自动携带 context 数据,得自己从c.Request.Context().Value("trace_id")拿,但注意:必须确保上游中间件已存入,且你能在Formatter里访问到请求上下文(Gin 默认不透传,需手动提取) - 文件日志乱码或截断?
Output传io.MultiWriter时,确保每个 writer 都是线程安全的;写文件建议用os.OpenFile配os.O_APPEND,别用os.Create每次覆盖
真正麻烦的从来不是“怎么加字段”,而是字段从哪来、什么时候来、谁负责存——日志格式只是表层,上下文传递和中间件顺序才是决定能否稳定输出 trace_id 或 user_id 的关键。











