在Golang程序里,panic会中断正常流程,若只调用recover而没有记录堆栈,排查错误会非常困难。借助标准库的相关函数,我们可以把panic发生时的完整调用栈保存下来,方便事后分析。

使用recover配合runtime.Stack
最常见的做法是在defer函数中调用recover,再通过runtime.Stack把当前goroutine的堆栈写入缓冲区。下面示例展示了基本的捕获方式:
package main
import (
"fmt"
"runtime"
)
func demo() {
defer func() {
if err := recover(); err != nil {
// 创建足够大的缓冲区存放堆栈
buf := make([]byte, 4096)
n := runtime.Stack(buf, false)
fmt.Printf("panic: %vnstack:n%s", err, buf[:n])
}
}()
// 故意触发panic
panic("something wrong")
}
func main() {
demo()
}
使用debug.Stack简化操作
runtime/debug包的Stack函数能直接返回当前堆栈的字节切片,代码更简洁。适合在日志组件里统一使用。
package main
import (
"fmt"
"runtime/debug"
)
func safeRun() {
defer func() {
if e := recover(); e != nil {
fmt.Printf("recovered: %vn%s", e, debug.Stack())
}
}()
panic("test panic")
}
func main() {
safeRun()
}
Web服务中的统一捕获
在HTTP服务中,可以用中间件在每次请求处理前挂一个defer,自动记录handler里出现的panic堆栈,避免进程直接退出。
package main
import (
"fmt"
"net/http"
"runtime/debug"
)
func recoverMiddleware(next http.HandlerFunc) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
defer func() {
if err := recover(); err != nil {
fmt.Printf("panic in %s: %vn%s", r.URL.Path, err, debug.Stack())
http.Error(w, "internal error", 500)
}
}()
next(w, r)
}
}
func hello(w http.ResponseWriter, r *http.Request) {
panic("handler panic")
}
func main() {
http.HandleFunc("/", recoverMiddleware(hello))
http.ListenAndServe("127.0.0.1:8080", nil)
}
捕获所有goroutine堆栈
如果希望看到包括其他goroutine在内的全部堆栈,可以把runtime.Stack的第二个参数设为true。
package main
import (
"fmt"
"runtime"
)
func main() {
buf := make([]byte, 1<<20)
n := runtime.Stack(buf, true)
fmt.Printf("all goroutines stack:n%s", buf[:n])
}
小结
通过recover结合runtime.Stack或debug.Stack,开发者可以轻松记录Golang中panic的完整堆栈。在线上环境建议统一封装异常捕获逻辑,将堆栈输出到日志系统,从而显著提升故障排查效率。