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  }