直接修改formatter函数即可,无需改动中间件或重写日志逻辑;通过gin.loggerwithconfig自定义formatter,利用logformatterparams字段(如timestamp、statuscode、clientip、errormessage等)构造结构化日志,并设置disablecolors:true避免ansi字符干扰解析。

直接改 Formatter 函数就行,不用动中间件或重写整个日志逻辑。
怎么用 gin.LoggerWithConfig 替换默认日志格式
默认日志只输出一行带颜色的文本,没法直接塞进 ELK 或做字段提取。核心是传一个自定义 Formatter 函数给 gin.LoggerConfig:
-
Formatter参数类型是func(gin.LogFormatterParams) string,返回你要的日志字符串 -
LogFormatterParams结构体里有你需要的所有字段:TimeStamp、StatusCode、Latency、ClientIP、Method、Path、ErrorMessage等 - 别忘了设置
DisableColors: true,否则写入文件时会混入 ANSI 控制字符(比如\x1b[32m),导致日志解析失败
Formatter 里容易漏掉的关键字段
很多人只拼了状态码和路径,但线上排障真正有用的字段常被忽略:
-
param.ClientIP:必须保留,尤其是反向代理后要靠X-Forwarded-For或X-Real-IP头修正,否则全是127.0.0.1 -
param.ErrorMessage:只有出错时非空,但它是定位 5xx 的关键线索,比如 panic 信息或中间件提前中断原因 -
param.Request.Header.Get("User-Agent"):不是LogFormatterParams原生字段,得手动从c.Request拿——这意味着你不能只靠Formatter,得配合中间件上下文
想输出 JSON 日志?别硬套 Formatter
Formatter 只能返回 string,强行 json.Marshal 会把双引号、转义符全打进去,看着乱还难解析:
- 例如
"{\"status\":200,\"path\":\"/api/v1/user\"}"这种嵌套 JSON 不是标准日志格式 - 真要结构化日志,得用
logrus或zerolog写独立中间件,接管c.Next()前后时机,自己构造 log event - 如果只是临时调试,用
fmt.Sprintf拼个类 JSON 字符串(如"status=%d path=%s ip=%s")更稳妥,兼容 grep 和 awk
为什么跳过健康检查路径后日志还是出现
SkipPaths 只匹配完整路径,不支持通配或前缀匹配:
-
SkipPaths: []string{"/health"}能跳过GET /health,但对GET /healthz或GET /health/detail无效 - 如果要用模糊匹配,得写
Skip函数:Skip: func(c *gin.Context) bool { return strings.HasPrefix(c.Request.URL.Path, "/health") } -
Skip函数在每次请求都执行,别在里面做 heavy 操作(比如 DB 查询或 HTTP 调用)
实际项目里,Formatter 往往只是日志链的第一环;后续是否需要按级别分流、自动切文件、对接 Loki,得看日志量级和 infra 支持程度——别一上来就堆 JSON + Hook,先让关键字段稳稳落盘再说。











