diff --git a/internal/status/status.go b/internal/status/status.go index 41cbf0437aa..c06432f264f 100644 --- a/internal/status/status.go +++ b/internal/status/status.go @@ -30,12 +30,14 @@ package status import ( "errors" "fmt" + "runtime" "strings" ) import ( "github.com/golang/protobuf/proto" "github.com/golang/protobuf/ptypes" + perrors "github.com/pkg/errors" "google.golang.org/genproto/googleapis/rpc/errdetails" spb "google.golang.org/genproto/googleapis/rpc/status" @@ -48,20 +50,27 @@ import ( // Status represents an RPC status code, message, and details. It is immutable // and should be created with New, Newf, or FromProto. type Status struct { - s *spb.Status + s *spb.Status + stack *stack } -// New returns a Status representing c and msg. +// New returns a Status representing c and msg, with user-made error as stack func New(c codes.Code, msg string) *Status { - return &Status{s: &spb.Status{Code: int32(c), Message: msg}} + newStatus := &Status{s: &spb.Status{Code: int32(c), Message: msg}} + newStatusWithDetail, _ := newStatus.WithDetails(&errdetails.DebugInfo{ + StackEntries: []string{ + fmt.Sprintf("%+v", callers().StackTrace()), // use e.String() as triple stack + }, + }) + return newStatusWithDetail } -// New returns a Status representing c and msg. -func NewWithStacks(c codes.Code, e error) *Status { +// New returns a Status representing c and msg. with e.String() as triple stack field +func NewWithoutStacks(c codes.Code, e error) *Status { newStatus := &Status{s: &spb.Status{Code: int32(c), Message: e.Error()}} newStatusWithDetail, _ := newStatus.WithDetails(&errdetails.DebugInfo{ StackEntries: []string{ - fmt.Sprintf("%+v", e), + fmt.Sprintf("%+v", e), // use e.String() as triple stack }, }) return newStatusWithDetail @@ -162,13 +171,22 @@ func (s *Status) String() string { // Error wraps a pointer of a status proto. It implements error and Status, // and a nil *Error should never be returned by this package. type Error struct { - s *Status + s *Status + stack stack } func (e *Error) Error() string { return e.s.String() } +func (e *Error) Message() string { + return e.s.String() +} + +func (e *Error) Code() codes.Code { + return e.s.Code() +} + // GRPCStatus returns the Status represented by se. func (e *Error) GRPCStatus() *Status { return e.s @@ -196,3 +214,35 @@ func (e *Error) Is(target error) bool { } return proto.Equal(e.s.s, tse.s.s) } + +// stack represents a stack of program counters. +type stack []uintptr + +func (s *stack) Format(st fmt.State, verb rune) { + switch verb { + case 'v': + switch { + case st.Flag('+'): + for _, pc := range *s { + f := perrors.Frame(pc) + fmt.Fprintf(st, "\n%+v", f) + } + } + } +} + +func (s *stack) StackTrace() perrors.StackTrace { + f := make([]perrors.Frame, len(*s)) + for i := 0; i < len(f); i++ { + f[i] = perrors.Frame((*s)[i]) + } + return f +} + +func callers() *stack { + const depth = 32 + var pcs [depth]uintptr + n := runtime.Callers(5, pcs[:]) + var st stack = pcs[0:n] + return &st +} diff --git a/server.go b/server.go index ea600457255..d4aac5a79d2 100644 --- a/server.go +++ b/server.go @@ -1300,7 +1300,7 @@ func (s *Server) processUnaryRPC(method string, t transport.ServerTransport, str appStatus, ok := status.FromError(appErr) if !ok { // Convert appErr if it is not a grpc status error. - appStatus = status.ErrorWithStacks(codes.Unknown, appErr) + appStatus = status.ErrorWithoutStacks(appStatus.Code(), appErr) } if trInfo != nil { trInfo.tr.LazyLog(stringer(appStatus.Message()), true) diff --git a/status/status.go b/status/status.go index f4aebdcb968..60341d9d0f4 100644 --- a/status/status.go +++ b/status/status.go @@ -63,13 +63,13 @@ func Error(c codes.Code, msg string) error { } // Error returns an error representing c and msg. If c is OK, returns nil. -func ErrorWithStacks(c codes.Code, e error) *Status { - return status.NewWithStacks(c, e) +func ErrorWithoutStacks(c codes.Code, e error) *Status { + return status.NewWithoutStacks(c, e) } // Errorf returns Error(c, fmt.Sprintf(format, a...)). func Errorf(c codes.Code, format string, a ...interface{}) error { - return Error(c, fmt.Sprintf(format, a...)) + return New(c, fmt.Sprintf(format, a...)).Err() } // ErrorProto returns an error representing s. If s.Code is OK, returns nil.