package middleware import ( "allapp/logx" "allapp/utils/response" "context" "fmt" "go.uber.org/zap" "runtime/debug" "strings" "github.com/cloudwego/hertz/pkg/app" "github.com/pkg/errors" ) // ErrorInfo 封装错误信息和调用栈 type ErrorInfo struct { Message string // 错误信息 FuncName string // 出错函数 File string // 文件路径 Line string // 行号 Stack string // 完整堆栈信息 } // Error 中间件 func Error() app.HandlerFunc { return func(ctx context.Context, c *app.RequestContext) { c.Next(ctx) if e := c.Errors.Last(); e != nil && e.Err != nil { info := ParseError(e.Err) logx.CtxError(ctx, "系统异常", zap.String("Message", info.Message), zap.String("File", info.File), zap.String("Line", info.Line), zap.String("Func", info.FuncName), zap.String("Stack", info.Stack), ) // 返回给前端 response.Fail(c).Message("系统异常,请联系管理员!").Send() } } } // ParseError 解析 error 为 ErrorInfo func ParseError(err error) *ErrorInfo { if err == nil { return nil } info := &ErrorInfo{ Message: err.Error(), Stack: string(debug.Stack()), // 默认 runtime stack } // 尝试解析 pkg/errors stack type stackTracer interface { StackTrace() errors.StackTrace } if errWithStack, ok := err.(stackTracer); ok { st := errWithStack.StackTrace() var funcName, fileLine string for _, f := range st { line := fmt.Sprintf("%+v", f) // 取 biz/ 目录下第一个函数作为业务函数 if strings.Contains(line, "/biz/") { parts := strings.Split(line, "\n") if len(parts) >= 2 { funcName = strings.TrimSpace(parts[0]) fileLine = strings.TrimSpace(parts[1]) } else { funcName = strings.TrimSpace(line) fileLine = strings.TrimSpace(line) } break } } info.FuncName = funcName // 分离文件和行号 if idx := strings.LastIndex(fileLine, ":"); idx > 0 { info.File = fileLine[:idx] info.Line = fileLine[idx+1:] } else { info.File = fileLine info.Line = "" } // 完整堆栈 stackStr := "" for _, f := range st { stackStr += fmt.Sprintf("%+v\n", f) } info.Stack = stackStr } return info }