zoukankan      html  css  js  c++  java
  • 【Gin-API系列】Gin中间件之异常处理(六)

    本文我们介绍生产环境上如何通过捕捉异常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, "
    
    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), "
    ")
    					for idx, header := range headers {
    						current := strings.Split(header, ":")
    						if current[0] == "Authorization" {
    							headers[idx] = current[0] + ": *"
    						}
    					}
    					if brokenPipe {
    						logger.Printf("%s
    %s%s", err, string(httpRequest), reset)
    					} else if IsDebugging() {
    						logger.Printf("[Recovery] %s panic recovered:
    %s
    %s
    %s%s",
    							timeFormat(time.Now()), strings.Join(headers, "
    "), err, stack, reset)
    					} else {
    						logger.Printf("[Recovery] %s panic recovered:
    %s
    %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

  • 相关阅读:
    DataGird导出EXCEL的几个方法
    csv文件与DataTable互相导入处理
    LeetCode 345
    LeetCode 168
    LeetCode 344
    LeetCode 342
    LeetCode 343
    LeetCode 326
    LeetCode 338
    LeetCode 319
  • 原文地址:https://www.cnblogs.com/lxmhhy/p/13608517.html
Copyright © 2011-2022 走看看