0

0

Golang实现自定义的Recovery中间件_记录详细堆栈与请求信息

P粉602998670

P粉602998670

发布时间:2026-02-14 15:23:05

|

783人浏览过

|

来源于php中文网

原创

go http服务panic后不崩溃并记录完整堆栈的关键是:在最外层handler用defer+recover捕获panic,配合responsewriterwrapper避免重复写响应,提前读取并保存request body、query、headers等上下文,使用足够深度(≥32帧)的runtime.stack,并结构化记录remoteaddr、useragent、x-request-id等关键信息。

golang实现自定义的recovery中间件_记录详细堆栈与请求信息

Go HTTP 服务 panic 后如何不崩溃还能记录完整堆

默认的 http.Server 遇到 panic 会直接终止连接、丢弃上下文,连请求路径和参数都看不到。自定义 Recovery 中间件不是为了“兜底运行”,而是确保 panic 发生时能拿到:runtime.Stackhttp.Request 的 method/path/headers/body(需提前读取)、以及调用链中关键变量值。

关键点在于:panic 捕获必须在最外层 handler 函数内,且不能影响原有 responseWriter;堆栈要足够深(至少 32 帧),否则看不到业务代码行号。

  • 务必用 defer + recover() 包裹整个 handler 执行逻辑,而不是只包业务函数
  • 不要在 recover 后继续写入原始 http.ResponseWriter,应使用 ResponseWriter 包装器拦截状态码和 body
  • 请求 body 只能读一次,要在 handler 开头就用 io.ReadAll(r.Body) 保存,否则 recover 时已关闭

为什么用 http.Handler 而不是 gin.Echo 的内置 Recovery

gin 的 Recovery() 默认只打日志、返回 500,堆栈截断严重(默认 48 字节),且不暴露 *http.Request 实例;Echo 的同名中间件甚至不支持自定义 stack size。纯 net/http 下自己写,才能控制三件事:stackSize 参数、requestID 注入、错误上报渠道(如 sentry 或本地文件)。

如果你用的是标准库或 chi/gorilla,别被框架封装劝退——中间件本质就是 func(http.Handler) http.Handler,没黑盒。

立即学习go语言免费学习笔记(深入)”;

Windsurf
Windsurf

Codeium团队打造的AI编程助手

下载
  • gin 的 RecoveryWithWriter 不让改 stack depth,源码里写死 debug.Stack()
  • 想记录 query 参数?得从 r.URL.RawQuery 提取,而不是依赖框架解析后的 r.URL.Query()(可能已被修改)
  • 并发安全:log 打印前建议加锁,或用 log/slog(Go 1.21+)自带原子写入

recover() 后怎么安全地写响应而不触发 “http: multiple response.WriteHeader calls”

panic 可能在任何位置发生,包括已经调用过 w.WriteHeader(200) 之后。此时再写 header 就会报错。正确做法是用一个包装了 http.ResponseWriter 的结构体,在 WriteHeaderWrite 里做状态标记,确保 panic 后只写一次错误响应。

示例核心逻辑:

type responseWriterWrapper struct {
    http.ResponseWriter
    written bool
    status  int
}
func (w *responseWriterWrapper) WriteHeader(status int) {
    if !w.written {
        w.status = status
        w.ResponseWriter.WriteHeader(status)
        w.written = true
    }
}
func (w *responseWriterWrapper) Write(b []byte) (int, error) {
    if !w.written {
        w.WriteHeader(http.StatusInternalServerError)
    }
    return w.ResponseWriter.Write(b)
}
  • 必须重写 WriteHeaderWrite,忽略 Hijack/Flush 等高级方法(panic 场景下基本用不到)
  • 不要在 wrapper 里存 body 内容——内存开销不可控;只记状态,错误响应由 recovery handler 统一生成
  • 注意:如果原 handler 已经调用 Write 输出了部分 JSON,panic 后再写 500 响应会导致前端收到截断/混合响应,此时应优先关闭连接(http.CloseNotifier 已废弃,可用 r.Context().Done() 检测)

记录堆栈时最容易漏掉的两个上下文信息

90% 的自定义 recovery 日志只打了 debug.Stack(),但线上定位问题真正卡住的是:这个 panic 是谁触发的?在什么条件下触发的?光有堆栈不够。

