hincky的主页 hincky的主页
  • 学习笔记

    • Vue笔记
    • Vuepress
    • nginx
  • 语言类

    • java
    • go
    • python
    • 设计模式
  • 框架类

    • Spring
    • Spring Security
    • Mybatis
  • 容器技术

    • docker
    • k8s
    • helm
    • prometheus
    • grafana
    • jenkins
  • 命令集合

    • linux命令
    • docker命令
    • git命令
    • vim命令
    • k8s命令
  • 数据库

    • sql
    • mysql
  • 协议

    • 网络模型
    • http/1.1
    • WebSocket
    • http/2
    • TLS/SSL
    • tcp
    • IP
    • tcpdump抓包命令
    • wireshark抓包工具
  • 通用

    • Git
  • 技术分享

    • git push/pull总是超时怎么办
    • idea debug技巧
    • postman使用
    • 问题总结
    • idea使用技巧
  • Oauth2

    • Oauth2原理
  • 项目列表

    • redis项目
    • 微服务项目
  • 分类
  • 标签
  • 归档
  • 随笔
GitHub (opens new window)

Hincky

当有趣的人,做想做的事
  • 学习笔记

    • Vue笔记
    • Vuepress
    • nginx
  • 语言类

    • java
    • go
    • python
    • 设计模式
  • 框架类

    • Spring
    • Spring Security
    • Mybatis
  • 容器技术

    • docker
    • k8s
    • helm
    • prometheus
    • grafana
    • jenkins
  • 命令集合

    • linux命令
    • docker命令
    • git命令
    • vim命令
    • k8s命令
  • 数据库

    • sql
    • mysql
  • 协议

    • 网络模型
    • http/1.1
    • WebSocket
    • http/2
    • TLS/SSL
    • tcp
    • IP
    • tcpdump抓包命令
    • wireshark抓包工具
  • 通用

    • Git
  • 技术分享

    • git push/pull总是超时怎么办
    • idea debug技巧
    • postman使用
    • 问题总结
    • idea使用技巧
  • Oauth2

    • Oauth2原理
  • 项目列表

    • redis项目
    • 微服务项目
  • 分类
  • 标签
  • 归档
  • 随笔
GitHub (opens new window)
  • java

  • python

  • Spring

  • SpringMVC

  • SpringSecurity

  • Mybatis

  • 设计模式

  • Go

    • 基础语法

    • Gin框架

    • Go gRPC

    • go-gin-api [文档]

      • 使用 go modules 初始化项目
        • 概述
        • 初始化
        • 添加依赖包
        • go mod 命令
        • 小结
        • 源码地址
      • 规划项目目录和参数验证
        • 概述
        • 规划目录结构
        • 模型绑定和验证
        • 自定义验证器
        • 制定 API 返回结构
        • 源码地址
        • go-gin-api 系列文章
        • 备注
      • 路由中间件 - 日志记录
        • 概述
        • gin.Logger()
        • 自定义 Logger()
        • 源码地址
        • go-gin-api 系列文章
      • 路由中间件 - 捕获异常
        • 概述
        • 什么是异常?
        • 怎么捕获异常?
        • 封装发邮件方法
        • 自定义邮件模板
        • 封装一个中间件
        • 备注
        • 源码地址
        • go-gin-api 系列文章
      • 路由中间件 - 链路追踪(Jaeger)
        • 概述
        • 如何设计日志记录?
        • 开源工具
        • Jaeger 架构图
        • Jaeger Span
        • Jaeger 部署
        • Jaeger 端口
        • Jaeger 采样率
        • Jaeger 缺点
        • 实战
        • 源码地址
        • go-gin-api 系列文章
      • 路由中间件 - 链路追踪(Jaeger)实战
        • 概述
        • Jaeger 部署
        • 准备测试服务
          • 听(listen)
          • 说(speak)
          • 读(read)
          • 写(write)
          • 唱(sing)
        • 应用示例
          • 实例化 Tracer
          • HTTP 注入
          • HTTP 拦截
          • gRPC 注入
          • gRPC 拦截
        • 运行
          • 启动服务
          • 访问路由
        • 效果
        • API 源码地址
        • Service 源码地址
        • go-gin-api 系列文章
      • 路由中间件 - 签名验证
        • 概览
        • MD5 组合
          • 生成签名
          • 验证签名
          • 中间件 - 代码实现
        • AES 对称加密
          • 生成签名
          • 验证签名
          • 中间件 - 代码实现
        • RSA 非对称加密
          • 创建签名
          • 验证签名
          • 中间件 - 代码实现
        • 如何调用?
        • 性能测试
          • MD5
          • AES
          • RSA
        • PHP 与 Go 加密方法如何互通?
        • 源码地址
        • go-gin-api 系列文章
    • go基本介绍
      • 安装go
      • hello world
      • 变量类型
      • 变量和声明
        • 定义和赋值分开
        • 定义和赋值合并
        • 最简单的写法
        • 最简写法结合函数
        • 多个变量同时赋值
        • 变量总结
      • if
      • 导入包
      • 运行go代码
      • 函数声明
        • 返回值个数
        • 参数接收
        • 两个参数都接收
        • 只接收其中一个参数
    • Go结构体
      • 定义结构体
      • 声明和初始化
        • 结构体字段
        • 结构体初始化
        • 函数参数传递方式
        • 镜像复制
        • 指针
      • 结构体上的函数
      • 构造器
        • new
    • Go相关项目推荐
      • 收录golang的一些好的开源项目
  • 后端
  • Go
  • go-gin-api [文档]
