由于这个问题问得比较多,因此专门为大家撰写了这一章节。

基本介绍

目前大多数Golang的第三方WebServer库均没有默认对HTTP请求处理过程中产生的异常进行捕获,轻者错误产生后无法记录到日志造成排查错困难,重则异常造成进程直接崩溃,服务不可用。

吐槽:由于作者公司产品也有在用gin,某几次生产事故便因为开发者不严谨的代码panic造成进程崩溃退出。解决方案是所有相关项目必须手动创建recover中间件进行捕获。

当你选择goframe,你很幸运。裸奔谁都会,但作为一款企业级的基础开发框架,对严谨及安全性的要求大于性能,因此默认情况下,执行过程中产生的panic是有被Server自动捕获的,产生panic时当前执行流程会立即中止,但是绝对不会影响进程直接崩溃。

获取异常错误

HTTP执行流程中产生panic异常时,默认处理是记录到Server的日志文件中。当然,开发者也可以通过注册中间件方式手动捕获,然后自定义相关的错误处理。这一操作其实在中间件章节的示例中也有介绍,我们这里来再仔细说明下。

相关方法

异常的捕获我们通过Request对象中的GetError方法获取。

开发者不能通过recover方法来捕获异常,因为goframe框架的Server已经做了捕获,并且为保证默认情况下异常不会引起进程崩溃,因此不会再次往上抛异常。

  1. // GetError returns the error occurs in the procedure of the request.
  2. // It returns nil if there's no error.
  3. func (r *Request) GetError() error

该方法往往使用在流程控制组件中,如后置中间件或者HOOK钩子方法中。

使用示例

我们这里使用一个全局的后置中间件来捕获异常,当异常产生后,捕获并写入到指定的日志文件中,返回固定友好的提示信息,避免敏感的报错信息暴露给调用端。

需要注意的是:

  • 即使开发者有自己捕获记录异常错误的日志,但是Server依旧会打印到Server自己的错误日志文件中。由开发者调用日志接口方法输出的日志属于业务日志(与业务相关),而Server自行管理的日志属于服务日志(类似于nginxerror.log)。
  • 由于goframe框架大部分的底层错误都包含有错误时的堆栈信息,如果对于error的具体堆栈信息感兴趣(具体调用链、报错文件路径、源码行号等),可以使用gerror来获取,具体请参考 错误处理-堆栈特性 章节。如果异常包含堆栈信息,默认情况下会打印到Servererror日志文件中。
  1. package main
  2. import (
  3. "github.com/gogf/gf/v2/frame/g"
  4. "github.com/gogf/gf/v2/net/ghttp"
  5. )
  6. func MiddlewareErrorHandler(r *ghttp.Request) {
  7. r.Middleware.Next()
  8. if err := r.GetError(); err != nil {
  9. // 记录到自定义错误日志文件
  10. g.Log("exception").Error(err)
  11. //返回固定的友好信息
  12. r.Response.ClearBuffer()
  13. r.Response.Writeln("服务器居然开小差了,请稍后再试吧!")
  14. }
  15. }
  16. func main() {
  17. s := g.Server()
  18. s.Use(MiddlewareErrorHandler)
  19. s.Group("/api.v2", func(group *ghttp.RouterGroup) {
  20. group.ALL("/user/list", func(r *ghttp.Request) {
  21. panic("db error: sql is xxxxxxx")
  22. })
  23. })
  24. s.SetPort(8199)
  25. s.Run()
  26. }

