recover必须在defer中调用才有效,否则返回nil;需用匿名函数立即执行并注意goroutine隔离、类型断言及记录完整堆栈。

recover必须在defer中调用,否则无效
Go 的 recover 只有在 panic 正在发生、且当前 goroutine 的 defer 函数正在执行时才有效。如果把它写在普通函数体里,或者放在非 defer 位置,返回值永远是 nil,日志里什么也捞不到。
常见错误写法:if err := recover(); err != nil { log.Println(err) } —— 这段代码根本不会触发,因为没在 defer 里。
- 必须写成
defer func() { if r := recover(); r != nil { /* 记录日志 */ } }() - 注意:匿名函数要立即执行(末尾加
()),否则只是声明,不会注册到 defer 链 - 多个 defer 按后进先出顺序执行,
recover应该放在最外层或关键路径的 defer 中
recover捕获的是interface{},需类型断言才能获取具体错误信息
recover() 返回的是 interface{},不是 error 类型。直接打印可能只看到 <nil> 或难读的内存地址,尤其当 panic 是由 panic("xxx") 字符串触发时。
- 用
fmt.Sprintf("%v", r)是最稳妥的通用转换方式 - 若确定 panic 来自
error(如panic(err)),可做类型断言:if err, ok := r.(error); ok { log.Error(err.Error()) } - 但别假设所有 panic 都是 error:整数、结构体、甚至 nil 都可能被 panic,强转
r.(error)会 panic 二次崩溃
goroutine隔离导致主流程recover无法捕获子goroutine panic
Go 中每个 goroutine 有独立的 panic/recover 作用域。在主线程 defer 里调用 recover,完全捕获不到 go func() { panic("oops") }() 里的 panic —— 它会直接终止那个 goroutine,并静默丢弃。
诊断并恢复通过 SSH 隧道连接的 OpenClaw 节点。用于解决配对必需错误、隧道冲突、远程端点错误以及 SSH 目标配置错误等问题。
立即学习“go语言免费学习笔记(深入)”;
- 子 goroutine 必须自带 defer + recover,例如:
go func() { defer func() { if r := recover(); r != nil { log.Printf("goroutine panic: %v", r) } }(); /* 业务逻辑 */ }() - 不要依赖全局 panic handler;Go 没有类似 Node.js 的
uncaughtException机制 - 第三方库(如 http.Server)内部启动的 goroutine 通常已内置 recover,但自定义 handler 仍需自行包裹
日志中应同时记录堆栈,否则无法定位 panic 位置
仅记录 recover() 返回值,等于只保存了“发生了什么”,却丢了“在哪发生的”。没有堆栈,你只能看到 panic: runtime error: index out of range,但不知道是第几行、哪个切片访问越界。
- 用
debug.PrintStack()输出到标准错误(适合开发) - 生产环境推荐
debug.Stack()获取字节切片,再传给日志库:log.Printf("panic recovered: %v\n%s", r, debug.Stack()) - 注意
debug.Stack()开销略高,高频 panic 场景要考虑采样或降级 - 某些日志库(如 zap)支持结构化字段,可把 stack 单独作为
stack字段,便于后续检索
实际线上服务中,最常被忽略的是 goroutine 隔离和堆栈缺失——前者让 panic 看似“消失”,后者让修复变成盲人摸象。

















