|
@@ -0,0 +1,64 @@
|
|
|
|
|
+package middlewares
|
|
|
|
|
+
|
|
|
|
|
+import (
|
|
|
|
|
+ "net"
|
|
|
|
|
+ "net/http"
|
|
|
|
|
+ "net/http/httputil"
|
|
|
|
|
+ "os"
|
|
|
|
|
+ "strings"
|
|
|
|
|
+ "time"
|
|
|
|
|
+
|
|
|
|
|
+ "github.com/gin-gonic/gin"
|
|
|
|
|
+ "github.com/runningwater/gohub/pkg/logger"
|
|
|
|
|
+ "go.uber.org/zap"
|
|
|
|
|
+)
|
|
|
|
|
+
|
|
|
|
|
+// Recovery 使用 zap.Error() 来记录 panic 和 call stack
|
|
|
|
|
+func Recovery() gin.HandlerFunc {
|
|
|
|
|
+
|
|
|
|
|
+ return func(c *gin.Context) {
|
|
|
|
|
+ defer func() {
|
|
|
|
|
+ if err := recover(); err != nil {
|
|
|
|
|
+
|
|
|
|
|
+ // 获取用户的请求信息
|
|
|
|
|
+ httpRquest, _ := httputil.DumpRequest(c.Request, true)
|
|
|
|
|
+
|
|
|
|
|
+ // 链接中断,客户端中断连接为正常行为,不需要记录堆栈信息
|
|
|
|
|
+ var brokenPipe bool
|
|
|
|
|
+ if ne, ok := err.(*net.OpError); ok {
|
|
|
|
|
+ if se, ok := ne.Err.(*os.SyscallError); ok {
|
|
|
|
|
+ errStr := strings.ToLower(se.Error())
|
|
|
|
|
+ if strings.Contains(errStr, "broken pipe") || strings.Contains(errStr, "connection reset by peer") {
|
|
|
|
|
+ brokenPipe = true
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+ // 链接中断的情况
|
|
|
|
|
+ if brokenPipe {
|
|
|
|
|
+ logger.Error(c.Request.URL.Path,
|
|
|
|
|
+ zap.Time("time", time.Now()),
|
|
|
|
|
+ zap.Any("error", err),
|
|
|
|
|
+ zap.String("request", string(httpRquest)),
|
|
|
|
|
+ )
|
|
|
|
|
+ c.Error(err.(error))
|
|
|
|
|
+ c.Abort()
|
|
|
|
|
+ // 链接已断开,无法写状态
|
|
|
|
|
+ return
|
|
|
|
|
+ }
|
|
|
|
|
+ // 如果不是链接中断的情况,记录堆栈信息
|
|
|
|
|
+ logger.Error("[Recovery from panic]",
|
|
|
|
|
+ zap.Time("time", time.Now()), // 记录当前时间
|
|
|
|
|
+ zap.Any("error", err), // 记录错误信息
|
|
|
|
|
+ zap.String("request", string(httpRquest)), // 记录请求信息
|
|
|
|
|
+ zap.Stack("stack"), // 记录堆栈信息
|
|
|
|
|
+ )
|
|
|
|
|
+ c.AbortWithStatusJSON(http.StatusInternalServerError, gin.H{
|
|
|
|
|
+ "message": "服务器内部错误,请稍后再试",
|
|
|
|
|
+ })
|
|
|
|
|
+ }
|
|
|
|
|
+ }()
|
|
|
|
|
+
|
|
|
|
|
+ // 继续处理请求
|
|
|
|
|
+ c.Next()
|
|
|
|
|
+ }
|
|
|
|
|
+}
|