专栏首页新亮笔记[系列] - go-gin-api 路由中间件 - 捕获异常(四)

[系列] - go-gin-api 路由中间件 - 捕获异常(四)

概述

首先同步下项目概况:

上篇文章分享了,路由中间件 - 日志记录,这篇文章咱们分享:路由中间件 - 捕获异常。

当系统发生异常时,提示 “系统异常,请联系管理员!”,并发送 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 rangegoroutine 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。
  • 测试发邮件时,一定要配置邮箱信息。

源码地址

https://github.com/xinliangnote/go-gin-api

本文分享自微信公众号 - 新亮笔记(XinLiangTalk),作者:訢亮

原文出处及转载信息见文内详细说明,如有侵权,请联系 yunjia_community@tencent.com 删除。

原始发表时间:2019-09-10

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

我来说两句

0 条评论
登录 后参与评论

相关文章

  • 《三》Swoole WebSocket 的应用

    WebSocket 使得客户端和服务器之间的数据交换变得更加简单,允许服务端主动向客户端推送数据。

    新亮
  • 《四》Swoole HTTP 的应用

    我们都知道 HTTP 是一种协议,允许 WEB 服务器和浏览器通过互联网进行发送和接受数据。

    新亮
  • Go - chan 通道

    在 go 关键字后面加一个函数,就可以创建一个线程,函数可以为已经写好的函数,也可以是匿名函数。

    新亮
  • JS:this、hoisting、precedence

    WEBJ2EE
  • Kubernetes(11:打包)

    Helm有两个重要的概念:chart和release。 chart是创建一个应用的信息集合,包括各种Kubernetes对象的配置模板、参数定义、依赖关系、文...

    用户5760343
  • 闭包是更好的对象---从Reload配置说起

    如果项目中,到处用了一个全局对象的指针,那一定是很糟糕的. 我们假设这个对象就是Conf,也就是全局配置. 现在要改进代码,实现Reload Conf. ...

    Linker
  • squid代理http和https方式上网的操作记录

    背景: 公司IDC机房有一台服务器A,只有内网环境:192.168.1.150 现在需要让这台服务器能对外访问,能正常访问http和https请求(即80端口和...

    洗尽了浮华
  • 「JAVA」从语法到案例,从定义到使用,详细解释Java 内部类的完全实现

    Java 类中的可以定义的成员有:字段、方法、内部类,内部类是定义在类结构中的另一个类,因为定义在类的内部,故称为内部类。

    老夫不正经
  • 4亿工人将因机器人替代而失业,政府应该做些什么?

    根据麦肯锡全球研究院的报告,被自动化淘汰的大量工人仍有就业机会,但首先政府需要着手展开大规模再培训和基建投资。 ? 从现在起到2030年,数以亿计的工人将因自动...

    企鹅号小编
  • 何恺明等NeurlPS新作:定义迁移学习新范式

    【摘要】基于深度学习的迁移学习的主流方法一般是从一个任务中学习到可迁移到其他任务的通用特征向量,例如语言中的单词嵌入和视觉中的预训练卷积特征(比如imagene...

    张俊怡

扫码关注云+社区

领取腾讯云代金券