diff --git a/merr/merr.go b/merr/merr.go index 0fb1175..9d59282 100644 --- a/merr/merr.go +++ b/merr/merr.go @@ -47,7 +47,10 @@ func wrap(e error, cp bool) *err { er, ok := e.(*err) if !ok { - return &err{err: e} + return &err{ + err: e, + attr: make(map[interface{}]interface{}, 1), + } } else if !cp { return er } @@ -100,19 +103,21 @@ func Value(e error, k interface{}) interface{} { // WrapSkip is like Wrap but also allows for skipping extra stack frames when // embedding the stack into the error. func WrapSkip(ctx context.Context, e error, skip int, kvs ...interface{}) error { - prevCtx, _ := Value(e, attrKeyCtx).(context.Context) - if prevCtx != nil { + er := wrap(e, true) + if _, ok := getStack(er); !ok { + setStack(er, skip+1) + } + + if prevCtx, _ := er.attr[attrKeyCtx].(context.Context); prevCtx != nil { ctx = mctx.MergeAnnotations(prevCtx, ctx) } - if _, ok := mctx.Stack(ctx); !ok { - ctx = mctx.WithStack(ctx, skip+1) - } if len(kvs) > 0 { ctx = mctx.Annotate(ctx, kvs...) } - return WithValue(e, attrKeyCtx, ctx) + er.attr[attrKeyCtx] = ctx + return er } // Wrap takes in an error and returns one which wraps it in merr's inner type, @@ -122,9 +127,9 @@ func WrapSkip(ctx context.Context, e error, skip int, kvs ...interface{}) error // For convenience, extra annotation information can be passed in here as well // via the kvs argument. See mctx.Annotate for more information. // -// This function automatically embeds stack information into the Context as it's -// being stored, using mctx.WithStack, unless the error already has stack -// information in it. +// This function automatically embeds stack information into the error as it's +// being stored, using WithStack, unless the error already has stack information +// in it. func Wrap(ctx context.Context, e error, kvs ...interface{}) error { return WrapSkip(ctx, e, 1, kvs...) } @@ -146,7 +151,7 @@ func ctx(e error) context.Context { ctx = context.Background() } - if stack, ok := mctx.Stack(ctx); ok { + if stack, ok := Stack(e); ok { ctx = mctx.Annotate(ctx, annotateKey("errLoc"), stack.String()) } return ctx @@ -156,9 +161,9 @@ func ctx(e error) context.Context { // or New. If none is embedded this uses context.Background(). // // The returned Context will have annotated on it (see mctx.Annotate) the -// underlying error's string (as returned by Error()) and the stack location in -// the Context. Stack locations are automatically added by New and Wrap via -// mctx.WithStack. +// underlying error's string (as returned by Error()) and the error's stack +// location. Stack locations are automatically added by New and Wrap via +// WithStack. // // If this error is nil this returns context.Background(). func Context(e error) context.Context { diff --git a/mctx/stack.go b/merr/stack.go similarity index 73% rename from mctx/stack.go rename to merr/stack.go index 37bd4a9..2e59094 100644 --- a/mctx/stack.go +++ b/merr/stack.go @@ -1,7 +1,6 @@ -package mctx +package merr import ( - "context" "fmt" "path/filepath" "runtime" @@ -13,7 +12,7 @@ import ( // stored when embedding stack traces in errors. var MaxStackSize = 50 -type ctxStackKey int +type stackKey int // Stacktrace represents a stack trace at a particular point in execution. type Stacktrace struct { @@ -73,22 +72,34 @@ func (s Stacktrace) FullString() string { return sb.String() } -// WithStack returns a Context with the current stacktrace embedded in it (as a +func setStack(er *err, skip int) { + stackSlice := make([]uintptr, MaxStackSize+skip) + // incr skip once for WithStack, and once for runtime.Callers + l := runtime.Callers(skip+2, stackSlice) + er.attr[stackKey(0)] = Stacktrace{frames: stackSlice[:l]} +} + +// WithStack returns an error with the current stacktrace embedded in it (as a // Stacktrace type). If skip is non-zero it will skip that many frames from the // top of the stack. The frame containing the WithStack call itself is always // excluded. -func WithStack(ctx context.Context, skip int) context.Context { - stackSlice := make([]uintptr, MaxStackSize) - // incr skip once for WithStack, and once for runtime.Callers - l := runtime.Callers(skip+2, stackSlice) - stack := Stacktrace{frames: stackSlice[:l]} - - return context.WithValue(ctx, ctxStackKey(0), stack) +// +// This call always overwrites any previously existing stack information on the +// error, as opposed to Wrap which only does so if the error didn't already have +// any. +func WithStack(e error, skip int) error { + er := wrap(e, true) + setStack(er, skip+1) + return er } -// Stack returns the Stacktrace instance which was embedded by WithStack, or false if -// none ever was. -func Stack(ctx context.Context) (Stacktrace, bool) { - stack, ok := ctx.Value(ctxStackKey(0)).(Stacktrace) +func getStack(er *err) (Stacktrace, bool) { + stack, ok := er.attr[stackKey(0)].(Stacktrace) return stack, ok } + +// Stack returns the Stacktrace instance which was embedded by Wrap/WrapSkip, or +// false if none ever was. +func Stack(e error) (Stacktrace, bool) { + return getStack(wrap(e, false)) +} diff --git a/mctx/stack_test.go b/merr/stack_test.go similarity index 95% rename from mctx/stack_test.go rename to merr/stack_test.go index cea617b..5a27138 100644 --- a/mctx/stack_test.go +++ b/merr/stack_test.go @@ -1,4 +1,4 @@ -package mctx +package merr import ( "context" @@ -9,7 +9,7 @@ import ( ) func TestStack(t *T) { - foo := WithStack(context.Background(), 0) + foo := New(context.Background(), "test") fooStack, ok := Stack(foo) massert.Fatal(t, massert.Equal(true, ok))