本文我们介绍生产环境上如何通过捕捉异常recovery来完善程序设计和提高用户体验。

Golang异常处理

golang 的异常处理比较简单,通常都是在程序遇到异常崩溃panic之后通过defer调用延迟函数捕捉异常,并对异常信息进行输出和记录。

  • 异常处理代码
defer func() {
if err := recover(); err != nil {
fmt.Println(err)
... // 上报异常 或者 发送告警
}
}()

通过Gin中间件捕捉异常

  • 内置中间件

gin在gin.Default中就使用了自带的Recovery函数,将状态码置为500并输出错误信息到终端

func Recovery() HandlerFunc {
return RecoveryWithWriter(DefaultErrorWriter)
} // RecoveryWithWriter returns a middleware for a given writer that recovers from any panics and writes a 500 if there was one.
func RecoveryWithWriter(out io.Writer) HandlerFunc {
var logger *log.Logger
if out != nil {
logger = log.New(out, "\n\n\x1b[31m", log.LstdFlags)
}
return func(c *Context) {
defer func() {
if err := recover(); err != nil {
// Check for a broken connection, as it is not really a
// condition that warrants a panic stack trace.
var brokenPipe bool
if ne, ok := err.(*net.OpError); ok {
if se, ok := ne.Err.(*os.SyscallError); ok {
if strings.Contains(strings.ToLower(se.Error()), "broken pipe") || strings.Contains(strings.ToLower(se.Error()), "connection reset by peer") {
brokenPipe = true
}
}
}
if logger != nil {
stack := stack(3)
httpRequest, _ := httputil.DumpRequest(c.Request, false)
headers := strings.Split(string(httpRequest), "\r\n")
for idx, header := range headers {
current := strings.Split(header, ":")
if current[0] == "Authorization" {
headers[idx] = current[0] + ": *"
}
}
if brokenPipe {
logger.Printf("%s\n%s%s", err, string(httpRequest), reset)
} else if IsDebugging() {
logger.Printf("[Recovery] %s panic recovered:\n%s\n%s\n%s%s",
timeFormat(time.Now()), strings.Join(headers, "\r\n"), err, stack, reset)
} else {
logger.Printf("[Recovery] %s panic recovered:\n%s\n%s%s",
timeFormat(time.Now()), err, stack, reset)
}
} // If the connection is dead, we can't write a status to it.
if brokenPipe {
c.Error(err.(error)) // nolint: errcheck
c.Abort()
} else {
c.AbortWithStatus(http.StatusInternalServerError)
}
}
}()
c.Next()
}
}
  • 自定义中间件

内置的Recovery函数功能比较简单,我们需要重新开发自定义的异常处理中间件。

在Gin-IPs项目中我们将堆栈信息上报到Redis,方便监控。同时,我们还通过traceId对该请求进行简单的链路跟踪,可以方便定位到请求日志。

// 日志打印没必要异步处理,一般crash比较少
func Recovery() gin.HandlerFunc {
log, _ := mylog.New(
configure.GinConfigValue.ErrorLog.Path, configure.GinConfigValue.ErrorLog.Name,
configure.GinConfigValue.ErrorLog.Level, nil, configure.GinConfigValue.ErrorLog.Count)
log.Info("Test Panic")
return func(c *gin.Context) {
defer func() {
if err := recover(); err != nil {
response := route_response.Response{}
response.Data.List = []interface{}{} // 初始化为空切片,而不是空引用
traceId := c.Writer.Header().Get("X-Request-Trace-Id")
stackMsg := string(debug.Stack())
logField := map[string]interface{}{
"trace_id": traceId, // 鉴权之后可以得到唯一跟踪ID和用户名
"user": c.Writer.Header().Get("X-Request-User"),
"uri": c.Request.URL.Path,
"remote_addr": c.ClientIP(),
"stack": stackMsg, // 打印堆栈信息
}
c.Abort()
response.Code, response.Message = configure.ApiInnerResponseError, fmt.Sprintf("Api内部报错,请联系管理员(id=%s", traceId)
log.WithFields(logField).Error(err) // 输出panic 信息
redisField := make(map[string]interface{})
for k, v := range logField {
redisField[k] = v
}
redisField["time"] = time.Now().Format("2006-01-02 15:04:05")
redisField["error"] = err
dao.ModelClient.RedisClient.HMSet(traceId, redisField) // 上报redis
c.JSON(http.StatusUnauthorized, response)
return
}
}() c.Next()
}
}
  • redis查询异常
10.2.147.167:11700[1]> keys *
1) "445ffc1bb864000"
2) "445ff1b25864000" 10.2.147.167:11700[1]> hgetall 445ffc1bb864000
1) "time"
2) "2020-09-03 16:42:46"
3) "error"
4) "this is test panic"
5) "user"
6) "xiaoming"
7) "remote_addr"
8) "127.0.0.1"
9) "uri"
10) "/"
11) "stack"
12) "goroutine 274...." # ...省略

至此,我们将异常捕捉模块也完成了,这其中不仅涉及到异常处理,还简单的完成了程序内部请求链路跟踪,异常信息落地到Redis也为日后的运维监控做好准备。

Github 代码

请访问 Gin-IPs 或者搜索 Gin-IPs

