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)
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 {

View File

@ -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))
}

View File

@ -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))