github.com/haraldrudell/parl@v0.4.176/perrors/errorglue/error-stack.go (about) 1 /* 2 © 2020–present Harald Rudell <harald.rudell@gmail.com> (https://haraldrudell.github.io/haraldrudell/) 3 ISC License 4 */ 5 6 package errorglue 7 8 import ( 9 "fmt" 10 "strings" 11 12 "github.com/haraldrudell/parl/pruntime" 13 ) 14 15 const errorStackAtString = "\x20at\x20" 16 17 // errorStack provides a stack trace to an error chain. 18 // - errorStack has publics Error() Unwrap() Format() ChainString() StackTrace() 19 type errorStack struct { 20 // Format() Unwrap() Error() 21 RichError 22 s pruntime.Stack 23 } 24 25 // errorStack implements [error] 26 var _ error = &errorStack{} 27 28 // errorStack implements [ErrorCallStacker] can provide a stack trace [ErrorCallStacker.StackTrace] 29 var _ ErrorCallStacker = &errorStack{} 30 31 // errorStack implements [ChainStringer]: [ChainStringer.ChainString] 32 var _ ChainStringer = &errorStack{} 33 34 // errorStack implements [fmt.Formatter]: has features for [fmt.Printf]: [fmt.Formatter.Format] 35 var _ fmt.Formatter = &errorStack{} 36 37 // errorStack implements [Wrapper]: has an error chain [Wrapper.Unwrap] 38 var _ Wrapper = &errorStack{} 39 40 // NewErrorStack attaches a stack to err 41 func NewErrorStack(err error, st pruntime.Stack) (e2 error) { 42 return &errorStack{*newRichError(err), st} 43 } 44 45 // StackTrace returns a clone of e’s stack trace 46 func (e *errorStack) StackTrace() (st pruntime.Stack) { 47 if e == nil { 48 return 49 } 50 return e.s 51 } 52 53 // ChainString is invoked by [ChainStringer] to get a specific format 54 // - nil error or unknown format returns empty string “” 55 // - DefaultFormat: “message” 56 // - ShortFormat: “message at runtime/panic.go:914” 57 // - LongFormat: “message \n runtime.gopanic \n runtime/panic.go:914” 58 // - ShortSuffix: “runtime/panic.go:914” 59 // - LongSuffix: “message \n runtime.gopanic \n runtime/panic.go:914” 60 func (errorStackValue *errorStack) ChainString(format CSFormat) (s string) { 61 if errorStackValue == nil { 62 return // nil error return: no location 63 } 64 switch format { 65 case DefaultFormat: 66 s = errorStackValue.Error() 67 return 68 case ShortFormat: 69 _, s = errorStackValue.shortCodeLocationString() 70 // ensure stack frame begins with " at " 71 if !strings.HasPrefix(s, errorStackAtString) { 72 s = errorStackAtString + s 73 } 74 s = errorStackValue.Error() + s 75 return 76 case ShortSuffix: 77 _, s = errorStackValue.shortCodeLocationString() 78 // ensure no " at " 79 s = strings.TrimPrefix(s, errorStackAtString) 80 return 81 case LongFormat: // full stack trace 82 // add location if trace contains panic 83 if isPanic, loc := errorStackValue.shortCodeLocationString(); isPanic { 84 s = loc 85 if !strings.HasPrefix(s, errorStackAtString) { 86 s = errorStackAtString + s 87 } 88 } 89 s = fmt.Sprintf("%s [%T]%s\n%s", 90 errorStackValue.Error(), errorStackValue, // “error-message [errors.Type]” 91 s, // “ at runtime.gopanic:17” 92 errorStackValue.s.String(), // multiple-line stack-trace 93 ) 94 return 95 case LongSuffix: 96 s = errorStackValue.s.String() 97 return 98 default: 99 return // unknown format 100 } 101 } 102 103 // find the code location where the error occured 104 // - “mains.(*Executable).AddErr-executable.go:25” 105 func (errorStackValue *errorStack) shortCodeLocationString() (isPanic bool, shortCodeLocationString string) { 106 107 // check for a panic which will be the code location 108 // - instead of returning random runtime locations, 109 // this returns the code line where the panic occurred 110 // - this may be a stack from subordinate error in the error chain 111 var frame pruntime.Frame 112 if isPanic0, stack, _, panicIndex, _, _ := FirstPanicStack(errorStackValue); isPanic0 { 113 isPanic = true 114 var frames = stack.Frames() 115 // code line that caused panic 116 frame = frames[panicIndex] 117 } else { 118 // regular code location 119 frame = errorStackValue.s.Frames()[0] 120 } 121 shortCodeLocationString = frame.Loc().Short() 122 123 return 124 }