github.com/xg0n/routine@v0.0.0-20240119033701-c364deb94aee/error.go (about)

     1  package routine
     2  
     3  import (
     4  	"bytes"
     5  	"fmt"
     6  	"reflect"
     7  	"runtime"
     8  	"strconv"
     9  	"unicode"
    10  )
    11  
    12  const (
    13  	newLine              = "\n"
    14  	innerErrorPrefix     = " ---> "
    15  	endOfInnerErrorStack = "--- End of inner error stack trace ---"
    16  	endOfErrorStack      = "--- End of error stack trace ---"
    17  	wordAt               = "at"
    18  	wordIn               = "in"
    19  	wordCreatedBy        = "created by"
    20  )
    21  
    22  type runtimeError struct {
    23  	goid       int64
    24  	gopc       uintptr
    25  	message    string
    26  	stackTrace []uintptr
    27  	cause      RuntimeError
    28  }
    29  
    30  func (re *runtimeError) Goid() int64 {
    31  	return re.goid
    32  }
    33  
    34  func (re *runtimeError) Gopc() uintptr {
    35  	return re.gopc
    36  }
    37  
    38  func (re *runtimeError) Message() string {
    39  	return re.message
    40  }
    41  
    42  func (re *runtimeError) StackTrace() []uintptr {
    43  	return re.stackTrace
    44  }
    45  
    46  func (re *runtimeError) Cause() RuntimeError {
    47  	return re.cause
    48  }
    49  
    50  func (re *runtimeError) Error() string {
    51  	return runtimeErrorError(re)
    52  }
    53  
    54  func runtimeErrorNew(cause any) (goid int64, gopc uintptr, msg string, stackTrace []uintptr, innerErr RuntimeError) {
    55  	runtimeErr, isRuntimeErr := cause.(RuntimeError)
    56  	if !isRuntimeErr {
    57  		if err, isErr := cause.(error); isErr {
    58  			msg = err.Error()
    59  		} else if cause != nil {
    60  			msg = fmt.Sprint(cause)
    61  		}
    62  	}
    63  	gp := getg()
    64  	return gp.goid, *gp.gopc, msg, captureStackTrace(2, 100), runtimeErr
    65  }
    66  
    67  func runtimeErrorNewWithMessage(message string) (goid int64, gopc uintptr, msg string, stackTrace []uintptr, innerErr RuntimeError) {
    68  	gp := getg()
    69  	return gp.goid, *gp.gopc, message, captureStackTrace(2, 100), nil
    70  }
    71  
    72  func runtimeErrorNewWithMessageCause(message string, cause any) (goid int64, gopc uintptr, msg string, stackTrace []uintptr, innerErr RuntimeError) {
    73  	runtimeErr, isRuntimeErr := cause.(RuntimeError)
    74  	if !isRuntimeErr {
    75  		causeMsg := ""
    76  		if err, isErr := cause.(error); isErr {
    77  			causeMsg = err.Error()
    78  		} else if cause != nil {
    79  			causeMsg = fmt.Sprint(cause)
    80  		}
    81  		if len(message) == 0 {
    82  			message = causeMsg
    83  		} else if len(causeMsg) != 0 {
    84  			message += " - " + causeMsg
    85  		}
    86  	}
    87  	gp := getg()
    88  	return gp.goid, *gp.gopc, message, captureStackTrace(2, 100), runtimeErr
    89  }
    90  
    91  func runtimeErrorError(re RuntimeError) string {
    92  	builder := &bytes.Buffer{}
    93  	runtimeErrorPrintStackTrace(re, builder)
    94  	runtimeErrorPrintCreatedBy(re, builder)
    95  	return builder.String()
    96  }
    97  
    98  func runtimeErrorPrintStackTrace(re RuntimeError, builder *bytes.Buffer) {
    99  	builder.WriteString(runtimeErrorTypeName(re))
   100  	message := re.Message()
   101  	if len(message) > 0 {
   102  		builder.WriteString(": ")
   103  		builder.WriteString(message)
   104  	}
   105  	cause := re.Cause()
   106  	if cause != nil {
   107  		builder.WriteString(newLine)
   108  		builder.WriteString(innerErrorPrefix)
   109  		runtimeErrorPrintStackTrace(cause, builder)
   110  		builder.WriteString(newLine)
   111  		builder.WriteString("   ")
   112  		builder.WriteString(endOfInnerErrorStack)
   113  	}
   114  	stackTrace := re.StackTrace()
   115  	if stackTrace != nil {
   116  		savePoint := builder.Len()
   117  		skippedPanic := false
   118  		frames := runtime.CallersFrames(stackTrace)
   119  		for {
   120  			frame, more := frames.Next()
   121  			if showFrame(frame.Function) {
   122  				builder.WriteString(newLine)
   123  				builder.WriteString("   ")
   124  				builder.WriteString(wordAt)
   125  				builder.WriteString(" ")
   126  				builder.WriteString(frame.Function)
   127  				builder.WriteString("() ")
   128  				builder.WriteString(wordIn)
   129  				builder.WriteString(" ")
   130  				builder.WriteString(frame.File)
   131  				builder.WriteString(":")
   132  				builder.WriteString(strconv.Itoa(frame.Line))
   133  			} else if skipFrame(frame.Function, skippedPanic) {
   134  				builder.Truncate(savePoint)
   135  				skippedPanic = true
   136  			}
   137  			if !more {
   138  				break
   139  			}
   140  		}
   141  	}
   142  }
   143  
   144  func runtimeErrorPrintCreatedBy(re RuntimeError, builder *bytes.Buffer) {
   145  	goid := re.Goid()
   146  	if goid == 1 {
   147  		return
   148  	}
   149  	pc := re.Gopc()
   150  	frame, _ := runtime.CallersFrames([]uintptr{pc}).Next()
   151  	if frame.Func == nil {
   152  		return
   153  	}
   154  	builder.WriteString(newLine)
   155  	builder.WriteString("   ")
   156  	builder.WriteString(endOfErrorStack)
   157  	builder.WriteString(newLine)
   158  	builder.WriteString("   ")
   159  	builder.WriteString(wordCreatedBy)
   160  	builder.WriteString(" ")
   161  	builder.WriteString(frame.Function)
   162  	builder.WriteString("() ")
   163  	builder.WriteString(wordIn)
   164  	builder.WriteString(" ")
   165  	builder.WriteString(frame.File)
   166  	builder.WriteString(":")
   167  	builder.WriteString(strconv.Itoa(frame.Line))
   168  }
   169  
   170  func runtimeErrorTypeName(re RuntimeError) string {
   171  	typeName := []rune(reflect.TypeOf(re).Elem().Name())
   172  	typeName[0] = unicode.ToUpper(typeName[0])
   173  	return string(typeName)
   174  }