Go中panic需用defer+recover捕获,recover必须在同goroutine且调用栈上游,返回值需类型断言转error或fmt.Sprint处理,debug.Stack()可获取堆栈但开销大,生产环境应结合runtime.Caller定位行号并统一日志。

panic发生时如何捕获并提取错误信息
Go里panic不是普通错误,不能用if err != nil处理;必须用recover()在defer中拦截,否则程序直接退出。关键点在于:只有在同一个goroutine中、且recover()位于panic()调用栈的上游(通常是同一函数或其调用者)才能生效。
-
recover()必须放在defer函数内部,且该defer需在panic触发前已注册 - 如果
panic(nil)或panic(42)这种非error类型值,recover()返回的就是原值,需类型断言或fmt.Sprint()转字符串 - 若想统一获取带堆栈的错误信息,建议始终
panic(errors.New("xxx"))或panic(fmt.Errorf("xxx")),避免裸字符串
recover后怎么拿到可读的错误字符串
recover()返回interface{},不是error。直接fmt.Println(r)可能输出<nil>或无意义数字,必须做类型判断和转换。
- 最稳妥方式:
v, ok := recover().(error),再用v.Error()取字符串 - 兼容非error panic(如
panic("oops")):fmt.Sprint(recover()),但会丢失原始类型语义 - 不要写
err := recover().(error)——当panic不是error时会再次panic
如何保留panic时的调用堆栈
recover()本身不提供堆栈;要拿到类似panic: xxx\n goroutine 1 [running]:\n main.main...这样的完整信息,得手动捕获goroutine stack trace。
- 用
debug.PrintStack()可打印到os.Stderr,但无法捕获为字符串 - 用
debug.Stack()返回[]byte,可转成字符串:string(debug.Stack()) - 注意:
debug.Stack()开销较大,仅用于开发/日志,别在高频路径调用 - 生产环境建议结合
runtime.Caller()定位到panic发生行,例如:_, file, line, _ := runtime.Caller(1)
实际日志记录中的常见写法
真实项目里,一般不会只打印panic内容,而是组合错误消息、文件行号、堆栈,再交给日志库处理。下面是一个轻量封装示例:
立即学习“go语言免费学习笔记(深入)”;
func handlePanic() {
defer func() {
if r := recover(); r != nil {
err, ok := r.(error)
if !ok {
err = fmt.Errorf("%v", r)
}
stack := string(debug.Stack())
log.Printf("PANIC: %v\nFILE: %s:%d\nSTACK:\n%s",
err,
file, line, // 需在defer内用runtime.Caller获取
stack)
}
}()
}
真正容易被忽略的是:recover只对当前goroutine有效,启动新goroutine后panic必须各自defer recover;另外,HTTP handler等框架内部可能已做了recover,重复包一层反而掩盖问题。


















