基本介绍
目前大多数的第三方WebServer
库均没有默认对HTTP
请求处理过程中产生的异常进行捕获,轻者错误产生后无法记录到日志造成排查错困难,重则异常造成进程直接崩溃,服务不可用。
当你选择goframe
,你很幸运。裸奔谁都会,但作为一款企业级的基础开发框架,对严谨及安全性的要求大于性能,因此默认情况下,执行过程中产生的panic
是有被Server
自动捕获的,产生panic
时当前执行流程会立即中止,但是绝对不会影响进程直接崩溃。
获取异常错误
HTTP
执行流程中产生panic
异常时,默认处理是记录到Server
的日志文件中。当然,开发者也可以通过注册中间件方式手动捕获,然后自定义相关的错误处理。这一操作其实在中间件章节的示例中也有介绍,我们这里来再仔细说明下。
开发者不能通过recover
方法来捕获异常,因为goframe
框架的Server
已经做了捕获,并且为保证默认情况下异常不会引起进程崩溃,因此不会再次往上抛异常。
该方法往往使用在流程控制组件中,如后置中间件或者HOOK
钩子方法中。
我们这里使用一个全局的后置中间件来捕获异常,当异常产生后,捕获并写入到指定的日志文件中,返回固定友好的提示信息,避免敏感的报错信息暴露给调用端。
需要注意的是:
- 即使开发者有自己捕获记录异常错误的日志,但是
Server
依旧会打印到Server
自己的错误日志文件中。由开发者调用日志接口方法输出的日志属于业务日志(与业务相关),而Server
自行管理的日志属于服务日志(类似于nginx
的error.log
)。 - 由于
goframe
框架大部分的底层错误都包含有错误时的堆栈信息,如果对于error
的具体堆栈信息感兴趣(具体调用链、报错文件路径、源码行号等),可以使用gerror
来获取,具体请参考 章节。如果异常包含堆栈信息,默认情况下会打印到Server
的error
日志文件中。
package main
import (
"github.com/gogf/gf/v2/frame/g"
"github.com/gogf/gf/v2/net/ghttp"
)
func MiddlewareErrorHandler(r *ghttp.Request) {
r.Middleware.Next()
if err := r.GetError(); err != nil {
// 记录到自定义错误日志文件
g.Log("exception").Error(err)
//返回固定的友好信息
r.Response.ClearBuffer()
r.Response.Writeln("服务器居然开小差了,请稍后再试吧!")
}
func main() {
s := g.Server()
s.Use(MiddlewareErrorHandler)
s.Group("/api.v2", func(group *ghttp.RouterGroup) {
group.ALL("/user/list", func(r *ghttp.Request) {
panic("db error: sql is xxxxxxx")
})
s.SetPort(8199)
s.Run()
}
获取异常堆栈
WebServer
本身在捕获异常时,如果抛出的异常信息并不包含堆栈内容,那么WebServer
会自动获取抛出异常位点(即panic
的位置)的堆栈并创建一个新的包含该堆栈信息的错误对象。我们来看一个示例。
package main
import (
"github.com/gogf/gf/v2/frame/g"
"github.com/gogf/gf/v2/net/ghttp"
)
func MiddlewareErrorHandler(r *ghttp.Request) {
r.Middleware.Next()
if err := r.GetError(); err != nil {
r.Response.ClearBuffer()
r.Response.Writef("%+v", err)
}
}
func main() {
s := g.Server()
s.Use(MiddlewareErrorHandler)
s.Group("/api.v2", func(group *ghttp.RouterGroup) {
group.ALL("/user/list", func(r *ghttp.Request) {
panic("db error: sql is xxxxxxx")
})
})
s.SetPort(8199)
s.Run()
}
可以看到,我们通过的格式化打印来获取异常错误中的堆栈信息,具体原理请参考章节:。执行后,我们通过curl
工具来测试下:
如果抛出的异常是一个通过gerror
组件的错误对象,或者实现堆栈打印接口的错误对象,由于该异常的错误对象已经包含了详细的堆栈信息,那么WebServer
将会直接返回该错误对象,不会自动创建错误对象。我们来看一个示例。
package main
import (
"github.com/gogf/gf/v2/errors/gerror"
"github.com/gogf/gf/v2/net/ghttp"
)
func MiddlewareErrorHandler(r *ghttp.Request) {
r.Middleware.Next()
if err := r.GetError(); err != nil {
r.Response.ClearBuffer()
r.Response.Writef("%+v", err)
}
}
func DbOperation() error {
// ...
return gerror.New("DbOperation error: sql is xxxxxxx")
}
func UpdateData() {
err := DbOperation()
if err != nil {
panic(gerror.Wrap(err, "UpdateData error"))
}
}
func main() {
s := g.Server()
s.Use(MiddlewareErrorHandler)
s.Group("/api.v2", func(group *ghttp.RouterGroup) {
group.ALL("/user/list", func(r *ghttp.Request) {
UpdateData()
})
})
s.SetPort(8199)
s.Run()
执行后,我们通过curl
工具来测试下: