gin.recovery()默认不记录panic堆栈到文件,因其仅调用log.println()输出至os.stdout,堆栈被截断且不可重定向;须改用gin.recoverywithwriter()传入自定义io.writer(如log.new(file, "[panic] ", flags))并确保其排在中间件最前,才能完整捕获堆栈与请求上下文。

为什么 gin.Recovery() 默认不记录 panic 堆栈到日志文件
因为 gin.Recovery() 内部只调用 log.Println() 打印到标准输出,且默认 writer 是 os.Stdout,不支持重定向到文件或结构化日志库;它也不暴露 panic 值供你二次处理——你看到的只是“[Recovery] 2026/08/12 - 12:18:00 panic recovered”这一行,堆栈被吞掉了。
如何用 gin.RecoveryWithWriter() 替换默认 recovery 并写入文件日志
必须用 gin.RecoveryWithWriter() 替代 gin.Recovery(),传入自定义 io.Writer(比如带缓冲的文件句柄),并配合 log.SetOutput() 控制日志流向。关键点:
- 打开文件时用
os.OpenFile()+os.O_CREATE | os.O_APPEND | os.O_WRONLY,避免每次请求都覆盖 - 不能直接把
*os.File传给gin.RecoveryWithWriter(),需包装成线程安全的io.Writer(例如用log.New()包一层) - panic 堆栈会通过
debug.Stack()获取,但gin.RecoveryWithWriter()不自动调用它——你得自己在 writer 的Write()方法里触发
示例核心逻辑:
file, _ := os.OpenFile("panic.log", os.O_CREATE|os.O_APPEND|os.O_WRONLY, 0644)
logger := log.New(file, "[PANIC] ", log.LstdFlags|log.Lshortfile)
r.Use(gin.RecoveryWithWriter(logger))
自定义 panic 记录器中间件里为什么不能直接用 defer+recover
自己写 defer func() { if r := recover(); r != nil { ... } }() 容易出问题:
- 如果
gin.Recovery()已注册在更外层,你的中间件根本捕不到 panic——它早被上层截走了 - recover 后调用
c.AbortWithStatusJSON()之前,c.Writer可能已被部分写入,导致http: multiple response.WriteHeader calls - 没处理好 goroutine 生命周期:panic 发生后
c.Request.Body已关闭,你在 recover 里再读会 panic
真正安全的做法是:禁用默认 recovery,用 gin.New() 初始化引擎,然后只注册你自己的 CustomRecovery() 中间件,并确保它排在所有中间件最前面。
panic 日志里要不要包含用户请求信息(如 path、method、IP)
要,但不能硬编码拼接——Gin 的 gin.Context 在 panic 发生时仍可访问,但必须在 recover() 后立刻取,否则上下文可能失效。常见错误是试图在日志 writer 里反向查 c,其实做不到;正确做法是在自定义 recovery 中间件的 defer 块里直接提取:
- 用
c.Request.URL.Path和c.Request.Method获取路由信息 - 用
c.ClientIP()拿真实 IP(注意 X-Forwarded-For 头是否可信) - 把它们和
debug.Stack()一起格式化进日志字符串,再写入文件
别忘了脱敏:生产环境禁止记录 Authorization 头、密码字段、token 原文——这些必须在写入前过滤掉。
golang免费学习笔记(深入):立即使用
在学习笔记中,你将探索golang的核心概念和高级技巧!