执行后,我们通过curl工具来试试吧:

  1. $ curl -v "http://127.0.0.1:8199/api.v2/user/list"
  2. > GET /api.v2/user/list HTTP/1.1
  3. > Host: 127.0.0.1:8199
  4. > User-Agent: curl/7.61.1
  5. > Accept: */*
  6. >
  7. < HTTP/1.1 500 Internal Server Error
  8. < Server: GF HTTP Server
  9. < Date: Sun, 19 Jul 2020 07:44:30 GMT
  10. < Content-Length: 52
  11. < Content-Type: text/plain; charset=utf-8
  12. <
  13. 服务器居然开小差了,请稍后再试吧!

获取异常堆栈

异常堆栈信息

WebServer本身在捕获异常时,如果抛出的异常信息并不包含堆栈内容,那么WebServer会自动获取抛出异常位点(即panic的位置)的堆栈并创建一个新的包含该堆栈信息的错误对象。我们来看一个示例。

  1. package main
  2. import (
  3. "github.com/gogf/gf/v2/frame/g"
  4. "github.com/gogf/gf/v2/net/ghttp"
  5. )
  6. func MiddlewareErrorHandler(r *ghttp.Request) {
  7. r.Middleware.Next()
  8. if err := r.GetError(); err != nil {
  9. r.Response.ClearBuffer()
  10. r.Response.Writef("%+v", err)
  11. }
  12. }
  13. func main() {
  14. s := g.Server()
  15. s.Use(MiddlewareErrorHandler)
  16. s.Group("/api.v2", func(group *ghttp.RouterGroup) {
  17. group.ALL("/user/list", func(r *ghttp.Request) {
  18. panic("db error: sql is xxxxxxx")
  19. })
  20. })
  21. s.SetPort(8199)
  22. s.Run()
  23. }

可以看到,我们通过%+v的格式化打印来获取异常错误中的堆栈信息,具体原理请参考章节:错误处理-堆栈特性。执行后,我们通过curl工具来测试下:

  1. $ curl "http://127.0.0.1:8199/api.v2/user/list"
  2. db error: sql is xxxxxxx
  3. 1. db error: sql is xxxxxxx
  4. 1). main.main.func1.1
  5. /Users/john/Workspace/Go/GOPATH/src/github.com/gogf/gf/.example/other/test.go:25
  6. 2). github.com/gogf/gf/v2/net/ghttp.(*middleware).Next.func1.8
  7. /Users/john/Workspace/Go/GOPATH/src/github.com/gogf/gf/v2/net/ghttp/ghttp_request_middleware.go:111
  8. 3). github.com/gogf/gf/v2/net/ghttp.niceCallFunc
  9. /Users/john/Workspace/Go/GOPATH/src/github.com/gogf/gf/v2/net/ghttp/ghttp_func.go:46
  10. 4). github.com/gogf/gf/v2/net/ghttp.(*middleware).Next.func1
  11. /Users/john/Workspace/Go/GOPATH/src/github.com/gogf/gf/v2/net/ghttp/ghttp_request_middleware.go:110
  12. 5). github.com/gogf/gf/v2/util/gutil.TryCatch
  13. /Users/john/Workspace/Go/GOPATH/src/github.com/gogf/gf/v2/util/gutil/gutil.go:46
  14. 6). github.com/gogf/gf/v2/net/ghttp.(*middleware).Next
  15. /Users/john/Workspace/Go/GOPATH/src/github.com/gogf/gf/v2/net/ghttp/ghttp_request_middleware.go:47
  16. 7). main.MiddlewareErrorHandler
  17. /Users/john/Workspace/Go/GOPATH/src/github.com/gogf/gf/.example/other/test.go:10
  18. 8). github.com/gogf/gf/v2/net/ghttp.(*middleware).Next.func1.9
  19. /Users/john/Workspace/Go/GOPATH/src/github.com/gogf/gf/v2/net/ghttp/ghttp_request_middleware.go:117
  20. 9). github.com/gogf/gf/v2/net/ghttp.niceCallFunc
  21. /Users/john/Workspace/Go/GOPATH/src/github.com/gogf/gf/v2/net/ghttp/ghttp_func.go:46
  22. 10). github.com/gogf/gf/v2/net/ghttp.(*middleware).Next.func1
  23. /Users/john/Workspace/Go/GOPATH/src/github.com/gogf/gf/v2/net/ghttp/ghttp_request_middleware.go:116
  24. 11). github.com/gogf/gf/v2/util/gutil.TryCatch
  25. /Users/john/Workspace/Go/GOPATH/src/github.com/gogf/gf/v2/util/gutil/gutil.go:46
  26. 12). github.com/gogf/gf/v2/net/ghttp.(*middleware).Next
  27. /Users/john/Workspace/Go/GOPATH/src/github.com/gogf/gf/v2/net/ghttp/ghttp_request_middleware.go:47
  28. 13). github.com/gogf/gf/v2/net/ghttp.(*Server).ServeHTTP
  29. /Users/john/Workspace/Go/GOPATH/src/github.com/gogf/gf/v2/net/ghttp/ghttp_server_handler.go:122

错误堆栈信息

如果抛出的异常是一个通过gerror组件的错误对象,或者实现堆栈打印接口的错误对象,由于该异常的错误对象已经包含了详细的堆栈信息,那么WebServer将会直接返回该错误对象,不会自动创建错误对象。我们来看一个示例。

  1. package main
  2. import (
  3. "github.com/gogf/gf/v2/errors/gerror"
  4. "github.com/gogf/gf/v2/frame/g"
  5. "github.com/gogf/gf/v2/net/ghttp"
  6. )
  7. func MiddlewareErrorHandler(r *ghttp.Request) {
  8. r.Middleware.Next()
  9. if err := r.GetError(); err != nil {
  10. r.Response.ClearBuffer()
  11. r.Response.Writef("%+v", err)
  12. }
  13. }
  14. func DbOperation() error {
  15. // ...
  16. return gerror.New("DbOperation error: sql is xxxxxxx")
  17. }
  18. func UpdateData() {
  19. err := DbOperation()
  20. if err != nil {
  21. panic(gerror.Wrap(err, "UpdateData error"))
  22. }
  23. }
  24. func main() {
  25. s := g.Server()
  26. s.Use(MiddlewareErrorHandler)
  27. s.Group("/api.v2", func(group *ghttp.RouterGroup) {
  28. group.ALL("/user/list", func(r *ghttp.Request) {
  29. UpdateData()
  30. })
  31. })
  32. s.SetPort(8199)
  33. s.Run()
  34. }

执行后,我们通过curl工具来测试下:

  1. $ curl "http://127.0.0.1:8199/api.v2/user/list"
  2. UpdateData error: DbOperation error: sql is xxxxxxx
  3. 1. UpdateData error
  4. 1). main.UpdateData
  5. /Users/john/Workspace/Go/GOPATH/src/github.com/gogf/gf/.example/other/test.go:25
  6. 2). main.main.func1.1
  7. /Users/john/Workspace/Go/GOPATH/src/github.com/gogf/gf/.example/other/test.go:34
  8. 3). github.com/gogf/gf/v2/net/ghttp.(*middleware).Next.func1.8
  9. /Users/john/Workspace/Go/GOPATH/src/github.com/gogf/gf/v2/net/ghttp/ghttp_request_middleware.go:111
  10. 4). github.com/gogf/gf/v2/net/ghttp.niceCallFunc
  11. /Users/john/Workspace/Go/GOPATH/src/github.com/gogf/gf/v2/net/ghttp/ghttp_func.go:46
  12. 5). github.com/gogf/gf/v2/net/ghttp.(*middleware).Next.func1
  13. /Users/john/Workspace/Go/GOPATH/src/github.com/gogf/gf/v2/net/ghttp/ghttp_request_middleware.go:110
  14. 6). github.com/gogf/gf/v2/util/gutil.TryCatch
  15. /Users/john/Workspace/Go/GOPATH/src/github.com/gogf/gf/v2/util/gutil/gutil.go:46
  16. 7). github.com/gogf/gf/v2/net/ghttp.(*middleware).Next
  17. /Users/john/Workspace/Go/GOPATH/src/github.com/gogf/gf/v2/net/ghttp/ghttp_request_middleware.go:47
  18. 8). main.MiddlewareErrorHandler
  19. /Users/john/Workspace/Go/GOPATH/src/github.com/gogf/gf/.example/other/test.go:10
  20. 9). github.com/gogf/gf/v2/net/ghttp.(*middleware).Next.func1.9
  21. /Users/john/Workspace/Go/GOPATH/src/github.com/gogf/gf/v2/net/ghttp/ghttp_request_middleware.go:117
  22. 10). github.com/gogf/gf/v2/net/ghttp.niceCallFunc
  23. /Users/john/Workspace/Go/GOPATH/src/github.com/gogf/gf/v2/net/ghttp/ghttp_func.go:46
  24. 11). github.com/gogf/gf/v2/net/ghttp.(*middleware).Next.func1
  25. /Users/john/Workspace/Go/GOPATH/src/github.com/gogf/gf/v2/net/ghttp/ghttp_request_middleware.go:116
  26. 12). github.com/gogf/gf/v2/util/gutil.TryCatch
  27. /Users/john/Workspace/Go/GOPATH/src/github.com/gogf/gf/v2/util/gutil/gutil.go:46
  28. 13). github.com/gogf/gf/v2/net/ghttp.(*middleware).Next
  29. /Users/john/Workspace/Go/GOPATH/src/github.com/gogf/gf/v2/net/ghttp/ghttp_request_middleware.go:47
  30. 14). github.com/gogf/gf/v2/net/ghttp.(*Server).ServeHTTP
  31. /Users/john/Workspace/Go/GOPATH/src/github.com/gogf/gf/v2/net/ghttp/ghttp_server_handler.go:122
  32. 2. DbOperation error: sql is xxxxxxx
  33. 1). main.DbOperation
  34. /Users/john/Workspace/Go/GOPATH/src/github.com/gogf/gf/.example/other/test.go:19
  35. 2). main.UpdateData
  36. /Users/john/Workspace/Go/GOPATH/src/github.com/gogf/gf/.example/other/test.go:23
  37. 3). main.main.func1.1
  38. /Users/john/Workspace/Go/GOPATH/src/github.com/gogf/gf/.example/other/test.go:34
  39. 4). github.com/gogf/gf/v2/net/ghttp.(*middleware).Next.func1.8
  40. /Users/john/Workspace/Go/GOPATH/src/github.com/gogf/gf/v2/net/ghttp/ghttp_request_middleware.go:111
  41. 5). github.com/gogf/gf/v2/net/ghttp.niceCallFunc
  42. /Users/john/Workspace/Go/GOPATH/src/github.com/gogf/gf/v2/net/ghttp/ghttp_func.go:46
  43. 6). github.com/gogf/gf/v2/net/ghttp.(*middleware).Next.func1
  44. /Users/john/Workspace/Go/GOPATH/src/github.com/gogf/gf/v2/net/ghttp/ghttp_request_middleware.go:110
  45. 7). github.com/gogf/gf/v2/util/gutil.TryCatch
  46. /Users/john/Workspace/Go/GOPATH/src/github.com/gogf/gf/v2/util/gutil/gutil.go:46
  47. 8). github.com/gogf/gf/v2/net/ghttp.(*middleware).Next
  48. /Users/john/Workspace/Go/GOPATH/src/github.com/gogf/gf/v2/net/ghttp/ghttp_request_middleware.go:47
  49. 9). main.MiddlewareErrorHandler
  50. /Users/john/Workspace/Go/GOPATH/src/github.com/gogf/gf/.example/other/test.go:10
  51. 10). github.com/gogf/gf/v2/net/ghttp.(*middleware).Next.func1.9
  52. /Users/john/Workspace/Go/GOPATH/src/github.com/gogf/gf/v2/net/ghttp/ghttp_request_middleware.go:117
  53. 11). github.com/gogf/gf/v2/net/ghttp.niceCallFunc
  54. /Users/john/Workspace/Go/GOPATH/src/github.com/gogf/gf/v2/net/ghttp/ghttp_func.go:46
  55. 12). github.com/gogf/gf/v2/net/ghttp.(*middleware).Next.func1
  56. /Users/john/Workspace/Go/GOPATH/src/github.com/gogf/gf/v2/net/ghttp/ghttp_request_middleware.go:116
  57. 13). github.com/gogf/gf/v2/util/gutil.TryCatch
  58. /Users/john/Workspace/Go/GOPATH/src/github.com/gogf/gf/v2/util/gutil/gutil.go:46
  59. 14). github.com/gogf/gf/v2/net/ghttp.(*middleware).Next
  60. /Users/john/Workspace/Go/GOPATH/src/github.com/gogf/gf/v2/net/ghttp/ghttp_request_middleware.go:47
  61. 15). github.com/gogf/gf/v2/net/ghttp.(*Server).ServeHTTP
  62. /Users/john/Workspace/Go/GOPATH/src/github.com/gogf/gf/v2/net/ghttp/ghttp_server_handler.go:122