From 3fb78b81957fa4c762f438ed5739c90853733971 Mon Sep 17 00:00:00 2001 From: douxu Date: Tue, 10 Jun 2025 16:29:52 +0800 Subject: [PATCH] refactor(common/error): optimize error struct MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit add msg 、cause and occurred field into error struct for logging detail wrong info --- common/errcode/error.go | 156 ++++++++++++++++++++++++++++++++++++++++ 1 file changed, 156 insertions(+) create mode 100644 common/errcode/error.go diff --git a/common/errcode/error.go b/common/errcode/error.go new file mode 100644 index 0000000..7b5dd71 --- /dev/null +++ b/common/errcode/error.go @@ -0,0 +1,156 @@ +package errcode + +import ( + "encoding/json" + "fmt" + "path" + "runtime" +) + +var codes = map[int]struct{}{} + +// AppError define struct of internal error +type AppError struct { + code int + msg string + cause error + occurred string // 保存由底层错误导致AppErr发生时的位置 +} + +func (e *AppError) Error() string { + if e == nil { + return "" + } + errBytes, err := json.Marshal(e.toStructuredError()) + if err != nil { + return fmt.Sprintf("Error() is error: json marshal error: %v", err) + } + return string(errBytes) +} + +func (e *AppError) String() string { + return e.Error() +} + +// Code define func return error code +func (e *AppError) Code() int { + return e.code +} + +// Msg define func return error msg +func (e *AppError) Msg() string { + return e.msg +} + +// Cause define func return base error +func (e *AppError) Cause() error { + return e.cause +} + +// WithCause define func return top level predefined errors,where the cause field contains the underlying base error +// 在逻辑执行中出现错误, 比如dao层返回的数据库查询错误 +// 可以在领域层返回预定义的错误前附加上导致错误的基础错误。 +// 如果业务模块预定义的错误码比较详细, 可以使用这个方法, 反之错误码定义的比较笼统建议使用Wrap方法包装底层错误生成项目自定义Error +// 并将其记录到日志后再使用预定义错误码返回接口响应 +func (e *AppError) WithCause(err error) *AppError { + newErr := e.Clone() + newErr.cause = err + newErr.occurred = getAppErrOccurredInfo() + return newErr +} + +// Wrap define func packaging information and errors returned by the underlying logic +// 用于逻辑中包装底层函数返回的error 和 WithCause 一样都是为了记录错误链条 +// 该方法生成的error 用于日志记录, 返回响应请使用预定义好的error +func Wrap(msg string, err error) *AppError { + if err == nil { + return nil + } + appErr := &AppError{code: -1, msg: msg, cause: err} + appErr.occurred = getAppErrOccurredInfo() + return appErr +} + +// UnWrap define func return the error wrapped in structure +func (e *AppError) UnWrap() error { + return e.cause +} + +// Is define func return result of whether any error in err's tree matches target. implemented to support errors.Is(err, target) +func (e *AppError) Is(target error) bool { + targetErr, ok := target.(*AppError) + if !ok { + return false + } + return targetErr.Code() == e.Code() +} + +// Clone define func return a new AppError with source AppError's code, msg, cause, occurred +func (e *AppError) Clone() *AppError { + return &AppError{ + code: e.code, + msg: e.msg, + cause: e.cause, + occurred: e.occurred, + } +} + +func newError(code int, msg string) *AppError { + if code > -1 { + if _, duplicated := codes[code]; duplicated { + panic(fmt.Sprintf("预定义错误码 %d 不能重复, 请检查后更换", code)) + } + codes[code] = struct{}{} + } + + return &AppError{code: code, msg: msg} +} + +// getAppErrOccurredInfo 获取项目中调用Wrap或者WithCause方法时的程序位置, 方便排查问题 +func getAppErrOccurredInfo() string { + pc, file, line, ok := runtime.Caller(2) + if !ok { + return "" + } + file = path.Base(file) + funcName := runtime.FuncForPC(pc).Name() + triggerInfo := fmt.Sprintf("func: %s, file: %s, line: %d", funcName, file, line) + return triggerInfo +} + +// AppendMsg define func append a message to the existing error message +func (e *AppError) AppendMsg(msg string) *AppError { + n := e.Clone() + n.msg = fmt.Sprintf("%s, %s", e.msg, msg) + return n +} + +// SetMsg define func set error message into specify field +func (e *AppError) SetMsg(msg string) *AppError { + n := e.Clone() + n.msg = msg + return n +} + +type formattedErr struct { + Code int `json:"code"` + Msg string `json:"msg"` + Cause interface{} `json:"cause"` + Occurred string `json:"occurred"` +} + +// toStructuredError 在JSON Encode 前把Error进行格式化 +func (e *AppError) toStructuredError() *formattedErr { + fe := new(formattedErr) + fe.Code = e.Code() + fe.Msg = e.Msg() + fe.Occurred = e.occurred + if e.cause != nil { + if appErr, ok := e.cause.(*AppError); ok { + fe.Cause = appErr.toStructuredError() + } else { + fe.Cause = e.cause.Error() + } + } + return fe +}