hincky
2022-11-21
目录

路由中间件 - 捕获异常

# 概述

首先同步下项目概况:

上篇文章分享了,路由中间件 - 日志记录,这篇文章咱们分享:路由中间件 - 捕获异常。当系统发生异常时,提示 “系统异常,请联系管理员!”,同时并发送 panic 告警邮件。

# 什么是异常?

在 Go 中异常就是 panic,它是在程序运行的时候抛出的,当 panic 抛出之后,如果在程序里没有添加任何保护措施的话,控制台就会在打印出 panic 的详细情况,然后终止运行。

我们可以将 panic 分为两种:

一种是有意抛出的,比如,

panic("自定义的 panic 信息")
1

输出:

2019/09/10 20:25:27 http: panic serving [::1]:61547: 自定义的 panic 信息
goroutine 8 [running]:
...
1
2
3

一种是无意抛出的,写程序马虎造成,比如,

var slice = [] int {1, 2, 3, 4, 5}

slice[6] = 6
1
2
3

输出:

2019/09/10 15:27:05 http: panic serving [::1]:61616: runtime error: index out of range
goroutine 6 [running]:
...
1
2
3

想象一下,如果在线上环境出现了 panic,命令行输出的,因为咱们无法捕获就无法定位问题呀,想想都可怕,那么问题来了,怎么捕获异常?

# 怎么捕获异常?

当程序发生 panic 后,在 defer(延迟函数) 内部可以调用 recover 进行捕获。

不多说,直接上代码:

defer func() {
	if err := recover(); err != nil {
		fmt.Println(err)
	}
}()
1
2
3
4
5

在运行一下 “无意抛出的 panic ”,输出:

runtime error: index out of range
1

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
}
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

在这块我加了一个开关,想开想关,您随意。

现在会发送邮件了,再整个邮件模板就完美了。

# 自定义邮件模板

如图:

这就是告警邮件的模板,还不错吧,大家还想记录什么,可以自定义去修改。

# 封装一个中间件

最后,封装一下。

直接上代码:

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()
	}
}
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

当发生 panic 异常时,输出:

{
    "code": 500,
    "msg": "系统异常,请联系管理员!",
    "data": null
}
1
2
3
4
5

同时,还会收到一封 panic 告警邮件。

便于截图,DebugStack 删减了一些信息。

到这,就结束了。

# 备注

  • 发邮件的地方,可以调整为异步发送。
  • 文章中仅贴了部分代码,相关代码请查阅 github。
  • 测试发邮件时,一定要配置邮箱信息。

# 源码地址

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

# go-gin-api 系列文章

  • 1. 使用 go modules 初始化项目 (opens new window)
  • 2. 规划项目目录和参数验证 (opens new window)
  • 3. 路由中间件 - 日志记录 (opens new window)
编辑 (opens new window)
路由中间件 - 日志记录
路由中间件 - 链路追踪(Jaeger)

← 路由中间件 - 日志记录 路由中间件 - 链路追踪(Jaeger)→

最近更新
01
人生前期重要的能力
05-17
02
防火墙命令
04-11
03
docker-compose部署mysql主从集群
03-22
更多文章>
Theme by Vdoing | Copyright © 2022-2023 Hincky | MIT License | 粤ICP备2022120427号
  • 跟随系统
  • 浅色模式
  • 深色模式
  • 阅读模式