必须额外捕获并结构化输出:

  • r.RemoteAddrr.UserAgent():区分是爬虫、测试脚本还是真实用户
  • r.Header.Get("X-Request-ID") 或自动生成一个(用 uuid.NewString()),方便全链路日志串联
  • 注意:r.FormValue 在未调用 r.ParseForm() 前为空,应统一在 middleware 开头调用 r.ParseMultipartForm(32 并忽略错误(小 body 直接 <code>ParseForm
  • 不要记录 r.Body 原始字节到日志文件——可能含密码、token;如需调试,只记录 len(body)md5(body)

堆栈本身也别直接 string(debug.Stack()),用 bytes.TrimSuffix(debug.Stack(), []byte("\n")) 去掉末尾换行,避免日志系统切行错乱。

热门AI工具

更多
DeepSeek
DeepSeek

幻方量化公司旗下的开源大模型平台

豆包大模型
豆包大模型

字节跳动自主研发的一系列大型语言模型

通义千问
通义千问

阿里巴巴推出的全能AI助手

腾讯元宝
腾讯元宝

腾讯混元平台推出的AI助手

文心一言
文心一言

文心一言是百度开发的AI聊天机器人,通过对话可以生成各种形式的内容。

讯飞写作
讯飞写作

基于讯飞星火大模型的AI写作工具,可以快速生成新闻稿件、品宣文案、工作总结、心得体会等各种文文稿

即梦AI
即梦AI

一站式AI创作平台,免费AI图片和视频生成。

ChatGPT
ChatGPT

最最强大的AI聊天机器人程序,ChatGPT不单是聊天机器人,还能进行撰写邮件、视频脚本、文案、翻译、代码等任务。

相关专题

更多
golang如何定义变量
golang如何定义变量

golang定义变量的方法:1、声明变量并赋予初始值“var age int =值”;2、声明变量但不赋初始值“var age int”;3、使用短变量声明“age :=值”等等。本专题为大家提供相关的文章、下载、课程内容,供大家免费下载体验。

206

2024.02.23

golang有哪些数据转换方法
golang有哪些数据转换方法

golang数据转换方法:1、类型转换操作符;2、类型断言;3、字符串和数字之间的转换;4、JSON序列化和反序列化;5、使用标准库进行数据转换;6、使用第三方库进行数据转换;7、自定义数据转换函数。本专题为大家提供相关的文章、下载、课程内容,供大家免费下载体验。

235

2024.02.23

golang常用库有哪些
golang常用库有哪些

golang常用库有:1、标准库;2、字符串处理库;3、网络库;4、加密库;5、压缩库;6、xml和json解析库;7、日期和时间库;8、数据库操作库;9、文件操作库;10、图像处理库。本专题为大家提供相关的文章、下载、课程内容,供大家免费下载体验。

346

2024.02.23

golang和python的区别是什么
golang和python的区别是什么

golang和python的区别是:1、golang是一种编译型语言,而python是一种解释型语言;2、golang天生支持并发编程,而python对并发与并行的支持相对较弱等等。本专题为大家提供相关的文章、下载、课程内容,供大家免费下载体验。

212

2024.03.05

golang是免费的吗
golang是免费的吗

golang是免费的。golang是google开发的一种静态强类型、编译型、并发型,并具有垃圾回收功能的开源编程语言,采用bsd开源协议。本专题为大家提供相关的文章、下载、课程内容,供大家免费下载体验。

402

2024.05.21

golang结构体相关大全
golang结构体相关大全

本专题整合了golang结构体相关大全,想了解更多内容,请阅读专题下面的文章。

342

2025.06.09

golang相关判断方法
golang相关判断方法

本专题整合了golang相关判断方法,想了解更详细的相关内容,请阅读下面的文章。

197

2025.06.10

golang数组使用方法
golang数组使用方法

本专题整合了golang数组用法,想了解更多的相关内容,请阅读专题下面的文章。

845

2025.06.17

pixiv网页版官网登录与阅读指南_pixiv官网直达入口与在线访问方法
pixiv网页版官网登录与阅读指南_pixiv官网直达入口与在线访问方法

本专题系统整理pixiv网页版官网入口及登录访问方式,涵盖官网登录页面直达路径、在线阅读入口及快速进入方法说明,帮助用户高效找到pixiv官方网站,实现便捷、安全的网页端浏览与账号登录体验。

23

2026.02.13

热门下载

更多
网站特效
/
网站源码
/
网站素材
/
前端模板

精品课程

更多
相关推荐
/
热门推荐
/
最新课程
Go 教程
Go 教程

共32课时 | 5.1万人学习

Go语言实战之 GraphQL
Go语言实战之 GraphQL

共10课时 | 0.8万人学习

关于我们 免责申明 举报中心 意见反馈 讲师合作 广告合作 最新更新
php中文网:公益在线php培训,帮助PHP学习者快速成长!
关注服务号 技术交流群
PHP中文网订阅号
每天精选资源文章推送

Copyright 2014-2026 https://www.php.cn/ All Rights Reserved | php.cn | 湘ICP备2023035733号