【Gin-API系列】Gin中间件之异常处理(六)的更多相关文章

  1. Web API系列(三) 异常处理

    在上一篇教程中我为大家介绍了Web API中Filter的开发使用,其中讲到ExceptionFilter时留了一个坑:ExceptionFilter只能截获并处理Action执行过程中发生的异常,在 ...

  2. Redis总结(五)缓存雪崩和缓存穿透等问题 Web API系列(三)统一异常处理 C#总结(一)AutoResetEvent的使用介绍(用AutoResetEvent实现同步) C#总结(二)事件Event 介绍总结 C#总结(三)DataGridView增加全选列 Web API系列(二)接口安全和参数校验 RabbitMQ学习系列(六): RabbitMQ 高可用集群

    Redis总结(五)缓存雪崩和缓存穿透等问题   前面讲过一些redis 缓存的使用和数据持久化.感兴趣的朋友可以看看之前的文章,http://www.cnblogs.com/zhangweizhon ...

  3. Gin框架系列03:换个姿势理解中间件

    什么是中间件 中间件,英译middleware,顾名思义,放在中间的物件,那么放在谁中间呢?本来,客户端可以直接请求到服务端接口. 现在,中间件横插一脚,它能在请求到达接口之前拦截请求,做一些特殊处理 ...

  4. Gin框架系列01:极速上手

    Gin是什么? Gin是Go语言编写的web框架,具备中间件.崩溃处理.JSON验证.内置渲染等多种功能. 准备工作 本系列演示所有代码都在Github中,感兴趣的同学可以自行查阅,欢迎大家一起完善. ...

  5. Web API系列(三)统一异常处理

    前面讲了webapi的安全验证和参数安全,不清楚的朋友,可以看看前面的文章,<Web API系列(二)接口安全和参数校验>,本文主要介绍Web API异常结果的处理.作为内部或者是对外提供 ...

  6. Gin实战:Gin+Mysql简单的Restful风格的API(二)

    上一篇介绍了Gin+Mysql简单的Restful风格的API,但代码放在一个文件中,还不属于restful风格,接下来将进行进一步的封装. 目录结构 ☁ gin_restful2 tree . ├─ ...

  7. Web API系列(二)接口安全和参数校验

    以前简单介绍过web api 的设计,但是还是有很多朋友问我,如何合理的设计和实现web api.比如,接口安全,异常处理,统一数据返回等问题.所以有必要系统的总结总结 web api 的设计和实现. ...

  8. ASP.NET Web API系列教程目录

    ASP.NET Web API系列教程目录 Introduction:What's This New Web API?引子:新的Web API是什么? Chapter 1: Getting Start ...

  9. ASP.NET Web API系列教程(目录)(转)

    注:微软随ASP.NET MVC 4一起还发布了一个框架,叫做ASP.NET Web API.这是一个用来在.NET平台上建立HTTP服务的Web API框架,是微软的又一项令人振奋的技术.目前,国内 ...

  10. [转]ASP.NET Web API系列教程(目录)

    本文转自:http://www.cnblogs.com/r01cn/archive/2012/11/11/2765432.html 注:微软随ASP.NET MVC 4一起还发布了一个框架,叫做ASP ...

随机推荐

  1. Pytorch Autograd (自动求导机制)

    Pytorch Autograd (自动求导机制) Introduce Pytorch Autograd库 (自动求导机制) 是训练神经网络时,反向误差传播(BP)算法的核心. 本文通过logisti ...

  2. ASP.NET中使用Cache类来缓存页面的信息

    实现 如果将数据保存在全局应用程序对象Application中,值将会在程序运行时一直存在,而我们只需要缓存一段时间. ASP.NET提供了一个Cache对象来执行对象数据的缓存. Cache对象是S ...

  3. 034_go语言中的工作池

    代码演示 package main import "fmt" import "time" func worker(id int, jobs <-chan ...

  4. jenkins集成spring boot持续化构建代码

    我个人使用的是阿里云的云服务器,项目采用的是spring boot为框架,现在要做的功能就是将本地开发的代码提交到github中,通过jenkins自动化集成部署到云服务器.接下来开始步骤. 1 首先 ...

  5. CSS表单与数据表(下)

    2.表单 表单是用户输入内容的地方.表单涉及的控件很多,而且一直很难给它们应用样式.无法控制样式的部分,可以通过自定义控件来解决. 2.1 简单的表单 2.1.1 fieldset与legend fi ...

  6. 从零搭建Spring Boot脚手架(2):增加通用的功能

    1. 前言 今天开始搭建我们的kono Spring Boot脚手架,首先会集成Spring MVC并进行定制化以满足日常开发的需要,我们先做一些刚性的需求定制,后续再补充细节.如果你看了本文有什么问 ...

  7. 16、Mediator 仲裁者模式

    只有一个仲裁者 Mediator 模式 组员向仲裁者报告,仲裁者向组员下达指示,组员之间不在相互询问和相互指示. 要调整多个对象之间的关系时,就需要用到 Mediator 模式.将逻辑处理交给仲裁者执 ...

  8. stat 命令家族(2)- 详解 pidstat

    性能测试必备的 Linux 命令系列,可以看下面链接的文章哦 https://www.cnblogs.com/poloyy/category/1819490.html 介绍 对 Linux 任务的统计 ...

  9. Spring,SpringMVC,MyBatis,Hibernate,Servlet的生命周期,jsp有哪些内置对象,Tomcat,Cookie和Session的作用以及区别,oracle,MYSQL等面试题总结

    1. 什么是Spring,谈谈你对Spring的理解 Spring是我们JAVA开发人员在搭建后台时选用的一个轻量级的开源框架,Spring框架目前也是功能丰富,十分优秀企业级采用最多的一个框架. S ...

  10. 简单快速导出word文档

    最近,我写公司项目word导出功能,应该只有2小时的工作量,却被硬生生的拉长2天,项目上线到业务正常运行也被拉长到2个星期. 为什么如此浪费时间呢? 1)公司的项目比较老,采用硬编码模式,意味着wor ...