[系列] go-gin-api 路由中间件 - 捕获异常(四)
程序员文章站
2022-03-21 18:09:43
[TOC] 概述 首先同步下项目概况: 上篇文章分享了,路由中间件 日志记录,这篇文章咱们分享:路由中间件 捕获异常。当系统发生异常时,提示 “系统异常,请联系管理员!”,同时并发送 panic 告警邮件。 什么是异常? 在 Go 中异常就是 panic,它是在程序运行的时候抛出的,当 panic ......
目录
概述
首先同步下项目概况:
上篇文章分享了,路由中间件 - 日志记录,这篇文章咱们分享:路由中间件 - 捕获异常。当系统发生异常时,提示 “系统异常,请联系管理员!”,同时并发送 panic 告警邮件。
什么是异常?
在 go 中异常就是 panic,它是在程序运行的时候抛出的,当 panic 抛出之后,如果在程序里没有添加任何保护措施的话,控制台就会在打印出 panic 的详细情况,然后终止运行。
我们可以将 panic 分为两种:
一种是有意抛出的,比如,
panic("自定义的 panic 信息")
输出:
2019/09/10 20:25:27 http: panic serving [::1]:61547: 自定义的 panic 信息 goroutine 8 [running]: ...
一种是无意抛出的,写程序马虎造成,比如,
var slice = [] int {1, 2, 3, 4, 5} slice[6] = 6
输出:
2019/09/10 15:27:05 http: panic serving [::1]:61616: runtime error: index out of range goroutine 6 [running]: ...
想象一下,如果在线上环境出现了 panic,命令行输出的,因为咱们无法捕获就无法定位问题呀,想想都可怕,那么问题来了,怎么捕获异常?
怎么捕获异常?
当程序发生 panic 后,在 defer(延迟函数) 内部可以调用 recover 进行捕获。
不多说,直接上代码:
defer func() { if err := recover(); err != nil { fmt.println(err) } }()
在运行一下 “无意抛出的 panic ”,输出:
runtime error: index out of range
ok,错误捕获到了,这时我们可以进行做文章了。
做啥文章,大家应该都知道了吧:
- 获取运行时的调用栈(debug.stack())
- 获取当时的 request 数据
- 组装数据,进行发邮件
那么,go 怎么发邮件呀,有没有开源包呀?
当然有,请往下看。
封装发邮件方法
使用包:gopkg.in/gomail.v2
直接上代码:
func sendmail(mailto string, subject string, body string) error { if config.errornotifyopen != 1 { return nil } m := gomail.newmessage() //设置发件人 m.setheader("from", config.systememailuser) //设置发送给多个用户 mailarrto := strings.split(mailto, ",") m.setheader("to", mailarrto...) //设置邮件主题 m.setheader("subject", subject) //设置邮件正文 m.setbody("text/html", body) d := gomail.newdialer(config.systememailhost, config.systememailport, config.systememailuser, config.systememailpass) err := d.dialandsend(m) if err != nil { fmt.println(err) } return err }
在这块我加了一个开关,想开想关,您随意。
现在会发送邮件了,再整个邮件模板就完美了。
自定义邮件模板
如图:
这就是告警邮件的模板,还不错吧,大家还想记录什么,可以自定义去修改。
封装一个中间件
最后,封装一下。
直接上代码:
func setup() gin.handlerfunc { return func(c *gin.context) { defer func() { if err := recover(); err != nil { debugstack := "" for _, v := range strings.split(string(debug.stack()), "\n") { debugstack += v + "<br>" } subject := fmt.sprintf("【重要错误】%s 项目出错了!", config.appname) body := strings.replaceall(mailtemplate, "{errormsg}", fmt.sprintf("%s", err)) body = strings.replaceall(body, "{requesttime}", util.getcurrentdate()) body = strings.replaceall(body, "{requesturl}", c.request.method + " " + c.request.host + c.request.requesturi) body = strings.replaceall(body, "{requestua}", c.request.useragent()) body = strings.replaceall(body, "{requestip}", c.clientip()) body = strings.replaceall(body, "{debugstack}", debugstack) _ = util.sendmail(config.errornotifyuser, subject, body) utilgin := util.gin{ctx: c} utilgin.response(500, "系统异常,请联系管理员!", nil) } }() c.next() } }
当发生 panic 异常时,输出:
{ "code": 500, "msg": "系统异常,请联系管理员!", "data": null }
同时,还会收到一封 panic 告警邮件。
便于截图,debugstack 删减了一些信息。
到这,就结束了。
备注
- 发邮件的地方,可以调整为异步发送。
- 文章中仅贴了部分代码,相关代码请查阅 github。
- 测试发邮件时,一定要配置邮箱信息。
源码地址
go-gin-api 系列文章
推荐阅读
-
[系列] go-gin-api 路由中间件 - 日志记录(三)
-
[系列] go-gin-api 路由中间件 - Jaeger 链路追踪(六)
-
[系列] go-gin-api 路由中间件 - 签名验证(七)
-
[系列] go-gin-api 路由中间件 - 日志记录(三)
-
go-gin-api 路由中间件 - 捕获异常
-
[系列] go-gin-api 路由中间件 - Jaeger 链路追踪(五)
-
[系列] go-gin-api 路由中间件 - 捕获异常(四)
-
[系列] go-gin-api 路由中间件 - Jaeger 链路追踪(六)
-
go-gin-api 路由中间件 - 捕获异常
-
[系列] go-gin-api 路由中间件 - Jaeger 链路追踪(五)