package sentry import ( "fmt" "reflect" "slices" ) const ( MechanismTypeGeneric string = "generic" MechanismTypeChained string = "chained" MechanismTypeUnwrap string = "unwrap" MechanismSourceCause string = "cause" ) func convertErrorToExceptions(err error, maxErrorDepth int) []Exception { var exceptions []Exception visited := make(map[error]bool) convertErrorDFS(err, &exceptions, nil, "", visited, maxErrorDepth, 0) // mechanism type is used for debugging purposes, but since we can't really distinguish the origin of who invoked // captureException, we set it to nil if the error is not chained. if len(exceptions) == 1 { exceptions[0].Mechanism = nil } slices.Reverse(exceptions) // Add a trace of the current stack to the top level(outermost) error in a chain if // it doesn't have a stack trace yet. // We only add to the most recent error to avoid duplication and because the // current stack is most likely unrelated to errors deeper in the chain. if len(exceptions) > 0 && exceptions[len(exceptions)-1].Stacktrace == nil { exceptions[len(exceptions)-1].Stacktrace = NewStacktrace() } return exceptions } func convertErrorDFS(err error, exceptions *[]Exception, parentID *int, source string, visited map[error]bool, maxErrorDepth int, currentDepth int) { if err == nil { return } if visited[err] { return } visited[err] = true _, isExceptionGroup := err.(interface{ Unwrap() []error }) exception := Exception{ Value: err.Error(), Type: reflect.TypeOf(err).String(), Stacktrace: ExtractStacktrace(err), } currentID := len(*exceptions) var mechanismType string if parentID == nil { mechanismType = MechanismTypeGeneric source = "" } else { mechanismType = MechanismTypeChained } exception.Mechanism = &Mechanism{ Type: mechanismType, ExceptionID: currentID, ParentID: parentID, Source: source, IsExceptionGroup: isExceptionGroup, } *exceptions = append(*exceptions, exception) if maxErrorDepth >= 0 && currentDepth >= maxErrorDepth { return } switch v := err.(type) { case interface{ Unwrap() []error }: unwrapped := v.Unwrap() for i := range unwrapped { if unwrapped[i] != nil { childSource := fmt.Sprintf("errors[%d]", i) convertErrorDFS(unwrapped[i], exceptions, ¤tID, childSource, visited, maxErrorDepth, currentDepth+1) } } case interface{ Unwrap() error }: unwrapped := v.Unwrap() if unwrapped != nil { convertErrorDFS(unwrapped, exceptions, ¤tID, MechanismTypeUnwrap, visited, maxErrorDepth, currentDepth+1) } case interface{ Cause() error }: cause := v.Cause() if cause != nil { convertErrorDFS(cause, exceptions, ¤tID, MechanismSourceCause, visited, maxErrorDepth, currentDepth+1) } } }