merr/mctx: move stack code into merr

This commit is contained in:
Brian Picciano 2019-02-27 12:18:56 -05:00
parent 553a4854ea
commit 4303da03cc
3 changed files with 47 additions and 31 deletions

View File

@ -47,7 +47,10 @@ func wrap(e error, cp bool) *err {
er, ok := e.(*err) er, ok := e.(*err)
if !ok { if !ok {
return &err{err: e} return &err{
err: e,
attr: make(map[interface{}]interface{}, 1),
}
} else if !cp { } else if !cp {
return er 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 // WrapSkip is like Wrap but also allows for skipping extra stack frames when
// embedding the stack into the error. // embedding the stack into the error.
func WrapSkip(ctx context.Context, e error, skip int, kvs ...interface{}) error { func WrapSkip(ctx context.Context, e error, skip int, kvs ...interface{}) error {
prevCtx, _ := Value(e, attrKeyCtx).(context.Context) er := wrap(e, true)
if prevCtx != nil { if _, ok := getStack(er); !ok {
setStack(er, skip+1)
}
if prevCtx, _ := er.attr[attrKeyCtx].(context.Context); prevCtx != nil {
ctx = mctx.MergeAnnotations(prevCtx, ctx) ctx = mctx.MergeAnnotations(prevCtx, ctx)
} }
if _, ok := mctx.Stack(ctx); !ok {
ctx = mctx.WithStack(ctx, skip+1)
}
if len(kvs) > 0 { if len(kvs) > 0 {
ctx = mctx.Annotate(ctx, kvs...) 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, // 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 // For convenience, extra annotation information can be passed in here as well
// via the kvs argument. See mctx.Annotate for more information. // via the kvs argument. See mctx.Annotate for more information.
// //
// This function automatically embeds stack information into the Context as it's // This function automatically embeds stack information into the error as it's
// being stored, using mctx.WithStack, unless the error already has stack // being stored, using WithStack, unless the error already has stack information
// information in it. // in it.
func Wrap(ctx context.Context, e error, kvs ...interface{}) error { func Wrap(ctx context.Context, e error, kvs ...interface{}) error {
return WrapSkip(ctx, e, 1, kvs...) return WrapSkip(ctx, e, 1, kvs...)
} }
@ -146,7 +151,7 @@ func ctx(e error) context.Context {
ctx = context.Background() ctx = context.Background()
} }
if stack, ok := mctx.Stack(ctx); ok { if stack, ok := Stack(e); ok {
ctx = mctx.Annotate(ctx, annotateKey("errLoc"), stack.String()) ctx = mctx.Annotate(ctx, annotateKey("errLoc"), stack.String())
} }
return ctx return ctx
@ -156,9 +161,9 @@ func ctx(e error) context.Context {
// or New. If none is embedded this uses context.Background(). // or New. If none is embedded this uses context.Background().
// //
// The returned Context will have annotated on it (see mctx.Annotate) the // 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 // underlying error's string (as returned by Error()) and the error's stack
// the Context. Stack locations are automatically added by New and Wrap via // location. Stack locations are automatically added by New and Wrap via
// mctx.WithStack. // WithStack.
// //
// If this error is nil this returns context.Background(). // If this error is nil this returns context.Background().
func Context(e error) context.Context { func Context(e error) context.Context {

View File

@ -1,7 +1,6 @@
package mctx package merr
import ( import (
"context"
"fmt" "fmt"
"path/filepath" "path/filepath"
"runtime" "runtime"
@ -13,7 +12,7 @@ import (
// stored when embedding stack traces in errors. // stored when embedding stack traces in errors.
var MaxStackSize = 50 var MaxStackSize = 50
type ctxStackKey int type stackKey int
// Stacktrace represents a stack trace at a particular point in execution. // Stacktrace represents a stack trace at a particular point in execution.
type Stacktrace struct { type Stacktrace struct {
@ -73,22 +72,34 @@ func (s Stacktrace) FullString() string {
return sb.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 // 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 // top of the stack. The frame containing the WithStack call itself is always
// excluded. // excluded.
func WithStack(ctx context.Context, skip int) context.Context { //
stackSlice := make([]uintptr, MaxStackSize) // This call always overwrites any previously existing stack information on the
// incr skip once for WithStack, and once for runtime.Callers // error, as opposed to Wrap which only does so if the error didn't already have
l := runtime.Callers(skip+2, stackSlice) // any.
stack := Stacktrace{frames: stackSlice[:l]} func WithStack(e error, skip int) error {
er := wrap(e, true)
return context.WithValue(ctx, ctxStackKey(0), stack) setStack(er, skip+1)
return er
} }
// Stack returns the Stacktrace instance which was embedded by WithStack, or false if func getStack(er *err) (Stacktrace, bool) {
// none ever was. stack, ok := er.attr[stackKey(0)].(Stacktrace)
func Stack(ctx context.Context) (Stacktrace, bool) {
stack, ok := ctx.Value(ctxStackKey(0)).(Stacktrace)
return stack, ok 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))
}

View File

@ -1,4 +1,4 @@
package mctx package merr
import ( import (
"context" "context"
@ -9,7 +9,7 @@ import (
) )
func TestStack(t *T) { func TestStack(t *T) {
foo := WithStack(context.Background(), 0) foo := New(context.Background(), "test")
fooStack, ok := Stack(foo) fooStack, ok := Stack(foo)
massert.Fatal(t, massert.Equal(true, ok)) massert.Fatal(t, massert.Equal(true, ok))