go 中 panic 不跨 goroutine 传播,每个 goroutine 必须独立 defer+recover;recover 仅在 defer 匿名函数中有效且需在 panic 前注册;应传 error 类型 panic 并结构化日志。

Go 语言中,panic 不会跨 goroutine 传播,所以并发场景下必须为每个可能出错的 goroutine 单独加 defer + recover,否则 panic 会导致该协程崩溃、日志丢失、状态不一致,甚至拖垮整个服务。
recover 必须在 defer 中调用,且 defer 要在 panic 前注册
这是最基础也最容易写错的一点。recover 只有在 defer 函数内部调用才有效,而且 defer 语句必须出现在 panic 触发之前(哪怕只差一行)。
- 错误写法:recover 写在普通 if 或函数体里,永远拿不到 panic 值
- 正确模式只有一种:用 defer 包裹一个匿名函数,在里面调用 recover()
- 多个 defer 按后进先出执行,但 recover 所在的 defer 必须在 panic 前注册,顺序不能颠倒
每个 goroutine 都要自己 recover,主 goroutine 捕不到子协程 panic
Go 的 panic 是 goroutine 局部的。你在 go func() { panic(...) } 里崩了,外面无论怎么 defer+recover 都看不见。
- 典型翻车:启动 worker 时没包 recover,一个请求触发 panic,整个协程静默退出,连接卡住、定时任务漏跑、数据库事务没回滚
- 推荐封装通用安全启动函数,比如 goSafe(func() { ... }),内部自动加 defer+recover+堆栈日志
- 不要依赖“统一中间件”来兜底——中间件只管 handler,不管后台 goroutine
传 error 类型 panic,别用字符串
panic("db timeout") 看起来简单,但线上排查时毫无上下文。传 error 类型才能对接结构化日志和监控系统。
- 优先用 errors.New 或 fmt.Errorf,支持 %w 包装原始错误
- 自定义错误类型实现 Error() 方法,可附带 traceID、userIP、SQL 语句等字段
- recover 拿到的是 interface{},做类型断言区分 string/error/其他,再格式化输出
HTTP handler 中的标准 recover 写法
这是最常需要 recover 的地方。一次 panic 就会让一个请求直接断连,还可能影响连接池复用。
- 中间件里 defer 必须放在 handler 执行前,不能塞在 if 分支里
- recover 后一定要调用 debug.Stack() 记录完整堆栈,不能只打 panic 值
- 返回 500 时避免暴露敏感信息,但服务端日志要保留全部上下文,方便定位
- 建议在 recover 后生成唯一 traceID 并写入日志,便于全链路追踪
golang免费学习笔记(深入):立即使用
在学习笔记中,你将探索golang的核心概念和高级技巧!











