diff --git a/exerr/builder.go b/exerr/builder.go index ab982fa..6404599 100644 --- a/exerr/builder.go +++ b/exerr/builder.go @@ -2,10 +2,13 @@ package exerr import ( "bytes" + "context" "encoding/json" "fmt" + "github.com/gin-gonic/gin" "github.com/rs/zerolog" "go.mongodb.org/mongo-driver/bson/primitive" + "gogs.mikescher.com/BlackForestBytes/goext/dataext" "gogs.mikescher.com/BlackForestBytes/goext/langext" "net/http" "os" @@ -17,21 +20,21 @@ import ( // // ==== USAGE ===== // -// If some method returns an error _always wrap it into an bmerror: +// If some method returns an error _always wrap it into an exerror: // value, err := do_something(..) // if err != nil { -// return nil, bmerror.Wrap(err, "do something failed").Build() +// return nil, exerror.Wrap(err, "do something failed").Build() // } // // If possible add metadata to the error (eg the id that was not found, ...), the methods are the same as in zerolog -// return nil, bmerror.Wrap(err, "do something failed").Str("someid", id).Int("count", in.Count).Build() +// return nil, exerror.Wrap(err, "do something failed").Str("someid", id).Int("count", in.Count).Build() // // You can change the errortype with `.User()` and `.System()` (User-errors are 400 and System-errors 500) // You can also manually set the statuscode with `.WithStatuscode(http.NotFound)` // You can set the type with `WithType(..)` // // New Errors (that don't wrap an existing err object) are created with New -// return nil, bmerror.New(bmerror.ErrInternal, "womethign wen horrible wrong").Build() +// return nil, exerror.New(exerror.TypeInternal, "womethign wen horrible wrong").Build() // You can eitehr use an existing ErrorType, the "catch-all" ErrInternal, or add you own ErrType in consts.go // // All errors should be handled one of the following four ways: @@ -64,37 +67,36 @@ func init() { } type Builder struct { - bmerror *bringmanError - + errorData *ExErr containsGinData bool } func Get(err error) *Builder { - return &Builder{bmerror: fromError(err)} + return &Builder{errorData: fromError(err)} } func New(t ErrorType, msg string) *Builder { - return &Builder{bmerror: newBringmanErr(CatSystem, t, msg)} + return &Builder{errorData: newExErr(CatSystem, t, msg)} } func Wrap(err error, msg string) *Builder { - return &Builder{bmerror: fromError(err).wrap(msg, CatWrap, 1)} + return &Builder{errorData: wrapExErr(fromError(err), msg, CatWrap, 1)} } // ---------------------------------------------------------------------------- func (b *Builder) WithType(t ErrorType) *Builder { - b.bmerror.Type = t + b.errorData.Type = t return b } func (b *Builder) WithStatuscode(status int) *Builder { - b.bmerror.StatusCode = status + b.errorData.StatusCode = &status return b } func (b *Builder) WithMessage(msg string) *Builder { - b.bmerror.Message = msg + b.errorData.Message = msg return b } @@ -119,7 +121,7 @@ func (b *Builder) WithMessage(msg string) *Builder { // // - Send to the error-service func (b *Builder) Err() *Builder { - b.bmerror.Severity = SevErr + b.errorData.Severity = SevErr return b } @@ -138,7 +140,7 @@ func (b *Builder) Err() *Builder { // // - Logged as Warn func (b *Builder) Warn() *Builder { - b.bmerror.Severity = SevWarn + b.errorData.Severity = SevWarn return b } @@ -157,7 +159,7 @@ func (b *Builder) Warn() *Builder { // // - -(nothing)- func (b *Builder) Info() *Builder { - b.bmerror.Severity = SevInfo + b.errorData.Severity = SevInfo return b } @@ -167,12 +169,12 @@ func (b *Builder) Info() *Builder { // // Errors with category func (b *Builder) User() *Builder { - b.bmerror.Category = CatUser + b.errorData.Category = CatUser return b } func (b *Builder) System() *Builder { - b.bmerror.Category = CatSystem + b.errorData.Category = CatSystem return b } @@ -268,7 +270,7 @@ func (b *Builder) Stack() *Builder { func (b *Builder) Errs(key string, val []error) *Builder { for i, valerr := range val { - b.addMeta(fmt.Sprintf("%v[%v]", key, i), MDTString, Get(valerr).toBMError().FormatLog(LogPrintFull)) + b.addMeta(fmt.Sprintf("%v[%v]", key, i), MDTString, Get(valerr).errorData.FormatLog(LogPrintFull)) } return b } @@ -299,7 +301,7 @@ func (b *Builder) GinReq(ctx context.Context, g *gin.Context, req *http.Request) b.Str("gin.context.reqid", ctxVal) } if req.Method != "GET" && req.Body != nil && req.Header.Get("Content-Type") == "application/json" { - if brc, ok := req.Body.(langext.BufferedReadCloser); ok { + if brc, ok := req.Body.(dataext.BufferedReadCloser); ok { if bin, err := brc.BufferedAll(); err == nil { if len(bin) < 16*1024 { var prettyJSON bytes.Buffer @@ -348,13 +350,17 @@ func formatHeader(header map[string][]string) string { // Build creates a new error, ready to pass up the stack // If the errors is not SevWarn or SevInfo it gets also logged (in short form, without stacktrace) onto stdout func (b *Builder) Build() error { - if b.bmerror.Severity == SevErr || b.bmerror.Severity == SevFatal { - b.bmerror.ShortLog(stackSkipLogger.Error()) + warnOnPkgConfigNotInitialized() + + if pkgconfig.ZeroLogErrTraces && (b.errorData.Severity == SevErr || b.errorData.Severity == SevFatal) { + b.errorData.ShortLog(stackSkipLogger.Error()) + } else if pkgconfig.ZeroLogAllTraces { + b.errorData.ShortLog(stackSkipLogger.Error()) } b.CallListener(MethodBuild) - return b.bmerror.ToGrpcError() + return b.errorData } // Output prints the error onto the gin stdout. @@ -366,12 +372,12 @@ func (b *Builder) Output(ctx context.Context, g *gin.Context) { b.GinReq(ctx, g, g.Request) } - b.bmerror.Output(ctx, g) + b.errorData.Output(ctx, g) - if b.bmerror.Severity == SevErr || b.bmerror.Severity == SevFatal { - b.bmerror.Log(stackSkipLogger.Error()) - } else if b.bmerror.Severity == SevWarn { - b.bmerror.Log(stackSkipLogger.Warn()) + if b.errorData.Severity == SevErr || b.errorData.Severity == SevFatal { + b.errorData.Log(stackSkipLogger.Error()) + } else if b.errorData.Severity == SevWarn { + b.errorData.Log(stackSkipLogger.Warn()) } b.CallListener(MethodOutput) @@ -380,24 +386,24 @@ func (b *Builder) Output(ctx context.Context, g *gin.Context) { // Print prints the error // If the error is SevErr we also send it to the error-service func (b *Builder) Print() { - if b.bmerror.Severity == SevErr || b.bmerror.Severity == SevFatal { - b.bmerror.Log(stackSkipLogger.Error()) - } else if b.bmerror.Severity == SevWarn { - b.bmerror.ShortLog(stackSkipLogger.Warn()) + if b.errorData.Severity == SevErr || b.errorData.Severity == SevFatal { + b.errorData.Log(stackSkipLogger.Error()) + } else if b.errorData.Severity == SevWarn { + b.errorData.ShortLog(stackSkipLogger.Warn()) } b.CallListener(MethodPrint) } func (b *Builder) Format(level LogPrintLevel) string { - return b.bmerror.FormatLog(level) + return b.errorData.FormatLog(level) } // Fatal prints the error and terminates the program // If the error is SevErr we also send it to the error-service func (b *Builder) Fatal() { - b.bmerror.Severity = SevFatal - b.bmerror.Log(stackSkipLogger.WithLevel(zerolog.FatalLevel)) + b.errorData.Severity = SevFatal + b.errorData.Log(stackSkipLogger.WithLevel(zerolog.FatalLevel)) b.CallListener(MethodFatal) @@ -407,10 +413,6 @@ func (b *Builder) Fatal() { // ---------------------------------------------------------------------------- func (b *Builder) addMeta(key string, mdtype metaDataType, val interface{}) *Builder { - b.bmerror.Meta.add(key, mdtype, val) + b.errorData.Meta.add(key, mdtype, val) return b } - -func (b *Builder) toBMError() BMError { - return b.bmerror.ToBMError() -} diff --git a/exerr/constructor.go b/exerr/constructor.go new file mode 100644 index 0000000..4d5c35c --- /dev/null +++ b/exerr/constructor.go @@ -0,0 +1,201 @@ +package exerr + +import ( + "encoding/json" + "fmt" + "go.mongodb.org/mongo-driver/bson/primitive" + "gogs.mikescher.com/BlackForestBytes/goext/langext" + "reflect" + "time" +) + +var reflectTypeStr = reflect.TypeOf("") + +func fromError(err error) *ExErr { + if verr, ok := err.(*ExErr); ok { + // A simple ExErr + return verr + } + + // A foreign error (eg a MongoDB exception) + return &ExErr{ + UniqueID: newID(), + Category: CatForeign, + Type: TypeInternal, + Severity: SevErr, + Timestamp: time.Time{}, + StatusCode: nil, + Message: err.Error(), + WrappedErrType: fmt.Sprintf("%T", err), + Caller: "", + OriginalError: nil, + Meta: getForeignMeta(err), + } +} + +func newExErr(cat ErrorCategory, errtype ErrorType, msg string) *ExErr { + return &ExErr{ + UniqueID: newID(), + Category: cat, + Type: errtype, + Severity: SevErr, + Timestamp: time.Now(), + StatusCode: nil, + Message: msg, + WrappedErrType: "", + Caller: callername(2), + OriginalError: nil, + Meta: make(map[string]MetaValue), + } +} + +func wrapExErr(e *ExErr, msg string, cat ErrorCategory, stacktraceskip int) *ExErr { + return &ExErr{ + UniqueID: newID(), + Category: cat, + Type: TypeWrap, + Severity: SevErr, + Timestamp: time.Now(), + StatusCode: e.StatusCode, + Message: msg, + WrappedErrType: "", + Caller: callername(1 + stacktraceskip), + OriginalError: e, + Meta: make(map[string]MetaValue), + } +} + +func getForeignMeta(err error) (mm MetaMap) { + mm = make(map[string]MetaValue) + + defer func() { + if panicerr := recover(); panicerr != nil { + New(TypePanic, "Panic while trying to get foreign meta"). + Str("source", err.Error()). + Interface("panic-object", panicerr). + Stack(). + Print() + } + }() + + rval := reflect.ValueOf(err) + if rval.Kind() == reflect.Interface || rval.Kind() == reflect.Ptr { + rval = reflect.ValueOf(err).Elem() + } + + mm.add("foreign.errortype", MDTString, rval.Type().String()) + + for k, v := range addMetaPrefix("foreign", getReflectedMetaValues(err, 8)) { + mm[k] = v + } + + return mm +} + +func getReflectedMetaValues(value interface{}, remainingDepth int) map[string]MetaValue { + + if remainingDepth <= 0 { + return map[string]MetaValue{} + } + + if langext.IsNil(value) { + return map[string]MetaValue{"": {DataType: MDTNil, Value: nil}} + } + + rval := reflect.ValueOf(value) + + if rval.Type().Kind() == reflect.Ptr { + + if rval.IsNil() { + return map[string]MetaValue{"*": {DataType: MDTNil, Value: nil}} + } + + elem := rval.Elem() + + return addMetaPrefix("*", getReflectedMetaValues(elem.Interface(), remainingDepth-1)) + } + + if !rval.CanInterface() { + return map[string]MetaValue{"": {DataType: MDTString, Value: "<>"}} + } + + raw := rval.Interface() + + switch ifraw := raw.(type) { + case time.Time: + return map[string]MetaValue{"": {DataType: MDTTime, Value: ifraw}} + case time.Duration: + return map[string]MetaValue{"": {DataType: MDTDuration, Value: ifraw}} + case int: + return map[string]MetaValue{"": {DataType: MDTInt, Value: ifraw}} + case int8: + return map[string]MetaValue{"": {DataType: MDTInt8, Value: ifraw}} + case int16: + return map[string]MetaValue{"": {DataType: MDTInt16, Value: ifraw}} + case int32: + return map[string]MetaValue{"": {DataType: MDTInt32, Value: ifraw}} + case int64: + return map[string]MetaValue{"": {DataType: MDTInt64, Value: ifraw}} + case string: + return map[string]MetaValue{"": {DataType: MDTString, Value: ifraw}} + case bool: + return map[string]MetaValue{"": {DataType: MDTBool, Value: ifraw}} + case []byte: + return map[string]MetaValue{"": {DataType: MDTBytes, Value: ifraw}} + case float32: + return map[string]MetaValue{"": {DataType: MDTFloat32, Value: ifraw}} + case float64: + return map[string]MetaValue{"": {DataType: MDTFloat64, Value: ifraw}} + case []int: + return map[string]MetaValue{"": {DataType: MDTIntArray, Value: ifraw}} + case []int32: + return map[string]MetaValue{"": {DataType: MDTInt32Array, Value: ifraw}} + case primitive.ObjectID: + return map[string]MetaValue{"": {DataType: MDTObjectID, Value: ifraw}} + case []string: + return map[string]MetaValue{"": {DataType: MDTStringArray, Value: ifraw}} + } + + if rval.Type().Kind() == reflect.Struct { + m := make(map[string]MetaValue) + for i := 0; i < rval.NumField(); i++ { + fieldtype := rval.Type().Field(i) + + fieldname := fieldtype.Name + + if fieldtype.IsExported() { + for k, v := range addMetaPrefix(fieldname, getReflectedMetaValues(rval.Field(i).Interface(), remainingDepth-1)) { + m[k] = v + } + } + } + return m + } + + if rval.Type().ConvertibleTo(reflectTypeStr) { + return map[string]MetaValue{"": {DataType: MDTString, Value: rval.Convert(reflectTypeStr).String()}} + } + + jsonval, err := json.Marshal(value) + if err != nil { + panic(err) // gets recovered later up + } + + return map[string]MetaValue{"": {DataType: MDTString, Value: string(jsonval)}} +} + +func addMetaPrefix(prefix string, m map[string]MetaValue) map[string]MetaValue { + if len(m) == 1 { + for k, v := range m { + if k == "" { + return map[string]MetaValue{prefix: v} + } + } + } + + r := make(map[string]MetaValue, len(m)) + for k, v := range m { + r[prefix+"."+k] = v + } + return r +} diff --git a/exerr/data.go b/exerr/data.go index d4996fa..8ad8b59 100644 --- a/exerr/data.go +++ b/exerr/data.go @@ -1,5 +1,10 @@ package exerr +import ( + "gogs.mikescher.com/BlackForestBytes/goext/langext" + "net/http" +) + type ErrorCategory struct{ Category string } var ( @@ -24,9 +29,22 @@ var ( var AllSeverities = []ErrorSeverity{SevTrace, SevDebug, SevInfo, SevWarn, SevErr, SevFatal} -type ErrorType struct{ Key string } +type ErrorType struct { + Key string + DefaultStatusCode *int +} var ( - TypeInternal = ErrorType{"Internal"} + TypeInternal = ErrorType{"Internal", langext.Ptr(http.StatusInternalServerError)} + TypePanic = ErrorType{"Panic", langext.Ptr(http.StatusInternalServerError)} + TypeWrap = ErrorType{"Wrap", nil} // other values come from pkgconfig ) + +type LogPrintLevel string + +const ( + LogPrintFull LogPrintLevel = "Full" + LogPrintOverview LogPrintLevel = "Overview" + LogPrintShort LogPrintLevel = "Short" +) diff --git a/exerr/errinit.go b/exerr/errinit.go index 8330d25..e55696f 100644 --- a/exerr/errinit.go +++ b/exerr/errinit.go @@ -1,23 +1,34 @@ package exerr +import ( + "fmt" + "gogs.mikescher.com/BlackForestBytes/goext/langext" +) + type ErrorPackageConfig struct { - ZeroLogTraces bool // autom print zerolog logs on CreateError - RecursiveErrors bool // errors contains their Origin-Error - Types []ErrorType // all available error-types + ZeroLogErrTraces bool // autom print zerolog logs on .Build() (for SevErr and SevFatal) + ZeroLogAllTraces bool // autom print zerolog logs on .Build() (for all Severities) + RecursiveErrors bool // errors contains their Origin-Error + ExtendedGinOutput bool // Log extended data (trace, meta, ...) to gin in err.Output() + Types []ErrorType // all available error-types } type ErrorPackageConfigInit struct { - LogTraces bool - RecursiveErrors bool - InitTypes func(_ func(_ string) ErrorType) + ZeroLogErrTraces bool + ZeroLogAllTraces bool + RecursiveErrors bool + ExtendedGinOutput bool + InitTypes func(_ func(key string, defaultStatusCode *int) ErrorType) } var initialized = false var pkgconfig = ErrorPackageConfig{ - ZeroLogTraces: true, - RecursiveErrors: true, - Types: []ErrorType{TypeInternal}, + ZeroLogErrTraces: true, + ZeroLogAllTraces: false, + RecursiveErrors: true, + ExtendedGinOutput: false, + Types: []ErrorType{TypeInternal, TypePanic, TypeWrap}, } // Init initializes the exerr packages @@ -30,8 +41,8 @@ func Init(cfg ErrorPackageConfigInit) { types := pkgconfig.Types - fnAddType := func(v string) ErrorType { - et := ErrorType{v} + fnAddType := func(key string, defaultStatusCode *int) ErrorType { + et := ErrorType{key, defaultStatusCode} types = append(types, et) return et } @@ -41,10 +52,23 @@ func Init(cfg ErrorPackageConfigInit) { } pkgconfig = ErrorPackageConfig{ - ZeroLogTraces: cfg.LogTraces, - RecursiveErrors: cfg.RecursiveErrors, - Types: types, + ZeroLogErrTraces: cfg.ZeroLogErrTraces, + ZeroLogAllTraces: cfg.ZeroLogAllTraces, + RecursiveErrors: cfg.RecursiveErrors, + ExtendedGinOutput: cfg.ExtendedGinOutput, + Types: types, } initialized = true } + +func warnOnPkgConfigNotInitialized() { + if !initialized { + fmt.Printf("\n") + fmt.Printf("%s\n", langext.StrRepeat("=", 80)) + fmt.Printf("%s\n", "[WARNING] exerr package used without initializiation") + fmt.Printf("%s\n", " call exerr.Init() in your main() function") + fmt.Printf("%s\n", langext.StrRepeat("=", 80)) + fmt.Printf("\n") + } +} diff --git a/exerr/exerr.go b/exerr/exerr.go index bc3e832..e65055f 100644 --- a/exerr/exerr.go +++ b/exerr/exerr.go @@ -1,6 +1,10 @@ package exerr import ( + "github.com/rs/xid" + "github.com/rs/zerolog" + "gogs.mikescher.com/BlackForestBytes/goext/langext" + "strings" "time" ) @@ -12,22 +16,177 @@ type ExErr struct { Severity ErrorSeverity `json:"severity"` Type ErrorType `json:"type"` - Message string `json:"message"` - Caller string `json:"caller"` + StatusCode *int `json:"statusCode"` + + Message string `json:"message"` + WrappedErrType string `json:"wrappedErrType"` + Caller string `json:"caller"` OriginalError *ExErr Meta MetaMap `json:"meta"` } -func (ee ExErr) Error() string { - +func (ee *ExErr) Error() string { + return ee.Message } -func (ee ExErr) Unwrap() error { - +func (ee *ExErr) Unwrap() error { + return ee.OriginalError } -func (ee ExErr) Is(err error) bool { - +func (ee *ExErr) Log(evt *zerolog.Event) { + evt.Msg(ee.FormatLog(LogPrintFull)) +} + +func (ee *ExErr) FormatLog(lvl LogPrintLevel) string { + if lvl == LogPrintShort { + + msg := ee.Message + if ee.OriginalError != nil && ee.OriginalError.Category == CatForeign { + msg = msg + " (" + strings.ReplaceAll(ee.OriginalError.Message, "\n", " ") + ")" + } + + if ee.Type != TypeWrap { + return "[" + ee.Type.Key + "] " + msg + } else { + return msg + } + + } else if lvl == LogPrintOverview { + + str := "[" + ee.RecursiveType().Key + "] <" + ee.UniqueID + "> " + strings.ReplaceAll(ee.RecursiveMessage(), "\n", " ") + "\n" + + indent := "" + for curr := ee; curr != nil; curr = curr.OriginalError { + indent += " " + + str += indent + str += "-> " + strmsg := strings.Trim(curr.Message, " \r\n\t") + if lbidx := strings.Index(curr.Message, "\n"); lbidx >= 0 { + strmsg = strmsg[0:lbidx] + } + strmsg = langext.StrLimit(strmsg, 61, "...") + str += strmsg + str += "\n" + + } + return str + + } else if lvl == LogPrintFull { + + str := "[" + ee.RecursiveType().Key + "] <" + ee.UniqueID + "> " + strings.ReplaceAll(ee.RecursiveMessage(), "\n", " ") + "\n" + + indent := "" + for curr := ee; curr != nil; curr = curr.OriginalError { + indent += " " + + etype := ee.Type.Key + if ee.Type == TypeWrap { + etype = "~" + } + + str += indent + str += "-> [" + str += etype + if curr.Category == CatForeign { + str += "|Foreign" + } + str += "] " + str += strings.ReplaceAll(curr.Message, "\n", " ") + if curr.Caller != "" { + str += " (@ " + str += curr.Caller + str += ")" + } + str += "\n" + + if curr.Meta.Any() { + meta := indent + " {" + curr.Meta.FormatOneLine(240) + "}" + if len(meta) < 200 { + str += meta + str += "\n" + } else { + str += curr.Meta.FormatMultiLine(indent+" ", " ", 1024) + str += "\n" + } + } + } + return str + + } else { + + return "[?[" + ee.UniqueID + "]?]" + + } +} + +func (ee *ExErr) ShortLog(evt *zerolog.Event) { + ee.Meta.Apply(evt).Msg(ee.FormatLog(LogPrintShort)) +} + +// RecursiveMessage returns the message to show +// = first error (top-down) that is not wrapping/foreign/empty +func (ee *ExErr) RecursiveMessage() string { + for curr := ee; curr != nil; curr = curr.OriginalError { + if curr.Message != "" && curr.Category != CatWrap && curr.Category != CatForeign { + return curr.Message + } + } + + // fallback to self + return ee.Message +} + +// RecursiveType returns the statuscode to use +// = first error (top-down) that is not wrapping/empty +func (ee *ExErr) RecursiveType() ErrorType { + for curr := ee; curr != nil; curr = curr.OriginalError { + if curr.Type != TypeWrap { + return curr.Type + } + + } + + // fallback to self + return ee.Type +} + +// RecursiveStatuscode returns the HTTP Statuscode to use +// = first error (top-down) that has a statuscode set +func (ee *ExErr) RecursiveStatuscode() *int { + for curr := ee; curr != nil; curr = curr.OriginalError { + if curr.StatusCode != nil { + return langext.Ptr(*curr.StatusCode) + } + } + + // fallback to + return nil +} + +// RecursiveCategory returns the ErrorCategory to use +// = first error (top-down) that has a statuscode set +func (ee *ExErr) RecursiveCategory() ErrorCategory { + for curr := ee; curr != nil; curr = curr.OriginalError { + if curr.Category != CatWrap { + return curr.Category + } + } + + // fallback to + return ee.Category +} + +func (ee *ExErr) Depth() int { + if ee.OriginalError == nil { + return 1 + } else { + return ee.OriginalError.Depth() + 1 + } +} + +func newID() string { + return xid.New().String() } diff --git a/exerr/gin.go b/exerr/gin.go new file mode 100644 index 0000000..b78882e --- /dev/null +++ b/exerr/gin.go @@ -0,0 +1,81 @@ +package exerr + +import ( + "context" + "github.com/gin-gonic/gin" + "net/http" + "time" +) + +func (ee *ExErr) toJson() gin.H { + json := gin.H{} + + if ee.UniqueID != "" { + json["id"] = ee.UniqueID + } + if ee.Category != CatWrap { + json["category"] = ee.Category + } + if ee.Type != TypeWrap { + json["type"] = ee.Type + } + if ee.StatusCode != nil { + json["statuscode"] = ee.StatusCode + } + if ee.Message != "" { + json["message"] = ee.Message + } + if ee.Caller != "" { + json["caller"] = ee.Caller + } + if ee.Severity != SevErr { + json["severity"] = ee.Severity + } + if ee.Timestamp != (time.Time{}) { + json["time"] = ee.Timestamp.Format(time.RFC3339) + } + if ee.OriginalError != nil { + json["original"] = ee.OriginalError.toJson() + } + + return json +} + +func (ee *ExErr) Output(ctx context.Context, g *gin.Context) { + var statuscode = http.StatusInternalServerError + + var baseCat = ee.RecursiveCategory() + var baseType = ee.RecursiveType() + var baseStatuscode = ee.RecursiveStatuscode() + + if baseCat == CatUser { + statuscode = http.StatusBadRequest + } else if baseCat == CatSystem { + statuscode = http.StatusInternalServerError + } + + if baseStatuscode != nil { + statuscode = *ee.StatusCode + } else if baseType.DefaultStatusCode != nil { + statuscode = *baseType.DefaultStatusCode + } + + warnOnPkgConfigNotInitialized() + + if pkgconfig.ExtendedGinOutput { + g.JSON(statuscode, gin.H{ + "errorid": ee.UniqueID, + "error": ee.RecursiveMessage(), + "errorcategory": ee.RecursiveCategory(), + "errortype": ee.RecursiveType(), + "errodata": ee.toJson(), + }) + } else { + g.JSON(statuscode, gin.H{ + "errorid": ee.UniqueID, + "error": ee.RecursiveMessage(), + "errorcategory": ee.RecursiveCategory(), + "errortype": ee.RecursiveType(), + }) + } +} diff --git a/exerr/helper.go b/exerr/helper.go new file mode 100644 index 0000000..4291159 --- /dev/null +++ b/exerr/helper.go @@ -0,0 +1,86 @@ +package exerr + +import "fmt" + +// IsType test if the supplied error is of the specified ErrorType. +func IsType(err error, errType ErrorType) bool { + if err == nil { + return false + } + + bmerr := fromError(err) + for bmerr != nil { + if bmerr.Type == errType { + return true + } + bmerr = bmerr.OriginalError + } + + return false +} + +// IsFrom test if the supplied error stems originally from original +func IsFrom(e error, original error) bool { + if e == nil { + return false + } + if e == original { + return true + } + + bmerr := fromError(e) + for bmerr == nil { + return false + } + + for curr := bmerr; curr != nil; curr = curr.OriginalError { + if curr.Category == CatForeign && curr.Message == original.Error() && curr.WrappedErrType == fmt.Sprintf("%T", original) { + return true + } + } + + return false +} + +// HasSourceMessage tests if the supplied error stems originally from an error with the message msg +func HasSourceMessage(e error, msg string) bool { + if e == nil { + return false + } + + bmerr := fromError(e) + for bmerr == nil { + return false + } + + for curr := bmerr; curr != nil; curr = curr.OriginalError { + if curr.OriginalError == nil && curr.Message == msg { + return true + } + } + + return false +} + +func MessageMatch(e error, matcher func(string) bool) bool { + if e == nil { + return false + } + + if matcher(e.Error()) { + return true + } + + bmerr := fromError(e) + for bmerr == nil { + return false + } + + for curr := bmerr; curr != nil; curr = curr.OriginalError { + if matcher(curr.Message) { + return true + } + } + + return false +} diff --git a/exerr/listener.go b/exerr/listener.go index b996be4..e890977 100644 --- a/exerr/listener.go +++ b/exerr/listener.go @@ -13,7 +13,7 @@ const ( MethodFatal Method = "FATAL" ) -type Listener = func(method Method, v ExErr) +type Listener = func(method Method, v *ExErr) var listenerLock = sync.Mutex{} var listener = make([]Listener, 0) @@ -26,7 +26,7 @@ func RegisterListener(l Listener) { } func (b *Builder) CallListener(m Method) { - valErr := b.toBMError() + valErr := b.errorData listenerLock.Lock() defer listenerLock.Unlock() diff --git a/exerr/meta.go b/exerr/meta.go index 2eee44c..4bd1393 100644 --- a/exerr/meta.go +++ b/exerr/meta.go @@ -6,7 +6,6 @@ import ( "errors" "fmt" "github.com/rs/zerolog" - "github.com/rs/zerolog/log" "go.mongodb.org/mongo-driver/bson" "go.mongodb.org/mongo-driver/bson/primitive" "gogs.mikescher.com/BlackForestBytes/goext/langext" @@ -15,6 +14,10 @@ import ( "time" ) +// This is a buffed up map[string]any +// we also save type information of the map-values +// which allows us to deserialize them back into te correct types later + type MetaMap map[string]MetaValue type metaDataType string @@ -350,11 +353,7 @@ func (v *MetaValue) Deserialize(value string, datatype metaDataType) error { v.DataType = datatype return nil } else { - r, err := valueFromProto(value[1:], MDTString) - if err != nil { - return err - } - v.Value = langext.Ptr(r.Value.(string)) + v.Value = langext.Ptr(value[1:]) v.DataType = datatype return nil } @@ -586,51 +585,6 @@ func (v MetaValue) ValueString() string { return "(err)" } -func valueFromProto(value string, datatype metaDataType) (MetaValue, error) { - obj := MetaValue{} - err := obj.Deserialize(value, datatype) - if err != nil { - return MetaValue{}, err - } - return obj, nil -} - -func metaFromProto(proto []*spbmodels.CustomError_MetaValue) MetaMap { - r := make(MetaMap) - - for _, v := range proto { - mval, err := valueFromProto(v.Value, metaDataType(v.Type)) - if err != nil { - log.Warn().Err(err).Msg("metaFromProto failed for " + v.Key) - continue - } - r[v.Key] = mval - } - - return r -} - -func (mm MetaMap) ToProto() []*spbmodels.CustomError_MetaValue { - if mm == nil { - return make([]*spbmodels.CustomError_MetaValue, 0) - } - r := make([]*spbmodels.CustomError_MetaValue, 0, len(mm)) - for k, v := range mm { - strval, err := v.SerializeValue() - if err != nil { - log.Warn().Err(err).Msg("MetaMap.ToProto failed for " + k) - continue - } - - r = append(r, &spbmodels.CustomError_MetaValue{ - Key: k, - Type: string(v.DataType), - Value: strval, - }) - } - return r -} - func (mm MetaMap) FormatOneLine(singleMaxLen int) string { r := "" diff --git a/exerr/stacktrace.go b/exerr/stacktrace.go new file mode 100644 index 0000000..d930e26 --- /dev/null +++ b/exerr/stacktrace.go @@ -0,0 +1,14 @@ +package exerr + +import ( + "fmt" + "runtime" +) + +func callername(skip int) string { + pc := make([]uintptr, 15) + n := runtime.Callers(skip+2, pc) + frames := runtime.CallersFrames(pc[:n]) + frame, _ := frames.Next() + return fmt.Sprintf("%s:%d %s", frame.File, frame.Line, frame.Function) +} diff --git a/exerr/wrapper.go b/exerr/typeWrapper.go similarity index 100% rename from exerr/wrapper.go rename to exerr/typeWrapper.go diff --git a/go.mod b/go.mod index c9d14c6..50e88ca 100644 --- a/go.mod +++ b/go.mod @@ -5,6 +5,7 @@ go 1.19 require ( github.com/gin-gonic/gin v1.9.1 github.com/jmoiron/sqlx v1.3.5 + github.com/rs/xid v1.5.0 github.com/rs/zerolog v1.29.1 go.mongodb.org/mongo-driver v1.12.0 golang.org/x/crypto v0.11.0 diff --git a/go.sum b/go.sum index b52aff2..fe72345 100644 --- a/go.sum +++ b/go.sum @@ -79,6 +79,8 @@ github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/rs/xid v1.4.0/go.mod h1:trrq9SKmegXys3aeAKXMUTdJsYXVwGY3RLcfgqegfbg= +github.com/rs/xid v1.5.0 h1:mKX4bl4iPYJtEIxp6CYiUuLQ/8DYMoz0PUdtGgMFRVc= +github.com/rs/xid v1.5.0/go.mod h1:trrq9SKmegXys3aeAKXMUTdJsYXVwGY3RLcfgqegfbg= github.com/rs/zerolog v1.29.0 h1:Zes4hju04hjbvkVkOhdl2HpZa+0PmVwigmo8XoORE5w= github.com/rs/zerolog v1.29.0/go.mod h1:NILgTygv/Uej1ra5XxGf82ZFSLk58MFGAUS2o6usyD0= github.com/rs/zerolog v1.29.1 h1:cO+d60CHkknCbvzEWxP0S9K6KqyTjrCNUy1LdQLCGPc= diff --git a/goextVersion.go b/goextVersion.go index 032bd6d..088f863 100644 --- a/goextVersion.go +++ b/goextVersion.go @@ -1,5 +1,5 @@ package goext -const GoextVersion = "0.0.187" +const GoextVersion = "0.0.188" -const GoextVersionTimestamp = "2023-07-24T09:16:37+0200" +const GoextVersionTimestamp = "2023-07-24T10:42:39+0200"