diff --git a/errors/error.go b/errors/error.go index 7e0cdb7..359d345 100644 --- a/errors/error.go +++ b/errors/error.go @@ -3,6 +3,8 @@ package errors import ( "fmt" "strings" + + "github.com/pkg/errors" ) type LogSeverity string @@ -73,6 +75,8 @@ type NCError struct { // Contains stack trace from the initial place when the error // was raised. Stack []string + // Raw stack trace in the form of program counters, as returned by the runtime.Callers + RawStack *stack //The root error at the base level. RootError error } @@ -85,6 +89,12 @@ func (n NCError) Error() string { return strings.Join(messages, ": ") } +// StackTrace returns github.com/pkg/errors stack trace. This is to implement interface from aws-xray-sdk-go +// for passing along stack traces to X-Ray to segments. +func (n NCError) StackTrace() errors.StackTrace { + return n.RawStack.StackTrace() +} + // New error with context. func New(message string, fields Fields) error { fileName, funcName, lineNumber := GetRuntimeContext() @@ -95,9 +105,12 @@ func New(message string, fields Fields) error { FileName: fileName, Line: lineNumber, Severity: ERROR} + stack, rawStack := getStackTraces() return NCError{ - Causes: []Cause{newCause}, - Stack: GetTrace()} + Causes: []Cause{newCause}, + Stack: stack, + RawStack: rawStack, + } } func NewWithSeverity(message string, fields Fields, severity LogSeverity) error { @@ -110,9 +123,11 @@ func NewWithSeverity(message string, fields Fields, severity LogSeverity) error Line: lineNumber, Severity: severity, } + stack, rawStack := getStackTraces() return NCError{ - Causes: []Cause{newCause}, - Stack: GetTrace(), + Causes: []Cause{newCause}, + Stack: stack, + RawStack: rawStack, } } @@ -135,9 +150,11 @@ func WithContext(err error, message string, fields Fields) error { return ncError } + stack, rawStack := getStackTraces() return NCError{ Causes: []Cause{newCause, Cause{Message: err.Error()}}, - Stack: GetTrace(), + Stack: stack, + RawStack: rawStack, RootError: err} } @@ -160,9 +177,11 @@ func WithContextAndSeverity(err error, message string, severity LogSeverity, fie return ncError } + stack, rawStack := getStackTraces() return NCError{ Causes: []Cause{newCause, Cause{Message: err.Error()}}, - Stack: GetTrace(), + Stack: stack, + RawStack: rawStack, RootError: err, } } diff --git a/errors/stack_trace.go b/errors/stack_trace.go index 3198fcf..8215e4a 100644 --- a/errors/stack_trace.go +++ b/errors/stack_trace.go @@ -5,26 +5,35 @@ import ( "regexp" "runtime" "strings" + + "github.com/pkg/errors" ) var ( regexFuncName = regexp.MustCompile(`(([^/]+/)+)?([^/.]+)((\.[^/.]+)+)?`) ) -// GetTrace return the simplified stack trace in the format file_name(func_name):line. It also contains the current goroutine entrypoint. -func GetTrace() []string { - var stack []string +// getStackTraces returns custom-formatted and raw (in the form of program counters) stack trace +// for the purpose of initializing NCError struct +func getStackTraces() ([]string, *stack) { + var formattedStack []string callStack := *callers() st := callStack[:len(callStack)-1] for _, f := range st { frame := frame(f) - stack = append(stack, frame.formatContext()) + formattedStack = append(formattedStack, frame.formatContext()) } + return formattedStack, &callStack +} + +// GetTrace return the simplified stack trace in the format file_name(func_name):line. It also contains the current goroutine entrypoint. +func GetTrace() []string { + stack, _ := getStackTraces() + return stack } -type stack []uintptr type frame uintptr func (f frame) pc() uintptr { return uintptr(f) - 1 } @@ -71,6 +80,16 @@ func GetRuntimeContext() (fileName, funcName string, line int) { return } +type stack []uintptr + +func (s *stack) StackTrace() errors.StackTrace { + f := make([]errors.Frame, len(*s)) + for i := 0; i < len(f); i++ { + f[i] = errors.Frame((*s)[i]) + } + return f +} + func callers() *stack { const depth = 32 var pcs [depth]uintptr