Go需分层设防panic:HTTP层中间件仅捕获主goroutine,子goroutine须用safeGo内嵌defer recover,main/init层需兜底defer;日志必须含traceID与debug.Stack()完整栈,结构化记录并统一链路追踪。

Go 没有真正的全局 panic 捕获机制,靠单层 recover 无法覆盖子 goroutine、第三方回调或初始化阶段的 panic。必须分层设防,并把 stack trace 和 traceID 一起落库,否则日志查不到上下文。
HTTP handler 层:中间件 recover 只管主 goroutine
像 Gin.Recovery() 或 Echo.Middleware.Recover() 这类中间件,只包裹请求处理主 goroutine。一旦你在 handler 里起新 goroutine(比如异步发消息、清理缓存),它里面的 panic 就会逃逸出去。
- 检查你用的框架 recovery 中间件是否开启
PrintStack或等价逻辑——很多默认只打r,不输出调用栈 - 确保中间件在所有路由注册前加载,顺序错会导致绕过
- 不要在 handler 内写裸
go fn();改用封装好的safeGo()(见下节)
goroutine 层:每个 go 启动前必须带 recover
子 goroutine 的 panic 不会传播到父 goroutine,recover 必须在它自己内部注册。漏掉一个,就可能让服务静默崩溃。
-
safeGo必须在go语句**内部**立即注册 defer,不能写成defer safeGo(f) - 示例中
debug.Stack()返回[]byte,比debug.PrintStack()更适合生产环境——可拼接 traceID、写入结构化日志字段 - 避免在热路径频繁调用
debug.Stack(),但 panic 本就是异常路径,不必过度优化
main 和 init 层:兜底 defer 覆盖启动期 panic
服务刚启动时发生的 panic(如配置解析失败、DB 连接池初始化 panic)不会经过 HTTP handler,必须在 main() 开头加 defer recover。
立即学习“go语言免费学习笔记(深入)”;
- 这个
defer要放在所有初始化代码之前,包括flag.Parse()、init()调用链之后 - 别依赖
runtime/debug.SetPanicOnFault(true)——它只对非法内存访问有效,对panic("xxx")或切片越界完全无效 - 如果用了
logrus或zap,这里要显式传入context.WithValue(ctx, "stage", "startup")区分场景
日志内容必须含 traceID + stack trace 字节流
只记录 panic: index out of range 没用。没有 traceID 找不到请求上下文,没有完整 stack trace 定位不到第几层函数出的问题。
- 用
debug.Stack()获取字节流,而不是fmt.Sprintf("%v", r)——后者丢掉全部调用帧 - 结构化日志库(如
zap)要将trace_id、stack、service_name、level="panic"作为独立字段,别塞进 message 字符串 - 日志采集端(Filebeat / Fluent Bit)需配置识别
level == "panic"并触发告警,不能只靠 grep "panic"
最常被忽略的是:goroutine panic 和 HTTP handler panic 的日志必须走同一套 traceID 注入逻辑。否则即使都打了 stack trace,也串不起完整链路。


















