From 17383894a712b3b9f5ecff86d344f1b72f0c29b7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mike=20Schw=C3=B6rer?= Date: Wed, 8 Feb 2023 18:45:31 +0100 Subject: [PATCH] copy bmerr stuff from bm --- exerr/builder.go | 1 + exerr/defaults.go | 1 + exerr/errinit.go | 17 ++ exerr/exerr.go | 32 ++ exerr/foreign.go | 146 +++++++++ exerr/listener.go | 35 +++ exerr/meta.go | 698 ++++++++++++++++++++++++++++++++++++++++++++ exerr/stacktrace.go | 14 + exerr/wrapper.go | 133 +++++++++ 9 files changed, 1077 insertions(+) create mode 100644 exerr/builder.go create mode 100644 exerr/defaults.go create mode 100644 exerr/errinit.go create mode 100644 exerr/exerr.go create mode 100644 exerr/foreign.go create mode 100644 exerr/listener.go create mode 100644 exerr/meta.go create mode 100644 exerr/stacktrace.go create mode 100644 exerr/wrapper.go diff --git a/exerr/builder.go b/exerr/builder.go new file mode 100644 index 0000000..651b2e0 --- /dev/null +++ b/exerr/builder.go @@ -0,0 +1 @@ +package exerr diff --git a/exerr/defaults.go b/exerr/defaults.go new file mode 100644 index 0000000..651b2e0 --- /dev/null +++ b/exerr/defaults.go @@ -0,0 +1 @@ +package exerr diff --git a/exerr/errinit.go b/exerr/errinit.go new file mode 100644 index 0000000..fe7c38f --- /dev/null +++ b/exerr/errinit.go @@ -0,0 +1,17 @@ +package exerr + +type ErrorPackageConfig struct { + LogTraces bool + RecursiveErrors bool +} + +var pkgconfig = ErrorPackageConfig{ + LogTraces: true, +} + +// Init initializes the exerr packages +// Must be called at the program start, before (!) any errors +// Is not thread-safe +func Init(cfg ErrorPackageConfig) { + pkgconfig = cfg +} diff --git a/exerr/exerr.go b/exerr/exerr.go new file mode 100644 index 0000000..aac4581 --- /dev/null +++ b/exerr/exerr.go @@ -0,0 +1,32 @@ +package exerr + +import ( + "time" +) + +type ExErr struct { + UniqueID string `json:"uniqueID"` + + Timestamp time.Time `json:"timestamp"` + Category ErrorCategory `json:"category"` + Severity ErrorSeverity `json:"severity"` + Type ErrorType `json:"type"` + Source ErrorSource `json:"source"` + + Message string `json:"message"` + Caller string `json:"caller"` + + Meta MetaMap `json:"meta"` +} + +func (ee ExErr) Error() string { + +} + +func (ee ExErr) Unwrap() error { + +} + +func (ee ExErr) Is(err error) bool { + +} diff --git a/exerr/foreign.go b/exerr/foreign.go new file mode 100644 index 0000000..a299841 --- /dev/null +++ b/exerr/foreign.go @@ -0,0 +1,146 @@ +package exerr + +import ( + "bringman.de/common/shared/langext" + "encoding/json" + "go.mongodb.org/mongo-driver/bson/primitive" + "reflect" + "time" +) + +var reflectTypeStr = reflect.TypeOf("") + +func getForeignMeta(err error) (mm MetaMap) { + mm = make(map[string]MetaValue) + + defer func() { + if panicerr := recover(); panicerr != nil { + New(ErrPanic, "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/listener.go b/exerr/listener.go new file mode 100644 index 0000000..67a4ae6 --- /dev/null +++ b/exerr/listener.go @@ -0,0 +1,35 @@ +package exerr + +import "sync" + +type Method string + +const ( + MethodOutput Method = "OUTPUT" + MethodPrint Method = "PRINT" + MethodBuild Method = "BUILD" + MethodFatal Method = "FATAL" +) + +type Listener = func(method Method, v BMError) + +var listenerLock = sync.Mutex{} +var listener = make([]Listener, 0) + +func RegisterListener(l Listener) { + listenerLock.Lock() + defer listenerLock.Unlock() + + listener = append(listener, l) +} + +func (b *Builder) CallListener(m Method) { + valErr := b.toBMError() + + listenerLock.Lock() + defer listenerLock.Unlock() + + for _, v := range listener { + v(m, valErr) + } +} diff --git a/exerr/meta.go b/exerr/meta.go new file mode 100644 index 0000000..e2f2f4e --- /dev/null +++ b/exerr/meta.go @@ -0,0 +1,698 @@ +package exerr + +import ( + "bringman.de/common/shared/langext" + spbmodels "bringman.de/proto/common/models" + "encoding/hex" + "encoding/json" + "errors" + "fmt" + "github.com/rs/zerolog" + "github.com/rs/zerolog/log" + "go.mongodb.org/mongo-driver/bson" + "go.mongodb.org/mongo-driver/bson/primitive" + "strconv" + "strings" + "time" +) + +type MetaMap map[string]MetaValue + +type metaDataType string + +const ( + MDTString metaDataType = "String" + MDTStringPtr metaDataType = "StringPtr" + MDTInt metaDataType = "Int" + MDTInt8 metaDataType = "Int8" + MDTInt16 metaDataType = "Int16" + MDTInt32 metaDataType = "Int32" + MDTInt64 metaDataType = "Int64" + MDTFloat32 metaDataType = "Float32" + MDTFloat64 metaDataType = "Float64" + MDTBool metaDataType = "Bool" + MDTBytes metaDataType = "Bytes" + MDTObjectID metaDataType = "ObjectID" + MDTTime metaDataType = "Time" + MDTDuration metaDataType = "Duration" + MDTStringArray metaDataType = "StringArr" + MDTIntArray metaDataType = "IntArr" + MDTInt32Array metaDataType = "Int32Arr" + MDTID metaDataType = "ID" + MDTAny metaDataType = "Interface" + MDTNil metaDataType = "Nil" +) + +type MetaValue struct { + DataType metaDataType `json:"dataType"` + Value interface{} `json:"value"` +} + +type metaValueSerialization struct { + DataType metaDataType `bson:"dataType"` + Value string `bson:"value"` + Raw interface{} `bson:"raw"` +} + +func (v MetaValue) SerializeValue() (string, error) { + switch v.DataType { + case MDTString: + return v.Value.(string), nil + case MDTID: + return v.Value.(IDWrap).Serialize(), nil + case MDTAny: + return v.Value.(AnyWrap).Serialize(), nil + case MDTStringPtr: + if langext.IsNil(v.Value) { + return "#", nil + } + r := v.Value.(*string) + if r != nil { + return "*" + *r, nil + } else { + return "#", nil + } + case MDTInt: + return strconv.Itoa(v.Value.(int)), nil + case MDTInt8: + return strconv.FormatInt(int64(v.Value.(int8)), 10), nil + case MDTInt16: + return strconv.FormatInt(int64(v.Value.(int16)), 10), nil + case MDTInt32: + return strconv.FormatInt(int64(v.Value.(int32)), 10), nil + case MDTInt64: + return strconv.FormatInt(v.Value.(int64), 10), nil + case MDTFloat32: + return strconv.FormatFloat(float64(v.Value.(float32)), 'X', -1, 32), nil + case MDTFloat64: + return strconv.FormatFloat(v.Value.(float64), 'X', -1, 64), nil + case MDTBool: + if v.Value.(bool) { + return "true", nil + } else { + return "false", nil + } + case MDTBytes: + return hex.EncodeToString(v.Value.([]byte)), nil + case MDTObjectID: + return v.Value.(primitive.ObjectID).Hex(), nil + case MDTTime: + return strconv.FormatInt(v.Value.(time.Time).Unix(), 10) + "|" + strconv.FormatInt(int64(v.Value.(time.Time).Nanosecond()), 10), nil + case MDTDuration: + return v.Value.(time.Duration).String(), nil + case MDTStringArray: + if langext.IsNil(v.Value) { + return "#", nil + } + r, err := json.Marshal(v.Value.([]string)) + if err != nil { + return "", err + } + return string(r), nil + case MDTIntArray: + if langext.IsNil(v.Value) { + return "#", nil + } + r, err := json.Marshal(v.Value.([]int)) + if err != nil { + return "", err + } + return string(r), nil + case MDTInt32Array: + if langext.IsNil(v.Value) { + return "#", nil + } + r, err := json.Marshal(v.Value.([]int32)) + if err != nil { + return "", err + } + return string(r), nil + case MDTNil: + return "", nil + } + return "", errors.New("Unknown type: " + string(v.DataType)) +} + +func (v MetaValue) ShortString(lim int) string { + switch v.DataType { + case MDTString: + r := strings.ReplaceAll(v.Value.(string), "\r", "") + r = strings.ReplaceAll(r, "\n", "\\n") + r = strings.ReplaceAll(r, "\t", "\\t") + return langext.StrLimit(r, lim, "...") + case MDTID: + return v.Value.(IDWrap).String() + case MDTAny: + return v.Value.(AnyWrap).String() + case MDTStringPtr: + if langext.IsNil(v.Value) { + return "<>" + } + r := langext.CoalesceString(v.Value.(*string), "<>") + r = strings.ReplaceAll(r, "\r", "") + r = strings.ReplaceAll(r, "\n", "\\n") + r = strings.ReplaceAll(r, "\t", "\\t") + return langext.StrLimit(r, lim, "...") + case MDTInt: + return strconv.Itoa(v.Value.(int)) + case MDTInt8: + return strconv.FormatInt(int64(v.Value.(int8)), 10) + case MDTInt16: + return strconv.FormatInt(int64(v.Value.(int16)), 10) + case MDTInt32: + return strconv.FormatInt(int64(v.Value.(int32)), 10) + case MDTInt64: + return strconv.FormatInt(v.Value.(int64), 10) + case MDTFloat32: + return strconv.FormatFloat(float64(v.Value.(float32)), 'g', 4, 32) + case MDTFloat64: + return strconv.FormatFloat(v.Value.(float64), 'g', 4, 64) + case MDTBool: + return fmt.Sprintf("%v", v.Value.(bool)) + case MDTBytes: + return langext.StrLimit(hex.EncodeToString(v.Value.([]byte)), lim, "...") + case MDTObjectID: + return v.Value.(primitive.ObjectID).Hex() + case MDTTime: + return v.Value.(time.Time).Format(time.RFC3339) + case MDTDuration: + return v.Value.(time.Duration).String() + case MDTStringArray: + if langext.IsNil(v.Value) { + return "<>" + } + r, err := json.Marshal(v.Value.([]string)) + if err != nil { + return "(err)" + } + return langext.StrLimit(string(r), lim, "...") + case MDTIntArray: + if langext.IsNil(v.Value) { + return "<>" + } + r, err := json.Marshal(v.Value.([]int)) + if err != nil { + return "(err)" + } + return langext.StrLimit(string(r), lim, "...") + case MDTInt32Array: + if langext.IsNil(v.Value) { + return "<>" + } + r, err := json.Marshal(v.Value.([]int32)) + if err != nil { + return "(err)" + } + return langext.StrLimit(string(r), lim, "...") + case MDTNil: + return "<>" + } + return "(err)" +} + +func (v MetaValue) Apply(key string, evt *zerolog.Event) *zerolog.Event { + switch v.DataType { + case MDTString: + return evt.Str(key, v.Value.(string)) + case MDTID: + return evt.Str(key, v.Value.(IDWrap).Value) + case MDTAny: + if v.Value.(AnyWrap).IsError { + return evt.Str(key, "(err)") + } else { + return evt.Str(key, v.Value.(AnyWrap).Json) + } + case MDTStringPtr: + if langext.IsNil(v.Value) { + return evt.Str(key, "<>") + } + return evt.Str(key, langext.CoalesceString(v.Value.(*string), "<>")) + case MDTInt: + return evt.Int(key, v.Value.(int)) + case MDTInt8: + return evt.Int8(key, v.Value.(int8)) + case MDTInt16: + return evt.Int16(key, v.Value.(int16)) + case MDTInt32: + return evt.Int32(key, v.Value.(int32)) + case MDTInt64: + return evt.Int64(key, v.Value.(int64)) + case MDTFloat32: + return evt.Float32(key, v.Value.(float32)) + case MDTFloat64: + return evt.Float64(key, v.Value.(float64)) + case MDTBool: + return evt.Bool(key, v.Value.(bool)) + case MDTBytes: + return evt.Bytes(key, v.Value.([]byte)) + case MDTObjectID: + return evt.Str(key, v.Value.(primitive.ObjectID).Hex()) + case MDTTime: + return evt.Time(key, v.Value.(time.Time)) + case MDTDuration: + return evt.Dur(key, v.Value.(time.Duration)) + case MDTStringArray: + if langext.IsNil(v.Value) { + return evt.Strs(key, nil) + } + return evt.Strs(key, v.Value.([]string)) + case MDTIntArray: + if langext.IsNil(v.Value) { + return evt.Ints(key, nil) + } + return evt.Ints(key, v.Value.([]int)) + case MDTInt32Array: + if langext.IsNil(v.Value) { + return evt.Ints32(key, nil) + } + return evt.Ints32(key, v.Value.([]int32)) + case MDTNil: + return evt.Str(key, "<>") + } + return evt.Str(key, "(err)") +} + +func (v MetaValue) MarshalJSON() ([]byte, error) { + str, err := v.SerializeValue() + if err != nil { + return nil, err + } + return json.Marshal(string(v.DataType) + ":" + str) +} + +func (v *MetaValue) UnmarshalJSON(data []byte) error { + var str = "" + err := json.Unmarshal(data, &str) + if err != nil { + return err + } + + split := strings.SplitN(str, ":", 2) + if len(split) != 2 { + return errors.New("failed to decode MetaValue: '" + str + "'") + } + + return v.Deserialize(split[1], metaDataType(split[0])) +} + +func (v MetaValue) MarshalBSON() ([]byte, error) { + serval, err := v.SerializeValue() + if err != nil { + return nil, Wrap(err, "failed to bson-marshal MetaValue (serialize)").Build() + } + + // this is an kinda ugly hack - but serialization to mongodb and back can loose the correct type information.... + bin, err := bson.Marshal(metaValueSerialization{ + DataType: v.DataType, + Value: serval, + Raw: v.Value, + }) + if err != nil { + return nil, Wrap(err, "failed to bson-marshal MetaValue (marshal)").Build() + } + + return bin, nil +} + +func (v *MetaValue) UnmarshalBSON(bytes []byte) error { + var serval metaValueSerialization + err := bson.Unmarshal(bytes, &serval) + if err != nil { + return Wrap(err, "failed to bson-unmarshal MetaValue (unmarshal)").Build() + } + + err = v.Deserialize(serval.Value, serval.DataType) + if err != nil { + return Wrap(err, "failed to deserialize MetaValue from bson").Str("raw", serval.Value).Build() + } + + return nil +} + +func (v *MetaValue) Deserialize(value string, datatype metaDataType) error { + switch datatype { + case MDTString: + v.Value = value + v.DataType = datatype + return nil + case MDTID: + v.Value = deserializeIDWrap(value) + v.DataType = datatype + return nil + case MDTAny: + v.Value = deserializeAnyWrap(value) + v.DataType = datatype + return nil + case MDTStringPtr: + if len(value) <= 0 || (value[0] != '*' && value[0] != '#') { + return errors.New("Invalid StringPtr: " + value) + } else if value == "#" { + v.Value = nil + 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.DataType = datatype + return nil + } + case MDTInt: + pv, err := strconv.ParseInt(value, 10, 0) + if err != nil { + return err + } + v.Value = int(pv) + v.DataType = datatype + return nil + case MDTInt8: + pv, err := strconv.ParseInt(value, 10, 8) + if err != nil { + return err + } + v.Value = int8(pv) + v.DataType = datatype + return nil + case MDTInt16: + pv, err := strconv.ParseInt(value, 10, 16) + if err != nil { + return err + } + v.Value = int16(pv) + v.DataType = datatype + return nil + case MDTInt32: + pv, err := strconv.ParseInt(value, 10, 32) + if err != nil { + return err + } + v.Value = int32(pv) + v.DataType = datatype + return nil + case MDTInt64: + pv, err := strconv.ParseInt(value, 10, 64) + if err != nil { + return err + } + v.Value = pv + v.DataType = datatype + return nil + case MDTFloat32: + pv, err := strconv.ParseFloat(value, 64) + if err != nil { + return err + } + v.Value = float32(pv) + v.DataType = datatype + return nil + case MDTFloat64: + pv, err := strconv.ParseFloat(value, 64) + if err != nil { + return err + } + v.Value = pv + v.DataType = datatype + return nil + case MDTBool: + if value == "true" { + v.Value = true + v.DataType = datatype + return nil + } + if value == "false" { + v.Value = false + v.DataType = datatype + return nil + } + return errors.New("invalid bool value: " + value) + case MDTBytes: + r, err := hex.DecodeString(value) + if err != nil { + return err + } + v.Value = r + v.DataType = datatype + return nil + case MDTObjectID: + r, err := primitive.ObjectIDFromHex(value) + if err != nil { + return err + } + v.Value = r + v.DataType = datatype + return nil + case MDTTime: + ps := strings.Split(value, "|") + if len(ps) != 2 { + return errors.New("invalid time.time: " + value) + } + p1, err := strconv.ParseInt(ps[0], 10, 64) + if err != nil { + return err + } + p2, err := strconv.ParseInt(ps[1], 10, 32) + if err != nil { + return err + } + v.Value = time.Unix(p1, p2) + v.DataType = datatype + return nil + case MDTDuration: + r, err := time.ParseDuration(value) + if err != nil { + return err + } + v.Value = r + v.DataType = datatype + return nil + case MDTStringArray: + if value == "#" { + v.Value = nil + v.DataType = datatype + return nil + } + pj := make([]string, 0) + err := json.Unmarshal([]byte(value), &pj) + if err != nil { + return err + } + v.Value = pj + v.DataType = datatype + return nil + case MDTIntArray: + if value == "#" { + v.Value = nil + v.DataType = datatype + return nil + } + pj := make([]int, 0) + err := json.Unmarshal([]byte(value), &pj) + if err != nil { + return err + } + v.Value = pj + v.DataType = datatype + return nil + case MDTInt32Array: + if value == "#" { + v.Value = nil + v.DataType = datatype + return nil + } + pj := make([]int32, 0) + err := json.Unmarshal([]byte(value), &pj) + if err != nil { + return err + } + v.Value = pj + v.DataType = datatype + return nil + case MDTNil: + v.Value = nil + v.DataType = datatype + return nil + } + return errors.New("Unknown type: " + string(datatype)) +} + +func (v MetaValue) ValueString() string { + switch v.DataType { + case MDTString: + return v.Value.(string) + case MDTID: + return v.Value.(IDWrap).String() + case MDTAny: + return v.Value.(AnyWrap).String() + case MDTStringPtr: + if langext.IsNil(v.Value) { + return "<>" + } + return langext.CoalesceString(v.Value.(*string), "<>") + case MDTInt: + return strconv.Itoa(v.Value.(int)) + case MDTInt8: + return strconv.FormatInt(int64(v.Value.(int8)), 10) + case MDTInt16: + return strconv.FormatInt(int64(v.Value.(int16)), 10) + case MDTInt32: + return strconv.FormatInt(int64(v.Value.(int32)), 10) + case MDTInt64: + return strconv.FormatInt(v.Value.(int64), 10) + case MDTFloat32: + return strconv.FormatFloat(float64(v.Value.(float32)), 'g', 4, 32) + case MDTFloat64: + return strconv.FormatFloat(v.Value.(float64), 'g', 4, 64) + case MDTBool: + return fmt.Sprintf("%v", v.Value.(bool)) + case MDTBytes: + return hex.EncodeToString(v.Value.([]byte)) + case MDTObjectID: + return v.Value.(primitive.ObjectID).Hex() + case MDTTime: + return v.Value.(time.Time).Format(time.RFC3339Nano) + case MDTDuration: + return v.Value.(time.Duration).String() + case MDTStringArray: + if langext.IsNil(v.Value) { + return "<>" + } + r, err := json.MarshalIndent(v.Value.([]string), "", " ") + if err != nil { + return "(err)" + } + return string(r) + case MDTIntArray: + if langext.IsNil(v.Value) { + return "<>" + } + r, err := json.MarshalIndent(v.Value.([]int), "", " ") + if err != nil { + return "(err)" + } + return string(r) + case MDTInt32Array: + if langext.IsNil(v.Value) { + return "<>" + } + r, err := json.MarshalIndent(v.Value.([]int32), "", " ") + if err != nil { + return "(err)" + } + return string(r) + case MDTNil: + return "<>" + } + 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 := "" + + i := 0 + for key, val := range mm { + if i > 0 { + r += ", " + } + + r += "\"" + key + "\"" + r += ": " + r += "\"" + val.ShortString(singleMaxLen) + "\"" + + i++ + } + + return r +} + +func (mm MetaMap) FormatMultiLine(indentFront string, indentKeys string, maxLenValue int) string { + r := "" + + r += indentFront + "{" + "\n" + for key, val := range mm { + if key == "gin.body" { + continue + } + + r += indentFront + r += indentKeys + r += "\"" + key + "\"" + r += ": " + r += "\"" + val.ShortString(maxLenValue) + "\"" + r += ",\n" + } + r += indentFront + "}" + + return r +} + +func (mm MetaMap) Any() bool { + return len(mm) > 0 +} + +func (mm MetaMap) Apply(evt *zerolog.Event) *zerolog.Event { + for key, val := range mm { + evt = val.Apply(key, evt) + } + return evt +} + +func (mm MetaMap) add(key string, mdtype metaDataType, val interface{}) { + if _, ok := mm[key]; !ok { + mm[key] = MetaValue{DataType: mdtype, Value: val} + return + } + for i := 2; ; i++ { + realkey := key + "-" + strconv.Itoa(i) + if _, ok := mm[realkey]; !ok { + mm[realkey] = MetaValue{DataType: mdtype, Value: val} + return + } + } +} 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/wrapper.go new file mode 100644 index 0000000..55dd1bb --- /dev/null +++ b/exerr/wrapper.go @@ -0,0 +1,133 @@ +package exerr + +import ( + "bringman.de/common/shared/langext" + "encoding/json" + "fmt" + "github.com/rs/zerolog/log" + "strings" +) + +// +// These are wrapper objects, because for some metadata-types we need to serialize a bit more complex data +// (eg thy actual type for ID objects, or the json representation for any types) +// + +type IDWrap struct { + Type string + Value string + IsNil bool +} + +func newIDWrap(val fmt.Stringer) IDWrap { + t := fmt.Sprintf("%T", val) + arr := strings.Split(t, ".") + if len(arr) > 0 { + t = arr[len(arr)-1] + } + + if langext.IsNil(val) { + return IDWrap{Type: t, Value: "", IsNil: true} + } + + v := val.String() + return IDWrap{Type: t, Value: v, IsNil: false} +} + +func (w IDWrap) Serialize() string { + if w.IsNil { + return "!nil" + ":" + w.Type + } + return w.Type + ":" + w.Value +} + +func (w IDWrap) String() string { + if w.IsNil { + return w.Type + "<>" + } + return w.Type + "(" + w.Value + ")" +} + +func deserializeIDWrap(v string) IDWrap { + r := strings.SplitN(v, ":", 2) + + if len(r) == 2 && r[0] == "!nil" { + return IDWrap{Type: r[1], Value: v, IsNil: true} + } + + if len(r) == 0 { + return IDWrap{} + } else if len(r) == 1 { + return IDWrap{Type: "", Value: v, IsNil: false} + } else { + return IDWrap{Type: r[0], Value: r[1], IsNil: false} + } +} + +type AnyWrap struct { + Type string + Json string + IsError bool + IsNil bool +} + +func newAnyWrap(val any) (result AnyWrap) { + result = AnyWrap{Type: "", Json: "", IsError: true, IsNil: false} // ensure a return in case of recover() + + defer func() { + if err := recover(); err != nil { + // send error should never crash our program + log.Error().Interface("err", err).Msg("Panic while trying to marshal anywrap ( bmerror.Interface )") + } + }() + + t := fmt.Sprintf("%T", val) + + if langext.IsNil(val) { + return AnyWrap{Type: t, Json: "", IsError: false, IsNil: true} + } + + j, err := json.Marshal(val) + if err == nil { + return AnyWrap{Type: t, Json: string(j), IsError: false, IsNil: false} + } else { + return AnyWrap{Type: t, Json: "", IsError: true, IsNil: false} + } +} + +func (w AnyWrap) Serialize() string { + if w.IsError { + return "ERR" + ":" + w.Type + ":" + w.Json + } else if w.IsNil { + return "NIL" + ":" + w.Type + ":" + w.Json + } else { + return "OK" + ":" + w.Type + ":" + w.Json + } +} + +func (w AnyWrap) String() string { + if w.IsError { + return "(error)" + } else if w.IsNil { + return "(nil)" + } else { + return w.Json + } +} + +func deserializeAnyWrap(v string) AnyWrap { + r := strings.SplitN(v, ":", 3) + if len(r) != 3 { + return AnyWrap{IsError: true, Type: "", Json: "", IsNil: false} + } else { + if r[0] == "OK" { + return AnyWrap{IsError: false, Type: r[1], Json: r[2], IsNil: false} + } else if r[0] == "ERR" { + return AnyWrap{IsError: true, Type: r[1], Json: r[2], IsNil: false} + } else if r[0] == "NIL" { + return AnyWrap{IsError: false, Type: r[1], Json: "", IsNil: true} + } else { + return AnyWrap{IsError: true, Type: "", Json: "", IsNil: false} + } + } +}