错误恢复 目标
panic、defer、recover panic 介绍:
panic 是一个 Go 内置函数,它用来停止当前常规控制流并启动 panicking(运行时恐慌)过程。当函数 F 调用 panic 函数时,函数 F 的执行停止,函数 F 中已进行了求值的 defer 函数都将得到正常执行,然后函数 F 将控制权返还给其调用者。函数 F 之后的行为就如同调用者调用的函数是 panic 一样,该 panicking(运行时恐慌)过程将继续在栈上进行下去,直到当前 goroutine 中的所有函数都返回为止,此时程序将崩溃退出。
Go 语言中,比较常见的错误处理方法是返回 error,由调用者决定后续如何处理。但是如果是无法恢复的错误,可以手动触发 panic,当然如果在程序运行过程中出现了类似于数组越界的错误,panic 也会被触发。panic 会中止当前执行的程序,退出。
主动触发panic的例子:
1 2 3 4 5 func main () { fmt.Println("before panic" ) panic ("crash" ) fmt.Println("after panic" ) }
1 2 3 4 5 func main () { arr := []int {1 , 2 , 3 } fmt.Println(arr[4 ]) }
defer panic 会导致程序被中止,但是在退出前,会先处理完当前协程上已经defer 的任务,执行完成后再退出。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 func main () { defer func () { fmt.Println("defer func" ) }() arr := []int {1 , 2 , 3 } fmt.Println(arr[4 ]) }
recover Recover 是一个Go语言的内建函数,可以让进入宕机流程中的 goroutine 恢复过来,recover 仅在延迟函数 defer 中有效,在正常的执行过程中,调用 recover 会返回 nil 并且没有其他任何效果,如果当前的 goroutine 陷入恐慌,调用 recover 可以捕获到 panic 的输入值,并且恢复正常的执行。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 func test_recover () { defer func () { fmt.Println("defer func" ) if err := recover (); err != nil { fmt.Println("recover success" ) } }() arr := []int {1 , 2 , 3 } fmt.Println(arr[4 ]) fmt.Println("after panic" ) } func main () { test_recover() fmt.Println("after recover" ) }
recover 捕获了 panic,程序正常结束。test_recover() 中的 after panic 没有打印,这是正确的,当 panic 被触发时,控制权就被交给了 defer 。在 main() 中打印了 after recover ,说明程序已经恢复正常,继续往下执行直到结束。
设计错误处理机制 对一个 Web 框架而言,错误处理机制是非常必要的。如果框架本身没有完备的测试,导致在某些情况下出现空指针异常等情况。也有可能用户不正确的参数,触发了某些异常,例如数组越界,空指针等。我们要避免因为这些原因导致系统宕机。
添加一个非常简单的错误处理机制,即在此类错误发生时,向用户返回 Internal Server Error ,并且在日志中打印必要的错误信息,方便进行错误定位。
将错误处理设置成中间件
recovery.go
Recovery
的实现:
使用 defer 挂载上错误恢复的函数。
在这个函数中调用 recover() ,捕获 panic,并且将堆栈信息打印在日志中,向用户返回 Internal Server Error 。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 package geeimport ( "fmt" "log" "net/http" "runtime" "strings" ) func trace (message string ) string { var pcs [32 ]uintptr n := runtime.Callers(3 , pcs[:]) var str strings.Builder str.WriteString(message + "\nTraceback:" ) for _, pc := range pcs[:n] { fn := runtime.FuncForPC(pc) file, line := fn.FileLine(pc) str.WriteString(fmt.Sprintf("\n\t%s:%d" , file, line)) } return str.String() } func Recovery () HandlerFunc { return func (c *Context) { defer func () { if err := recover (); err != nil { message := fmt.Sprintf("%s" , err) log.Printf("%s\n\n" , trace(message)) c.Fail(http.StatusInternalServerError, "Internal Server Error" ) } }() c.Next() } }
main.go
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 package mainimport ( "net/http" "gee" ) func main () { r := gee.Default() r.GET("/" , func (c *gee.Context) { c.String(http.StatusOK, "Hello jpc\n" ) }) r.GET("/panic" , func (c *gee.Context) { names := []string {"jpc" } c.String(http.StatusOK, names[100 ]) }) r.Run(":9999" ) }
运行后访问,发现程序没有因为错误而宕机,而是返回”Internal Server Error”。
项目地址 项目地址