From bc61230b657396db651b8a31de68e63ec05db04a Mon Sep 17 00:00:00 2001 From: Greg Weber Date: Thu, 16 Aug 2018 08:29:18 -0700 Subject: [PATCH 01/18] add pkg/errors to Gopkg --- Gopkg.lock | 6 + Gopkg.toml | 4 + vendor/github.com/pkg/errors/LICENSE | 23 +++ vendor/github.com/pkg/errors/errors.go | 269 +++++++++++++++++++++++++ vendor/github.com/pkg/errors/stack.go | 178 ++++++++++++++++ 5 files changed, 480 insertions(+) create mode 100644 vendor/github.com/pkg/errors/LICENSE create mode 100644 vendor/github.com/pkg/errors/errors.go create mode 100644 vendor/github.com/pkg/errors/stack.go diff --git a/Gopkg.lock b/Gopkg.lock index 85471fc1cc0..6a6b2d1565a 100644 --- a/Gopkg.lock +++ b/Gopkg.lock @@ -334,6 +334,12 @@ pruneopts = "NUT" revision = "279515615485b0f2d12f1421cc412fe2784e0190" +[[projects]] + name = "github.com/pkg/errors" + packages = ["."] + revision = "645ef00459ed84a119197bfb8d8205042c6df63d" + version = "v0.8.0" + [[projects]] digest = "1:8d8f554bbb62fb7aecf661b85b25e227f6ab6cfe2b4395ea65ef478bfc174940" name = "github.com/prometheus/client_golang" diff --git a/Gopkg.toml b/Gopkg.toml index 1ff28a85da4..b56494b4163 100644 --- a/Gopkg.toml +++ b/Gopkg.toml @@ -35,3 +35,7 @@ [[constraint]] name = "github.com/etcd-io/gofail" branch = "master" + +[[constraint]] + name = "github.com/pkg/errors" + version = "0.8.0" diff --git a/vendor/github.com/pkg/errors/LICENSE b/vendor/github.com/pkg/errors/LICENSE new file mode 100644 index 00000000000..835ba3e755c --- /dev/null +++ b/vendor/github.com/pkg/errors/LICENSE @@ -0,0 +1,23 @@ +Copyright (c) 2015, Dave Cheney +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + +* Redistributions of source code must retain the above copyright notice, this + list of conditions and the following disclaimer. + +* Redistributions in binary form must reproduce the above copyright notice, + this list of conditions and the following disclaimer in the documentation + and/or other materials provided with the distribution. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE +FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER +CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, +OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. diff --git a/vendor/github.com/pkg/errors/errors.go b/vendor/github.com/pkg/errors/errors.go new file mode 100644 index 00000000000..842ee80456d --- /dev/null +++ b/vendor/github.com/pkg/errors/errors.go @@ -0,0 +1,269 @@ +// Package errors provides simple error handling primitives. +// +// The traditional error handling idiom in Go is roughly akin to +// +// if err != nil { +// return err +// } +// +// which applied recursively up the call stack results in error reports +// without context or debugging information. The errors package allows +// programmers to add context to the failure path in their code in a way +// that does not destroy the original value of the error. +// +// Adding context to an error +// +// The errors.Wrap function returns a new error that adds context to the +// original error by recording a stack trace at the point Wrap is called, +// and the supplied message. For example +// +// _, err := ioutil.ReadAll(r) +// if err != nil { +// return errors.Wrap(err, "read failed") +// } +// +// If additional control is required the errors.WithStack and errors.WithMessage +// functions destructure errors.Wrap into its component operations of annotating +// an error with a stack trace and an a message, respectively. +// +// Retrieving the cause of an error +// +// Using errors.Wrap constructs a stack of errors, adding context to the +// preceding error. Depending on the nature of the error it may be necessary +// to reverse the operation of errors.Wrap to retrieve the original error +// for inspection. Any error value which implements this interface +// +// type causer interface { +// Cause() error +// } +// +// can be inspected by errors.Cause. errors.Cause will recursively retrieve +// the topmost error which does not implement causer, which is assumed to be +// the original cause. For example: +// +// switch err := errors.Cause(err).(type) { +// case *MyError: +// // handle specifically +// default: +// // unknown error +// } +// +// causer interface is not exported by this package, but is considered a part +// of stable public API. +// +// Formatted printing of errors +// +// All error values returned from this package implement fmt.Formatter and can +// be formatted by the fmt package. The following verbs are supported +// +// %s print the error. If the error has a Cause it will be +// printed recursively +// %v see %s +// %+v extended format. Each Frame of the error's StackTrace will +// be printed in detail. +// +// Retrieving the stack trace of an error or wrapper +// +// New, Errorf, Wrap, and Wrapf record a stack trace at the point they are +// invoked. This information can be retrieved with the following interface. +// +// type stackTracer interface { +// StackTrace() errors.StackTrace +// } +// +// Where errors.StackTrace is defined as +// +// type StackTrace []Frame +// +// The Frame type represents a call site in the stack trace. Frame supports +// the fmt.Formatter interface that can be used for printing information about +// the stack trace of this error. For example: +// +// if err, ok := err.(stackTracer); ok { +// for _, f := range err.StackTrace() { +// fmt.Printf("%+s:%d", f) +// } +// } +// +// stackTracer interface is not exported by this package, but is considered a part +// of stable public API. +// +// See the documentation for Frame.Format for more details. +package errors + +import ( + "fmt" + "io" +) + +// New returns an error with the supplied message. +// New also records the stack trace at the point it was called. +func New(message string) error { + return &fundamental{ + msg: message, + stack: callers(), + } +} + +// Errorf formats according to a format specifier and returns the string +// as a value that satisfies error. +// Errorf also records the stack trace at the point it was called. +func Errorf(format string, args ...interface{}) error { + return &fundamental{ + msg: fmt.Sprintf(format, args...), + stack: callers(), + } +} + +// fundamental is an error that has a message and a stack, but no caller. +type fundamental struct { + msg string + *stack +} + +func (f *fundamental) Error() string { return f.msg } + +func (f *fundamental) Format(s fmt.State, verb rune) { + switch verb { + case 'v': + if s.Flag('+') { + io.WriteString(s, f.msg) + f.stack.Format(s, verb) + return + } + fallthrough + case 's': + io.WriteString(s, f.msg) + case 'q': + fmt.Fprintf(s, "%q", f.msg) + } +} + +// WithStack annotates err with a stack trace at the point WithStack was called. +// If err is nil, WithStack returns nil. +func WithStack(err error) error { + if err == nil { + return nil + } + return &withStack{ + err, + callers(), + } +} + +type withStack struct { + error + *stack +} + +func (w *withStack) Cause() error { return w.error } + +func (w *withStack) Format(s fmt.State, verb rune) { + switch verb { + case 'v': + if s.Flag('+') { + fmt.Fprintf(s, "%+v", w.Cause()) + w.stack.Format(s, verb) + return + } + fallthrough + case 's': + io.WriteString(s, w.Error()) + case 'q': + fmt.Fprintf(s, "%q", w.Error()) + } +} + +// Wrap returns an error annotating err with a stack trace +// at the point Wrap is called, and the supplied message. +// If err is nil, Wrap returns nil. +func Wrap(err error, message string) error { + if err == nil { + return nil + } + err = &withMessage{ + cause: err, + msg: message, + } + return &withStack{ + err, + callers(), + } +} + +// Wrapf returns an error annotating err with a stack trace +// at the point Wrapf is call, and the format specifier. +// If err is nil, Wrapf returns nil. +func Wrapf(err error, format string, args ...interface{}) error { + if err == nil { + return nil + } + err = &withMessage{ + cause: err, + msg: fmt.Sprintf(format, args...), + } + return &withStack{ + err, + callers(), + } +} + +// WithMessage annotates err with a new message. +// If err is nil, WithMessage returns nil. +func WithMessage(err error, message string) error { + if err == nil { + return nil + } + return &withMessage{ + cause: err, + msg: message, + } +} + +type withMessage struct { + cause error + msg string +} + +func (w *withMessage) Error() string { return w.msg + ": " + w.cause.Error() } +func (w *withMessage) Cause() error { return w.cause } + +func (w *withMessage) Format(s fmt.State, verb rune) { + switch verb { + case 'v': + if s.Flag('+') { + fmt.Fprintf(s, "%+v\n", w.Cause()) + io.WriteString(s, w.msg) + return + } + fallthrough + case 's', 'q': + io.WriteString(s, w.Error()) + } +} + +// Cause returns the underlying cause of the error, if possible. +// An error value has a cause if it implements the following +// interface: +// +// type causer interface { +// Cause() error +// } +// +// If the error does not implement Cause, the original error will +// be returned. If the error is nil, nil will be returned without further +// investigation. +func Cause(err error) error { + type causer interface { + Cause() error + } + + for err != nil { + cause, ok := err.(causer) + if !ok { + break + } + err = cause.Cause() + } + return err +} diff --git a/vendor/github.com/pkg/errors/stack.go b/vendor/github.com/pkg/errors/stack.go new file mode 100644 index 00000000000..6b1f2891a5a --- /dev/null +++ b/vendor/github.com/pkg/errors/stack.go @@ -0,0 +1,178 @@ +package errors + +import ( + "fmt" + "io" + "path" + "runtime" + "strings" +) + +// Frame represents a program counter inside a stack frame. +type Frame uintptr + +// pc returns the program counter for this frame; +// multiple frames may have the same PC value. +func (f Frame) pc() uintptr { return uintptr(f) - 1 } + +// file returns the full path to the file that contains the +// function for this Frame's pc. +func (f Frame) file() string { + fn := runtime.FuncForPC(f.pc()) + if fn == nil { + return "unknown" + } + file, _ := fn.FileLine(f.pc()) + return file +} + +// line returns the line number of source code of the +// function for this Frame's pc. +func (f Frame) line() int { + fn := runtime.FuncForPC(f.pc()) + if fn == nil { + return 0 + } + _, line := fn.FileLine(f.pc()) + return line +} + +// Format formats the frame according to the fmt.Formatter interface. +// +// %s source file +// %d source line +// %n function name +// %v equivalent to %s:%d +// +// Format accepts flags that alter the printing of some verbs, as follows: +// +// %+s path of source file relative to the compile time GOPATH +// %+v equivalent to %+s:%d +func (f Frame) Format(s fmt.State, verb rune) { + switch verb { + case 's': + switch { + case s.Flag('+'): + pc := f.pc() + fn := runtime.FuncForPC(pc) + if fn == nil { + io.WriteString(s, "unknown") + } else { + file, _ := fn.FileLine(pc) + fmt.Fprintf(s, "%s\n\t%s", fn.Name(), file) + } + default: + io.WriteString(s, path.Base(f.file())) + } + case 'd': + fmt.Fprintf(s, "%d", f.line()) + case 'n': + name := runtime.FuncForPC(f.pc()).Name() + io.WriteString(s, funcname(name)) + case 'v': + f.Format(s, 's') + io.WriteString(s, ":") + f.Format(s, 'd') + } +} + +// StackTrace is stack of Frames from innermost (newest) to outermost (oldest). +type StackTrace []Frame + +func (st StackTrace) Format(s fmt.State, verb rune) { + switch verb { + case 'v': + switch { + case s.Flag('+'): + for _, f := range st { + fmt.Fprintf(s, "\n%+v", f) + } + case s.Flag('#'): + fmt.Fprintf(s, "%#v", []Frame(st)) + default: + fmt.Fprintf(s, "%v", []Frame(st)) + } + case 's': + fmt.Fprintf(s, "%s", []Frame(st)) + } +} + +// 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 := Frame(pc) + fmt.Fprintf(st, "\n%+v", f) + } + } + } +} + +func (s *stack) StackTrace() StackTrace { + f := make([]Frame, len(*s)) + for i := 0; i < len(f); i++ { + f[i] = Frame((*s)[i]) + } + return f +} + +func callers() *stack { + const depth = 32 + var pcs [depth]uintptr + n := runtime.Callers(3, pcs[:]) + var st stack = pcs[0:n] + return &st +} + +// funcname removes the path prefix component of a function's name reported by func.Name(). +func funcname(name string) string { + i := strings.LastIndex(name, "/") + name = name[i+1:] + i = strings.Index(name, ".") + return name[i+1:] +} + +func trimGOPATH(name, file string) string { + // Here we want to get the source file path relative to the compile time + // GOPATH. As of Go 1.6.x there is no direct way to know the compiled + // GOPATH at runtime, but we can infer the number of path segments in the + // GOPATH. We note that fn.Name() returns the function name qualified by + // the import path, which does not include the GOPATH. Thus we can trim + // segments from the beginning of the file path until the number of path + // separators remaining is one more than the number of path separators in + // the function name. For example, given: + // + // GOPATH /home/user + // file /home/user/src/pkg/sub/file.go + // fn.Name() pkg/sub.Type.Method + // + // We want to produce: + // + // pkg/sub/file.go + // + // From this we can easily see that fn.Name() has one less path separator + // than our desired output. We count separators from the end of the file + // path until it finds two more than in the function name and then move + // one character forward to preserve the initial path segment without a + // leading separator. + const sep = "/" + goal := strings.Count(name, sep) + 2 + i := len(file) + for n := 0; n < goal; n++ { + i = strings.LastIndex(file[:i], sep) + if i == -1 { + // not enough separators found, set i so that the slice expression + // below leaves file unmodified + i = -len(sep) + break + } + } + // get back to 0 or trim the leading separator + file = file[i+len(sep):] + return file +} From 7a5bbab429810ccf7ac6bb8fb5d7bb637435e774 Mon Sep 17 00:00:00 2001 From: Greg Weber Date: Tue, 14 Aug 2018 17:14:13 -0700 Subject: [PATCH 02/18] integrate with pkg/errors add the Causer interface internal errors record a StackTrace --- pkg/error_code/codes.go | 16 +++- pkg/error_code/error_code.go | 44 ++++++++--- pkg/error_code/error_code_test.go | 54 +++++++++++-- pkg/error_code/operation.go | 8 +- pkg/error_code/stack.go | 107 ++++++++++++++++++++++++++ pkg/error_code/wrapped.go | 122 ++++++++++++++++++++++++++++++ server/api/util.go | 2 +- 7 files changed, 331 insertions(+), 22 deletions(-) create mode 100644 pkg/error_code/stack.go create mode 100644 pkg/error_code/wrapped.go diff --git a/pkg/error_code/codes.go b/pkg/error_code/codes.go index a9ce721365c..a5bbcf9c64c 100644 --- a/pkg/error_code/codes.go +++ b/pkg/error_code/codes.go @@ -39,14 +39,16 @@ func NewInvalidInputErr(err error) ErrorCode { var _ ErrorCode = (*invalidInputErr)(nil) // assert implements interface var _ HasClientData = (*invalidInputErr)(nil) // assert implements interface +var _ Causer = (*invalidInputErr)(nil) // assert implements interface -// internalError gives the code InvalidInputCode -type internalErr struct{ CodedError } +// internalError gives the code InternalCode +type internalErr struct{ StackCode } // NewInternalErr creates an internalError from an err // If the given err is an ErrorCode that is a descendant of InternalCode, // its code will be used. // This ensures the intention of sending an HTTP 50x. +// This function also records a stack trace. func NewInternalErr(err error) ErrorCode { code := InternalCode if errcode, ok := err.(ErrorCode); ok { @@ -55,11 +57,12 @@ func NewInternalErr(err error) ErrorCode { code = errCode } } - return internalErr{CodedError{GetCode: code, Err: err}} + return internalErr{NewStackCode(CodedError{GetCode: code, Err: err}, 2)} } var _ ErrorCode = (*internalErr)(nil) // assert implements interface var _ HasClientData = (*internalErr)(nil) // assert implements interface +var _ Causer = (*internalErr)(nil) // assert implements interface // notFound gives the code NotFoundCode type notFoundErr struct{ CodedError } @@ -73,6 +76,7 @@ func NewNotFoundErr(err error) ErrorCode { var _ ErrorCode = (*notFoundErr)(nil) // assert implements interface var _ HasClientData = (*notFoundErr)(nil) // assert implements interface +var _ Causer = (*notFoundErr)(nil) // assert implements interface // CodedError is a convenience to attach a code to an error and already satisfy the ErrorCode interface. // If the error is a struct, that struct will get preseneted as data to the client. @@ -101,11 +105,17 @@ func NewCodedError(err error, code Code) CodedError { var _ ErrorCode = (*CodedError)(nil) // assert implements interface var _ HasClientData = (*CodedError)(nil) // assert implements interface +var _ Causer = (*CodedError)(nil) // assert implements interface func (e CodedError) Error() string { return e.Err.Error() } +// Cause satisfies the Causer interface. +func (e CodedError) Cause() error { + return e.Err +} + // Code returns the GetCode field func (e CodedError) Code() Code { return e.GetCode diff --git a/pkg/error_code/error_code.go b/pkg/error_code/error_code.go index 76df51d73f7..c2dd3d1fd21 100644 --- a/pkg/error_code/error_code.go +++ b/pkg/error_code/error_code.go @@ -21,7 +21,7 @@ // This package is designed to have few opinions and be a starting point for how you want to do errors in your project. // The main requirement is to satisfy the ErrorCode interface by attaching a Code to an Error. // See the documentation of ErrorCode. -// Additional optional interfaces HasClientData and HasOperation are provided for extensibility +// Additional optional interfaces HasClientData, HasOperation, Causer, and StackTracer are provided for extensibility // in creating structured error data representations. // // Hierarchies are supported: a Code can point to a parent. @@ -31,16 +31,20 @@ // A few generic top-level error codes are provided here. // You are encouraged to create your own application customized error codes rather than just using generic errors. // -// See JSONFormat for an opinion on how to send back meta data about errors with the error data to a client. +// See NewJSONFormat for an opinion on how to send back meta data about errors with the error data to a client. // JSONFormat includes a body of response data (the "data field") that is by default the data from the Error // serialized to JSON. -// This package provides no help on versioning error data. +// +// Errors are traced via PreviousErrorCode() which shows up as the Previous field in JSONFormat. +// Stack traces are automatically added by NewInternalErr and show up as the Stack field in JSONFormat. package errcode import ( "fmt" "net/http" "strings" + + "github.com/pkg/errors" ) // CodeStr is a representation of the type of a particular error. @@ -163,7 +167,7 @@ func (code Code) HTTPCode() int { // For an application specific error with a 1:1 mapping between a go error structure and a RegisteredCode, // You probably want to use this interface directly. Example: // -// // First define a normal error type +// // First define a normal error type // type PathBlocked struct { // start uint64 `json:"start"` // end uint64 `json:"end"` @@ -177,7 +181,7 @@ func (code Code) HTTPCode() int { // // Now define the code // var PathBlockedCode = errcode.StateCode.Child("state.blocked") // -// // Now attach the code to the error type +// // Now attach the code to the error type // func (e PathBlocked) Code() Code { // return PathBlockedCode // } @@ -206,13 +210,22 @@ func ClientData(errCode ErrorCode) interface{} { } // JSONFormat is an opinion on how to serialize an ErrorCode to JSON. -// Msg is the string from Error(). -// The Data field is filled in by GetClientData +// * Code is the error code string (CodeStr) +// * Msg is the string from Error() and should be friendly to end users. +// * Data is the ad-hoc data filled in by GetClientData and should be consumable by clients. +// * Operation is the high-level operation that was happening at the time of the error. +// The Operation field may be missing, and the Data field may be empty. +// +// The rest of the fields may be populated sparsely depending on the application: +// * Previous gives JSONFormat data for an ErrorCode that was wrapped by this one. It relies on the PreviousErrorCode function. +// * Stack is a stack trace. Usually only internal errors populate this. type JSONFormat struct { - Data interface{} `json:"data"` - Msg string `json:"msg"` - Code CodeStr `json:"code"` - Operation string `json:"operation,omitempty"` + Code CodeStr `json:"code"` + Msg string `json:"msg"` + Data interface{} `json:"data"` + Operation string `json:"operation,omitempty"` + Previous *JSONFormat `json:"previous,omitempty"` + Stack errors.StackTrace `json:"stack,omitempty"` } // OperationClientData gives the results of both the ClientData and Operation functions. @@ -231,11 +244,20 @@ func OperationClientData(errCode ErrorCode) (string, interface{}) { // NewJSONFormat turns an ErrorCode into a JSONFormat func NewJSONFormat(errCode ErrorCode) JSONFormat { op, data := OperationClientData(errCode) + + var previous *JSONFormat + if prevCode := PreviousErrorCode(errCode); prevCode != nil { + ptrVar := NewJSONFormat(prevCode) + previous = &ptrVar + } + return JSONFormat{ Data: data, Msg: errCode.Error(), Code: errCode.Code().CodeStr(), Operation: op, + Stack: StackTrace(errCode), + Previous: previous, } } diff --git a/pkg/error_code/error_code_test.go b/pkg/error_code/error_code_test.go index 7ea62dc03ed..ae45e6374a3 100644 --- a/pkg/error_code/error_code_test.go +++ b/pkg/error_code/error_code_test.go @@ -14,12 +14,13 @@ package errcode_test import ( - "errors" + "encoding/json" "fmt" "reflect" "testing" "github.com/pingcap/pd/pkg/error_code" + "github.com/pkg/errors" ) // Test setting the HTTP code @@ -202,6 +203,14 @@ func TestNewInvalidInputErr(t *testing.T) { ErrorEquals(t, err, "error") ClientDataEquals(t, err, MinimalError{}, internalCodeStr) + wrappedInternalErr := errcode.NewInternalErr(internalErr) + AssertCode(t, err, internalCodeStr) + AssertHTTPCode(t, err, 500) + ErrorEquals(t, err, "error") + ClientDataEquals(t, wrappedInternalErr, MinimalError{}, internalCodeStr) + // It should use the original stack trace, not the wrapped + AssertStackEquals(t, wrappedInternalErr, errcode.StackTrace(internalErr)) + err = errcode.NewInvalidInputErr(InternalChild{}) AssertCode(t, err, internalChildCodeStr) AssertHTTPCode(t, err, 503) @@ -209,6 +218,18 @@ func TestNewInvalidInputErr(t *testing.T) { ClientDataEquals(t, err, InternalChild{}, internalChildCodeStr) } +func TestStackTrace(t *testing.T) { + internalCodeStr := errcode.CodeStr("internal") + err := errors.New("errors stack") + wrappedInternalErr := errcode.NewInternalErr(err) + AssertCode(t, wrappedInternalErr, internalCodeStr) + AssertHTTPCode(t, wrappedInternalErr, 500) + ErrorEquals(t, err, "errors stack") + ClientDataEquals(t, wrappedInternalErr, err, internalCodeStr) + // It should use the original stack trace, not the wrapped + AssertStackEquals(t, wrappedInternalErr, errcode.StackTrace(err)) +} + func TestNewInternalErr(t *testing.T) { internalCodeStr := errcode.CodeStr("internal") err := errcode.NewInternalErr(errors.New("new error")) @@ -314,17 +335,30 @@ func ClientDataEquals(t *testing.T, code errcode.ErrorCode, data interface{}, co codeStr = codeStrs[0] } t.Helper() - if !reflect.DeepEqual(errcode.ClientData(code), data) { - t.Errorf("\nClientData expected: %#v\n ClientData but got: %#v", data, errcode.ClientData(code)) - } + + jsonEquals(t, "ClientData", data, errcode.ClientData(code)) + jsonExpected := errcode.JSONFormat{ Data: data, Msg: code.Error(), Code: codeStr, Operation: errcode.Operation(data), + Stack: errcode.StackTrace(code), + } + newJSON := errcode.NewJSONFormat(code) + newJSON.Previous = nil + jsonEquals(t, "JSONFormat", jsonExpected, newJSON) +} + +func jsonEquals(t *testing.T, errPrefix string, expectedIn interface{}, gotIn interface{}) { + t.Helper() + got, err1 := json.Marshal(gotIn) + expected, err2 := json.Marshal(expectedIn) + if err1 != nil || err2 != nil { + t.Errorf("%v could not serialize to json", errPrefix) } - if !reflect.DeepEqual(errcode.NewJSONFormat(code), jsonExpected) { - t.Errorf("\nJSON expected: %+v\n JSON but got: %+v", jsonExpected, errcode.NewJSONFormat(code)) + if !reflect.DeepEqual(expected, got) { + t.Errorf("%v\nClientData expected: %#v\n ClientData but got: %#v", errPrefix, expected, got) } } @@ -343,3 +377,11 @@ func AssertOperation(t *testing.T, v interface{}, op string) { t.Errorf("\nOp expected: %#v\n Op but got: %#v", op, opGot) } } + +func AssertStackEquals(t *testing.T, given errcode.ErrorCode, stExpected errors.StackTrace) { + t.Helper() + stGiven := errcode.StackTrace(given) + if stGiven == nil || stExpected == nil || stGiven[0] != stExpected[0] { + t.Errorf("\nStack expected: %#v\n Stack but got: %#v", stExpected[0], stGiven[0]) + } +} diff --git a/pkg/error_code/operation.go b/pkg/error_code/operation.go index 7e796d2ef25..14a119bdc20 100644 --- a/pkg/error_code/operation.go +++ b/pkg/error_code/operation.go @@ -56,6 +56,11 @@ type OpErrCode struct { Err ErrorCode } +// Cause satisfies the Causer interface +func (e OpErrCode) Cause() error { + return e.Err +} + // Error prefixes the operation to the underlying Err Error. func (e OpErrCode) Error() string { return e.Operation + ": " + e.Err.Error() @@ -79,6 +84,7 @@ func (e OpErrCode) GetClientData() interface{} { var _ ErrorCode = (*OpErrCode)(nil) // assert implements interface var _ HasClientData = (*OpErrCode)(nil) // assert implements interface var _ HasOperation = (*OpErrCode)(nil) // assert implements interface +var _ Causer = (*OpErrCode)(nil) // assert implements interface // AddOp is constructed by Op. It allows method chaining with AddTo. type AddOp func(ErrorCode) OpErrCode @@ -94,7 +100,7 @@ func (addOp AddOp) AddTo(err ErrorCode) OpErrCode { // op := errcode.Op("path.move.x") // if start < obstable && obstacle < end { // return op.AddTo(PathBlocked{start, end, obstacle}) -// } +// } // func Op(operation string) AddOp { return func(err ErrorCode) OpErrCode { diff --git a/pkg/error_code/stack.go b/pkg/error_code/stack.go new file mode 100644 index 00000000000..28df4f2f801 --- /dev/null +++ b/pkg/error_code/stack.go @@ -0,0 +1,107 @@ +// Copyright 2018 PingCAP, Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// See the License for the specific language governing permissions and +// limitations under the License. + +package errcode + +import ( + "github.com/pkg/errors" +) + +// StackTracer is the interface defined but not exported from pkg/errors +// The StackTrace() function (not method) is a preferred way to access the StackTrace +// +// Generally you should only bother with stack traces for internal errors. +type StackTracer interface { + StackTrace() errors.StackTrace +} + +// StackTrace retrieves the errors.StackTrace from the error if it is present. +// If there is not StackTrace it will return nil +// +// StackTrace looks to see if the error is a StackTracer or if a Causer of the error is a StackTracer. +func StackTrace(err error) errors.StackTrace { + if stackTraceErr, ok := err.(StackTracer); ok { + return stackTraceErr.StackTrace() + } + if prev := WrappedError(err); prev != nil { + return StackTrace(prev) + } + return nil +} + +// StackCode is an ErrorCode with stack trace information attached. +// This may be used as a convenience to record the strack trace information for the error. +// Generally stack traces aren't needed for user errors, but they are provided by NewInternalErr. +// Its also possible to define your own structures that satisfy the StackTracer interface. +type StackCode struct { + Err ErrorCode + GetStackTrace errors.StackTrace +} + +// StackTrace fulfills the StackTracer interface +func (e StackCode) StackTrace() errors.StackTrace { + return e.GetStackTrace +} + +// NewStackCode constrcuts a StackCode, which is an ErrorCode with stack trace information +// The second variable is an optional stack position gets rid of information about function calls to construct the stack trace. +// It is defaulted to 1 to remove this function call. +// +// NewStackCode first looks at the underlying error via WrappedError to see if it already has a StackTrace. +// If so, that StackTrace is used. +func NewStackCode(err ErrorCode, position ...int) StackCode { + stackPosition := 1 + if len(position) > 0 { + stackPosition = position[0] + } + + // if there is an existing trace, take that: it should be deeper + var prev error = err + for prev != nil { + if stackTraceErr, ok := prev.(StackTracer); ok { + return StackCode{Err: err, GetStackTrace: stackTraceErr.StackTrace()} + } + prev = WrappedError(prev) + } + + // we must go through some contortions to get a stack trace from pkg/errors + stackedErr := errors.WithStack(err) + if stackTraceErr, ok := stackedErr.(StackTracer); ok { + return StackCode{Err: err, GetStackTrace: stackTraceErr.StackTrace()[stackPosition:]} + } + panic("NewStackCode: pkg/errors WithStack StackTrace interface changed") +} + +// Cause satisfies the Causer interface +func (e StackCode) Cause() error { + return e.Err +} + +// Error ignores the stack and gives the underlying Err Error. +func (e StackCode) Error() string { + return e.Err.Error() +} + +// Code returns the unerlying Code of Err. +func (e StackCode) Code() Code { + return e.Err.Code() +} + +// GetClientData returns the ClientData of the underlying Err. +func (e StackCode) GetClientData() interface{} { + return ClientData(e.Err) +} + +var _ ErrorCode = (*StackCode)(nil) // assert implements interface +var _ HasClientData = (*StackCode)(nil) // assert implements interface +var _ Causer = (*StackCode)(nil) // assert implements interface diff --git a/pkg/error_code/wrapped.go b/pkg/error_code/wrapped.go new file mode 100644 index 00000000000..5ac4d195a53 --- /dev/null +++ b/pkg/error_code/wrapped.go @@ -0,0 +1,122 @@ +// Copyright 2018 PingCAP, Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// See the License for the specific language governing permissions and +// limitations under the License. + +package errcode + +// Causer allows the abstract retrieval of the underlying error. +// This is the interface that pkg/errors does not export but is considered part of the stable public API. +// +// Types that wrap errors should implement this to allow viewing of the underlying error. +// Generally you would use this via pkg/errors.Cause or WrappedError. +// PreviousErrorCode and StackTrace use Causer to check if the underlying error is an ErrorCode or a StackTracer. +type Causer interface { + Cause() error +} + +// WrappedError retrieves the wrapped error via Causer. +// Unlike pkg/errors.Cause, it goes just one level deep and does not recursively traverse. +// Return nil is there is no wrapped error. +func WrappedError(err error) error { + if hasCause, ok := err.(Causer); ok { + return hasCause.Cause() + } + return nil +} + +// EmbedErr is designed to be embedded into your existing error structs. +// It provides the Causer interface already, which can reduce your boilerplate. +type EmbedErr struct { + Err error +} + +// Cause implements the Causer interface +func (e EmbedErr) Cause() error { + return e.Err +} + +var _ Causer = (*EmbedErr)(nil) // assert implements interface + +// PreviousErrorCode looks for a previous ErrorCode that has a different code. +// This helps construct a trace of all previous errors. +// It will return nil if no previous ErrorCode is found. +// +// To look for a previous ErrorCode it looks at Causer to see if they are an ErrorCode. +// Wrappers of errors like OpErrCode and CodedError implement Causer. +func PreviousErrorCode(err ErrorCode) ErrorCode { + return previousErrorCodeCompare(err.Code(), err) +} + +func previousErrorCodeCompare(code Code, err error) ErrorCode { + prev := WrappedError(err) + if prev == nil { + return nil + } + if errcode, ok := prev.(ErrorCode); ok { + if errcode.Code() != code { + return errcode + } + } + return previousErrorCodeCompare(code, prev) +} + +// GetErrorCode tries to convert an error to an ErrorCode. +// If the error is not an ErrorCode, +// it looks for the first ErrorCode it can find via Causer. +// In that case it will retain the error message of the original error by returning a WrappedErrorCode. +func GetErrorCode(err error) ErrorCode { + if errcode, ok := err.(ErrorCode); ok { + return errcode + } + prev := WrappedError(err) + for prev != nil { + if errcode, ok := prev.(ErrorCode); ok { + return WrappedErrorCode{errcode, err} + } + prev = WrappedError(err) + } + return nil +} + +// WrappedErrorCode is returned by GetErrorCode to retain the full wrapped error message +type WrappedErrorCode struct { + ErrCode ErrorCode + Top error +} + +// Code satisfies the ErrorCode interface +func (err WrappedErrorCode) Code() Code { + return err.ErrCode.Code() +} + +// Error satisfies the Error interface +func (err WrappedErrorCode) Error() string { + return err.Top.Error() +} + +// Cause satisfies the Causer interface +func (err WrappedErrorCode) Cause() error { + if wrapped := WrappedError(err.Top); wrapped != nil { + return wrapped + } + // There should be a wrapped error and this should not be reached + return err.ErrCode +} + +// GetClientData satisfies the HasClientData interface +func (err WrappedErrorCode) GetClientData() interface{} { + return ClientData(err.ErrCode) +} + +var _ ErrorCode = (*WrappedErrorCode)(nil) +var _ HasClientData = (*WrappedErrorCode)(nil) +var _ Causer = (*WrappedErrorCode)(nil) diff --git a/server/api/util.go b/server/api/util.go index 7e10134c44a..644cba2279c 100644 --- a/server/api/util.go +++ b/server/api/util.go @@ -40,7 +40,7 @@ func errorResp(rd *render.Render, w http.ResponseWriter, err error) { rd.JSON(w, http.StatusInternalServerError, "nil error") return } - if errCode, ok := errors.Cause(err).(errcode.ErrorCode); ok { + if errCode := errcode.GetErrorCode(err); errCode != nil { w.Header().Set("TiDB-Error-Code", errCode.Code().CodeStr().String()) rd.JSON(w, errCode.Code().HTTPCode(), errcode.NewJSONFormat(errCode)) } else { From 8a0ced9e45d35c742f90a25ec5c8b4ab2ed291f1 Mon Sep 17 00:00:00 2001 From: Greg Weber Date: Mon, 27 Aug 2018 07:11:17 -0700 Subject: [PATCH 03/18] fix comment spellings --- pkg/error_code/operation.go | 2 +- pkg/error_code/stack.go | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/pkg/error_code/operation.go b/pkg/error_code/operation.go index 14a119bdc20..3c25d9dd2b3 100644 --- a/pkg/error_code/operation.go +++ b/pkg/error_code/operation.go @@ -71,7 +71,7 @@ func (e OpErrCode) GetOperation() string { return e.Operation } -// Code returns the unerlying Code of Err. +// Code returns the underlying Code of Err. func (e OpErrCode) Code() Code { return e.Err.Code() } diff --git a/pkg/error_code/stack.go b/pkg/error_code/stack.go index 28df4f2f801..423d79080ce 100644 --- a/pkg/error_code/stack.go +++ b/pkg/error_code/stack.go @@ -53,7 +53,7 @@ func (e StackCode) StackTrace() errors.StackTrace { return e.GetStackTrace } -// NewStackCode constrcuts a StackCode, which is an ErrorCode with stack trace information +// NewStackCode constructs a StackCode, which is an ErrorCode with stack trace information // The second variable is an optional stack position gets rid of information about function calls to construct the stack trace. // It is defaulted to 1 to remove this function call. // @@ -92,7 +92,7 @@ func (e StackCode) Error() string { return e.Err.Error() } -// Code returns the unerlying Code of Err. +// Code returns the underlying Code of Err. func (e StackCode) Code() Code { return e.Err.Code() } From c3c9a11fd8f9fe28bbff441c57baae1823430f3d Mon Sep 17 00:00:00 2001 From: Greg Weber Date: Mon, 27 Aug 2018 14:37:51 -0700 Subject: [PATCH 04/18] search for the deepest available stack trace --- pkg/error_code/stack.go | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/pkg/error_code/stack.go b/pkg/error_code/stack.go index 423d79080ce..653da23f9c0 100644 --- a/pkg/error_code/stack.go +++ b/pkg/error_code/stack.go @@ -29,13 +29,16 @@ type StackTracer interface { // If there is not StackTrace it will return nil // // StackTrace looks to see if the error is a StackTracer or if a Causer of the error is a StackTracer. +// It will return the stack trace from the deepest error it can find. func StackTrace(err error) errors.StackTrace { + if prev := WrappedError(err); prev != nil { + if trace := StackTrace(prev); trace != nil { + return trace + } + } if stackTraceErr, ok := err.(StackTracer); ok { return stackTraceErr.StackTrace() } - if prev := WrappedError(err); prev != nil { - return StackTrace(prev) - } return nil } From c4a46a36a16fe07aa1b502ad64630b4a1adf906a Mon Sep 17 00:00:00 2001 From: Greg Weber Date: Tue, 28 Aug 2018 09:24:01 -0700 Subject: [PATCH 05/18] re-use the existing StackTrace function --- pkg/error_code/stack.go | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/pkg/error_code/stack.go b/pkg/error_code/stack.go index 653da23f9c0..a75f10797bc 100644 --- a/pkg/error_code/stack.go +++ b/pkg/error_code/stack.go @@ -69,12 +69,8 @@ func NewStackCode(err ErrorCode, position ...int) StackCode { } // if there is an existing trace, take that: it should be deeper - var prev error = err - for prev != nil { - if stackTraceErr, ok := prev.(StackTracer); ok { - return StackCode{Err: err, GetStackTrace: stackTraceErr.StackTrace()} - } - prev = WrappedError(prev) + if trace := StackTrace(err); trace != nil { + return StackCode{Err: err, GetStackTrace: trace} } // we must go through some contortions to get a stack trace from pkg/errors From 3c88460e93fffed040dbf60165c6b3a02a123103 Mon Sep 17 00:00:00 2001 From: Greg Weber Date: Tue, 28 Aug 2018 09:24:47 -0700 Subject: [PATCH 06/18] only internal errors should show a stack trace To some extent this masks an issue with how pkg/errors is not used --- pkg/error_code/error_code.go | 7 ++++++- pkg/error_code/error_code_test.go | 6 +++++- 2 files changed, 11 insertions(+), 2 deletions(-) diff --git a/pkg/error_code/error_code.go b/pkg/error_code/error_code.go index c2dd3d1fd21..8ed843f43fd 100644 --- a/pkg/error_code/error_code.go +++ b/pkg/error_code/error_code.go @@ -251,12 +251,17 @@ func NewJSONFormat(errCode ErrorCode) JSONFormat { previous = &ptrVar } + var stack errors.StackTrace + if errCode.Code().IsAncestor(InternalCode) { + stack = StackTrace(errCode) + } + return JSONFormat{ Data: data, Msg: errCode.Error(), Code: errCode.Code().CodeStr(), Operation: op, - Stack: StackTrace(errCode), + Stack: stack, Previous: previous, } } diff --git a/pkg/error_code/error_code_test.go b/pkg/error_code/error_code_test.go index ae45e6374a3..ac5fd7422f7 100644 --- a/pkg/error_code/error_code_test.go +++ b/pkg/error_code/error_code_test.go @@ -331,8 +331,12 @@ func ErrorEquals(t *testing.T, err error, msg string) { func ClientDataEquals(t *testing.T, code errcode.ErrorCode, data interface{}, codeStrs ...errcode.CodeStr) { codeStr := codeString + var stack errors.StackTrace if len(codeStrs) > 0 { codeStr = codeStrs[0] + if code.Code().IsAncestor(errcode.InternalCode) { + stack = errcode.StackTrace(code) + } } t.Helper() @@ -343,7 +347,7 @@ func ClientDataEquals(t *testing.T, code errcode.ErrorCode, data interface{}, co Msg: code.Error(), Code: codeStr, Operation: errcode.Operation(data), - Stack: errcode.StackTrace(code), + Stack: stack, } newJSON := errcode.NewJSONFormat(code) newJSON.Previous = nil From acb0499dd38b6e710557d3326f246e879168fa60 Mon Sep 17 00:00:00 2001 From: Greg Weber Date: Tue, 28 Aug 2018 09:46:37 -0700 Subject: [PATCH 07/18] update documentation --- pkg/error_code/error_code.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pkg/error_code/error_code.go b/pkg/error_code/error_code.go index 8ed843f43fd..6d212d315b9 100644 --- a/pkg/error_code/error_code.go +++ b/pkg/error_code/error_code.go @@ -218,7 +218,7 @@ func ClientData(errCode ErrorCode) interface{} { // // The rest of the fields may be populated sparsely depending on the application: // * Previous gives JSONFormat data for an ErrorCode that was wrapped by this one. It relies on the PreviousErrorCode function. -// * Stack is a stack trace. Usually only internal errors populate this. +// * Stack is a stack trace. This is only given for internal errors. type JSONFormat struct { Code CodeStr `json:"code"` Msg string `json:"msg"` From 35dc1b080597f77dff4e572a69ac359fdb8fbf57 Mon Sep 17 00:00:00 2001 From: Greg Weber Date: Sat, 8 Sep 2018 07:05:35 -0700 Subject: [PATCH 08/18] Upgrade pkg/errors to latest pingcap/errors --- Gopkg.lock | 7 +- Gopkg.toml | 3 +- vendor/github.com/pkg/errors/errors.go | 150 ++++++++++++++----- vendor/github.com/pkg/errors/group.go | 33 ++++ vendor/github.com/pkg/errors/juju_adaptor.go | 76 ++++++++++ vendor/github.com/pkg/errors/stack.go | 72 ++++----- 6 files changed, 256 insertions(+), 85 deletions(-) create mode 100644 vendor/github.com/pkg/errors/group.go create mode 100644 vendor/github.com/pkg/errors/juju_adaptor.go diff --git a/Gopkg.lock b/Gopkg.lock index 99d5f6799b2..e4dfd892209 100644 --- a/Gopkg.lock +++ b/Gopkg.lock @@ -326,12 +326,13 @@ revision = "279515615485b0f2d12f1421cc412fe2784e0190" [[projects]] + digest = "1:91fee14a873676b2118c54e08c2e04897ea5c141b55a9b9fb8cf0721458d0d85" name = "github.com/pkg/errors" - digest = "1:5cf3f025cbee5951a4ee961de067c8a89fc95a5adabead774f82822efabab121" packages = ["."] pruneopts = "NUT" - revision = "645ef00459ed84a119197bfb8d8205042c6df63d" - version = "v0.8.0" + revision = "9316aeb006f59424c65ff505c217f90c43d6445d" + source = "https://github.com/pingcap/errors" + version = "v0.9.0" [[projects]] digest = "1:8d8f554bbb62fb7aecf661b85b25e227f6ab6cfe2b4395ea65ef478bfc174940" diff --git a/Gopkg.toml b/Gopkg.toml index d8fb33a4bba..5e061473774 100644 --- a/Gopkg.toml +++ b/Gopkg.toml @@ -38,7 +38,8 @@ [[constraint]] name = "github.com/pkg/errors" - version = "0.8.0" + version = ">= 0.9.0" + source = "https://github.com/pingcap/errors" [[override]] name = "github.com/BurntSushi/toml" diff --git a/vendor/github.com/pkg/errors/errors.go b/vendor/github.com/pkg/errors/errors.go index 842ee80456d..0168061fd52 100644 --- a/vendor/github.com/pkg/errors/errors.go +++ b/vendor/github.com/pkg/errors/errors.go @@ -13,24 +13,24 @@ // // Adding context to an error // -// The errors.Wrap function returns a new error that adds context to the -// original error by recording a stack trace at the point Wrap is called, +// The errors.Annotate function returns a new error that adds context to the +// original error by recording a stack trace at the point Annotate is called, // and the supplied message. For example // // _, err := ioutil.ReadAll(r) // if err != nil { -// return errors.Wrap(err, "read failed") +// return errors.Annotate(err, "read failed") // } // -// If additional control is required the errors.WithStack and errors.WithMessage -// functions destructure errors.Wrap into its component operations of annotating +// If additional control is required the errors.AddStack and errors.WithMessage +// functions destructure errors.Annotate into its component operations of annotating // an error with a stack trace and an a message, respectively. // // Retrieving the cause of an error // -// Using errors.Wrap constructs a stack of errors, adding context to the +// Using errors.Annotate constructs a stack of errors, adding context to the // preceding error. Depending on the nature of the error it may be necessary -// to reverse the operation of errors.Wrap to retrieve the original error +// to reverse the operation of errors.Annotate to retrieve the original error // for inspection. Any error value which implements this interface // // type causer interface { @@ -50,6 +50,7 @@ // // causer interface is not exported by this package, but is considered a part // of stable public API. +// errors.Unwrap is also available: this will retrieve the next error in the chain. // // Formatted printing of errors // @@ -64,14 +65,9 @@ // // Retrieving the stack trace of an error or wrapper // -// New, Errorf, Wrap, and Wrapf record a stack trace at the point they are -// invoked. This information can be retrieved with the following interface. -// -// type stackTracer interface { -// StackTrace() errors.StackTrace -// } -// -// Where errors.StackTrace is defined as +// New, Errorf, Annotate, and Annotatef record a stack trace at the point they are invoked. +// This information can be retrieved with the StackTracer interface that returns +// a StackTrace. Where errors.StackTrace is defined as // // type StackTrace []Frame // @@ -79,16 +75,15 @@ // the fmt.Formatter interface that can be used for printing information about // the stack trace of this error. For example: // -// if err, ok := err.(stackTracer); ok { -// for _, f := range err.StackTrace() { +// if stacked := errors.GetStackTracer(err); stacked != nil { +// for _, f := range stacked.StackTrace() { // fmt.Printf("%+s:%d", f) // } // } // -// stackTracer interface is not exported by this package, but is considered a part -// of stable public API. -// // See the documentation for Frame.Format for more details. +// +// errors.Find can be used to search for an error in the error chain. package errors import ( @@ -115,6 +110,21 @@ func Errorf(format string, args ...interface{}) error { } } +// StackTraceAware is an optimization to avoid repetitive traversals of an error chain. +// HasStack checks for this marker first. +// Annotate/Wrap and Annotatef/Wrapf will produce this marker. +type StackTraceAware interface { + HasStack() bool +} + +// HasStack tells whether a StackTracer exists in the error chain +func HasStack(err error) bool { + if errWithStack, ok := err.(StackTraceAware); ok { + return errWithStack.HasStack() + } + return GetStackTracer(err) != nil +} + // fundamental is an error that has a message and a stack, but no caller. type fundamental struct { msg string @@ -141,16 +151,44 @@ func (f *fundamental) Format(s fmt.State, verb rune) { // WithStack annotates err with a stack trace at the point WithStack was called. // If err is nil, WithStack returns nil. +// +// Deprecated: use AddStack func WithStack(err error) error { if err == nil { return nil } + return &withStack{ err, callers(), } } +// AddStack is similar to WithStack. +// However, it will first check with HasStack to see if a stack trace already exists in the causer chain before creating another one. +func AddStack(err error) error { + if HasStack(err) { + return err + } + return WithStack(err) +} + +// GetStackTracer will return the first StackTracer in the causer chain. +// This function is used by AddStack to avoid creating redundant stack traces. +// +// You can also use the StackTracer interface on the returned error to get the stack trace. +func GetStackTracer(origErr error) StackTracer { + var stacked StackTracer + WalkDeep(origErr, func(err error) bool { + if stackTracer, ok := err.(StackTracer); ok { + stacked = stackTracer + return true + } + return false + }) + return stacked +} + type withStack struct { error *stack @@ -175,15 +213,19 @@ func (w *withStack) Format(s fmt.State, verb rune) { } // Wrap returns an error annotating err with a stack trace -// at the point Wrap is called, and the supplied message. -// If err is nil, Wrap returns nil. +// at the point Annotate is called, and the supplied message. +// If err is nil, Annotate returns nil. +// +// Deprecated: use Annotate instead func Wrap(err error, message string) error { if err == nil { return nil } + hasStack := HasStack(err) err = &withMessage{ - cause: err, - msg: message, + cause: err, + msg: message, + causeHasStack: hasStack, } return &withStack{ err, @@ -192,15 +234,19 @@ func Wrap(err error, message string) error { } // Wrapf returns an error annotating err with a stack trace -// at the point Wrapf is call, and the format specifier. -// If err is nil, Wrapf returns nil. +// at the point Annotatef is call, and the format specifier. +// If err is nil, Annotatef returns nil. +// +// Deprecated: use Annotatef instead func Wrapf(err error, format string, args ...interface{}) error { if err == nil { return nil } + hasStack := HasStack(err) err = &withMessage{ - cause: err, - msg: fmt.Sprintf(format, args...), + cause: err, + msg: fmt.Sprintf(format, args...), + causeHasStack: hasStack, } return &withStack{ err, @@ -215,18 +261,21 @@ func WithMessage(err error, message string) error { return nil } return &withMessage{ - cause: err, - msg: message, + cause: err, + msg: message, + causeHasStack: HasStack(err), } } type withMessage struct { - cause error - msg string + cause error + msg string + causeHasStack bool } -func (w *withMessage) Error() string { return w.msg + ": " + w.cause.Error() } -func (w *withMessage) Cause() error { return w.cause } +func (w *withMessage) Error() string { return w.msg + ": " + w.cause.Error() } +func (w *withMessage) Cause() error { return w.cause } +func (w *withMessage) HasStack() bool { return w.causeHasStack } func (w *withMessage) Format(s fmt.State, verb rune) { switch verb { @@ -254,16 +303,35 @@ func (w *withMessage) Format(s fmt.State, verb rune) { // be returned. If the error is nil, nil will be returned without further // investigation. func Cause(err error) error { + cause := Unwrap(err) + if cause == nil { + return err + } + return Cause(cause) +} + +// Unwrap uses causer to return the next error in the chain or nil. +// This goes one-level deeper, whereas Cause goes as far as possible +func Unwrap(err error) error { type causer interface { Cause() error } + if unErr, ok := err.(causer); ok { + return unErr.Cause() + } + return nil +} - for err != nil { - cause, ok := err.(causer) - if !ok { - break +// Find an error in the chain that matches a test function. +// returns nil if no error is found. +func Find(origErr error, test func(error) bool) error { + var foundErr error + WalkDeep(origErr, func(err error) bool { + if test(err) { + foundErr = err + return true } - err = cause.Cause() - } - return err + return false + }) + return foundErr } diff --git a/vendor/github.com/pkg/errors/group.go b/vendor/github.com/pkg/errors/group.go new file mode 100644 index 00000000000..003932c95e8 --- /dev/null +++ b/vendor/github.com/pkg/errors/group.go @@ -0,0 +1,33 @@ +package errors + +// ErrorGroup is an interface for multiple errors that are not a chain. +// This happens for example when executing multiple operations in parallel. +type ErrorGroup interface { + Errors() []error +} + +// WalkDeep does a depth-first traversal of all errors. +// Any ErrorGroup is traversed (after going deep). +// The visitor function can return true to end the traversal early +// In that case, WalkDeep will return true, otherwise false. +func WalkDeep(err error, visitor func(err error) bool) bool { + // Go deep + unErr := err + for unErr != nil { + if done := visitor(unErr); done { + return true + } + unErr = Unwrap(unErr) + } + + // Go wide + if group, ok := err.(ErrorGroup); ok { + for _, err := range group.Errors() { + if early := WalkDeep(err, visitor); early { + return true + } + } + } + + return false +} diff --git a/vendor/github.com/pkg/errors/juju_adaptor.go b/vendor/github.com/pkg/errors/juju_adaptor.go new file mode 100644 index 00000000000..773a1970866 --- /dev/null +++ b/vendor/github.com/pkg/errors/juju_adaptor.go @@ -0,0 +1,76 @@ +package errors + +import ( + "fmt" +) + +// ==================== juju adaptor start ======================== + +// Trace annotates err with a stack trace at the point WithStack was called. +// If err is nil or already contain stack trace return directly. +func Trace(err error) error { + return AddStack(err) +} + +func Annotate(err error, message string) error { + if err == nil { + return nil + } + hasStack := HasStack(err) + err = &withMessage{ + cause: err, + msg: message, + causeHasStack: hasStack, + } + if hasStack { + return err + } + return &withStack{ + err, + callers(), + } +} + +func Annotatef(err error, format string, args ...interface{}) error { + if err == nil { + return nil + } + hasStack := HasStack(err) + err = &withMessage{ + cause: err, + msg: fmt.Sprintf(format, args...), + causeHasStack: hasStack, + } + if hasStack { + return err + } + return &withStack{ + err, + callers(), + } +} + +// ErrorStack will format a stack trace if it is available, otherwise it will be Error() +func ErrorStack(err error) string { + if err == nil { + return "" + } + return fmt.Sprintf("%+v", err) +} + +// NotFoundf represents an error with not found message. +func NotFoundf(format string, args ...interface{}) error { + return Errorf(format+" not found", args...) +} + +// BadRequestf represents an error with bad request message. +func BadRequestf(format string, args ...interface{}) error { + return Errorf(format+" bad request", args...) +} + +// NotSupportedf represents an error with not supported message. +func NotSupportedf(format string, args ...interface{}) error { + return Errorf(format+" not supported", args...) +} + +// ==================== juju adaptor end ======================== diff --git a/vendor/github.com/pkg/errors/stack.go b/vendor/github.com/pkg/errors/stack.go index 6b1f2891a5a..6edd7e5699f 100644 --- a/vendor/github.com/pkg/errors/stack.go +++ b/vendor/github.com/pkg/errors/stack.go @@ -8,6 +8,12 @@ import ( "strings" ) +// StackTracer retrieves the StackTrace +// Generally you would want to use the GetStackTracer function to do that. +type StackTracer interface { + StackTrace() StackTrace +} + // Frame represents a program counter inside a stack frame. type Frame uintptr @@ -46,7 +52,8 @@ func (f Frame) line() int { // // Format accepts flags that alter the printing of some verbs, as follows: // -// %+s path of source file relative to the compile time GOPATH +// %+s function name and path of source file relative to the compile time +// GOPATH separated by \n\t (\n\t) // %+v equivalent to %+s:%d func (f Frame) Format(s fmt.State, verb rune) { switch verb { @@ -79,6 +86,14 @@ func (f Frame) Format(s fmt.State, verb rune) { // StackTrace is stack of Frames from innermost (newest) to outermost (oldest). type StackTrace []Frame +// Format formats the stack of Frames according to the fmt.Formatter interface. +// +// %s lists source files for each Frame in the stack +// %v lists the source file and line number for each Frame in the stack +// +// Format accepts flags that alter the printing of some verbs, as follows: +// +// %+v Prints filename, function, and line number for each Frame in the stack. func (st StackTrace) Format(s fmt.State, verb rune) { switch verb { case 'v': @@ -122,9 +137,13 @@ func (s *stack) StackTrace() StackTrace { } func callers() *stack { + return callersSkip(4) +} + +func callersSkip(skip int) *stack { const depth = 32 var pcs [depth]uintptr - n := runtime.Callers(3, pcs[:]) + n := runtime.Callers(skip, pcs[:]) var st stack = pcs[0:n] return &st } @@ -137,42 +156,15 @@ func funcname(name string) string { return name[i+1:] } -func trimGOPATH(name, file string) string { - // Here we want to get the source file path relative to the compile time - // GOPATH. As of Go 1.6.x there is no direct way to know the compiled - // GOPATH at runtime, but we can infer the number of path segments in the - // GOPATH. We note that fn.Name() returns the function name qualified by - // the import path, which does not include the GOPATH. Thus we can trim - // segments from the beginning of the file path until the number of path - // separators remaining is one more than the number of path separators in - // the function name. For example, given: - // - // GOPATH /home/user - // file /home/user/src/pkg/sub/file.go - // fn.Name() pkg/sub.Type.Method - // - // We want to produce: - // - // pkg/sub/file.go - // - // From this we can easily see that fn.Name() has one less path separator - // than our desired output. We count separators from the end of the file - // path until it finds two more than in the function name and then move - // one character forward to preserve the initial path segment without a - // leading separator. - const sep = "/" - goal := strings.Count(name, sep) + 2 - i := len(file) - for n := 0; n < goal; n++ { - i = strings.LastIndex(file[:i], sep) - if i == -1 { - // not enough separators found, set i so that the slice expression - // below leaves file unmodified - i = -len(sep) - break - } - } - // get back to 0 or trim the leading separator - file = file[i+len(sep):] - return file +// NewStack is for library implementers that want to generate a stack trace. +// Normally you should insted use AddStack to get an error with a stack trace. +// +// The result of this function can be turned into a stack trace by calling .StackTrace() +// +// This function takes an argument for the number of stack frames to skip. +// This avoids putting stack generation function calls like this one in the stack trace. +// A value of 0 will give you the line that called NewStack(0) +// A library author wrapping this in their own function will want to use a value of at least 1. +func NewStack(skip int) StackTracer { + return callersSkip(skip + 3) } From a4d546f304af6590aa773215c74aa38d382448b0 Mon Sep 17 00:00:00 2001 From: Greg Weber Date: Sat, 8 Sep 2018 07:06:07 -0700 Subject: [PATCH 09/18] use stack code in pingcap/errors --- pkg/error_code/stack.go | 34 ++++++++-------------------------- 1 file changed, 8 insertions(+), 26 deletions(-) diff --git a/pkg/error_code/stack.go b/pkg/error_code/stack.go index a75f10797bc..47696d32481 100644 --- a/pkg/error_code/stack.go +++ b/pkg/error_code/stack.go @@ -17,27 +17,14 @@ import ( "github.com/pkg/errors" ) -// StackTracer is the interface defined but not exported from pkg/errors -// The StackTrace() function (not method) is a preferred way to access the StackTrace -// -// Generally you should only bother with stack traces for internal errors. -type StackTracer interface { - StackTrace() errors.StackTrace -} - // StackTrace retrieves the errors.StackTrace from the error if it is present. // If there is not StackTrace it will return nil // // StackTrace looks to see if the error is a StackTracer or if a Causer of the error is a StackTracer. // It will return the stack trace from the deepest error it can find. func StackTrace(err error) errors.StackTrace { - if prev := WrappedError(err); prev != nil { - if trace := StackTrace(prev); trace != nil { - return trace - } - } - if stackTraceErr, ok := err.(StackTracer); ok { - return stackTraceErr.StackTrace() + if tracer := errors.GetStackTracer(err); tracer != nil { + return tracer.StackTrace() } return nil } @@ -47,13 +34,13 @@ func StackTrace(err error) errors.StackTrace { // Generally stack traces aren't needed for user errors, but they are provided by NewInternalErr. // Its also possible to define your own structures that satisfy the StackTracer interface. type StackCode struct { - Err ErrorCode - GetStackTrace errors.StackTrace + Err ErrorCode + GetStack errors.StackTracer } // StackTrace fulfills the StackTracer interface func (e StackCode) StackTrace() errors.StackTrace { - return e.GetStackTrace + return e.GetStack.StackTrace() } // NewStackCode constructs a StackCode, which is an ErrorCode with stack trace information @@ -69,16 +56,11 @@ func NewStackCode(err ErrorCode, position ...int) StackCode { } // if there is an existing trace, take that: it should be deeper - if trace := StackTrace(err); trace != nil { - return StackCode{Err: err, GetStackTrace: trace} + if tracer := errors.GetStackTracer(err); tracer != nil { + return StackCode{Err: err, GetStack: tracer} } - // we must go through some contortions to get a stack trace from pkg/errors - stackedErr := errors.WithStack(err) - if stackTraceErr, ok := stackedErr.(StackTracer); ok { - return StackCode{Err: err, GetStackTrace: stackTraceErr.StackTrace()[stackPosition:]} - } - panic("NewStackCode: pkg/errors WithStack StackTrace interface changed") + return StackCode{Err: err, GetStack: errors.NewStack(stackPosition)} } // Cause satisfies the Causer interface From ee73eb5270102084ac2b3c487f6b84b06c7f2a98 Mon Sep 17 00:00:00 2001 From: Greg Weber Date: Sat, 8 Sep 2018 07:49:35 -0700 Subject: [PATCH 10/18] use errors.Unwrap --- pkg/error_code/stack.go | 2 +- pkg/error_code/wrapped.go | 24 +++++++++--------------- 2 files changed, 10 insertions(+), 16 deletions(-) diff --git a/pkg/error_code/stack.go b/pkg/error_code/stack.go index 47696d32481..3866904604e 100644 --- a/pkg/error_code/stack.go +++ b/pkg/error_code/stack.go @@ -47,7 +47,7 @@ func (e StackCode) StackTrace() errors.StackTrace { // The second variable is an optional stack position gets rid of information about function calls to construct the stack trace. // It is defaulted to 1 to remove this function call. // -// NewStackCode first looks at the underlying error via WrappedError to see if it already has a StackTrace. +// NewStackCode first looks at the underlying error chain to see if it already has a StackTrace. // If so, that StackTrace is used. func NewStackCode(err ErrorCode, position ...int) StackCode { stackPosition := 1 diff --git a/pkg/error_code/wrapped.go b/pkg/error_code/wrapped.go index 5ac4d195a53..e9c935c128a 100644 --- a/pkg/error_code/wrapped.go +++ b/pkg/error_code/wrapped.go @@ -13,26 +13,20 @@ package errcode +import ( + "github.com/pkg/errors" +) + // Causer allows the abstract retrieval of the underlying error. // This is the interface that pkg/errors does not export but is considered part of the stable public API. // // Types that wrap errors should implement this to allow viewing of the underlying error. -// Generally you would use this via pkg/errors.Cause or WrappedError. +// Generally you would use this via pkg/errors.Cause or pkg/errors.Unwrap. // PreviousErrorCode and StackTrace use Causer to check if the underlying error is an ErrorCode or a StackTracer. type Causer interface { Cause() error } -// WrappedError retrieves the wrapped error via Causer. -// Unlike pkg/errors.Cause, it goes just one level deep and does not recursively traverse. -// Return nil is there is no wrapped error. -func WrappedError(err error) error { - if hasCause, ok := err.(Causer); ok { - return hasCause.Cause() - } - return nil -} - // EmbedErr is designed to be embedded into your existing error structs. // It provides the Causer interface already, which can reduce your boilerplate. type EmbedErr struct { @@ -57,7 +51,7 @@ func PreviousErrorCode(err ErrorCode) ErrorCode { } func previousErrorCodeCompare(code Code, err error) ErrorCode { - prev := WrappedError(err) + prev := errors.Unwrap(err) if prev == nil { return nil } @@ -77,12 +71,12 @@ func GetErrorCode(err error) ErrorCode { if errcode, ok := err.(ErrorCode); ok { return errcode } - prev := WrappedError(err) + prev := errors.Unwrap(err) for prev != nil { if errcode, ok := prev.(ErrorCode); ok { return WrappedErrorCode{errcode, err} } - prev = WrappedError(err) + prev = errors.Unwrap(err) } return nil } @@ -105,7 +99,7 @@ func (err WrappedErrorCode) Error() string { // Cause satisfies the Causer interface func (err WrappedErrorCode) Cause() error { - if wrapped := WrappedError(err.Top); wrapped != nil { + if wrapped := errors.Unwrap(err.Top); wrapped != nil { return wrapped } // There should be a wrapped error and this should not be reached From b79940de3a97c8c9f2aef4a8ff99ec49ada6a34f Mon Sep 17 00:00:00 2001 From: Greg Weber Date: Wed, 15 Aug 2018 16:59:42 -0700 Subject: [PATCH 11/18] error grouping --- pkg/error_code/error_code.go | 8 +++ pkg/error_code/group.go | 100 +++++++++++++++++++++++++++++++++++ 2 files changed, 108 insertions(+) create mode 100644 pkg/error_code/group.go diff --git a/pkg/error_code/error_code.go b/pkg/error_code/error_code.go index 6d212d315b9..25203af88c9 100644 --- a/pkg/error_code/error_code.go +++ b/pkg/error_code/error_code.go @@ -243,6 +243,14 @@ func OperationClientData(errCode ErrorCode) (string, interface{}) { // NewJSONFormat turns an ErrorCode into a JSONFormat func NewJSONFormat(errCode ErrorCode) JSONFormat { + // Gather up multiple errors. + // We discard any that are not ErrorCode. + errorCodes := ErrorCodes(errCode) + additional := make([]JSONFormat, len(errorCodes)-1) + for i, err := range errorCodes[1:] { + additional[i] = NewJSONFormat(err) + } + op, data := OperationClientData(errCode) var previous *JSONFormat diff --git a/pkg/error_code/group.go b/pkg/error_code/group.go new file mode 100644 index 00000000000..c234b48ef54 --- /dev/null +++ b/pkg/error_code/group.go @@ -0,0 +1,100 @@ +// Copyright 2018 PingCAP, Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// See the License for the specific language governing permissions and +// limitations under the License. + +package errcode + +// ErrorGroup is the same interface as provided by uber-go/multierr +// Other multi error packages provide similar interfaces. +// +// There are two concepts around multiple errors +// * Wrapping errors (We have this already with Causer) +// * multiple parallel/sibling errors: this is ErrorGroup +type ErrorGroup interface { + Errors() []error +} + +// Errors uses the ErrorGroup interface to return a slice of errors. +// If the ErrorGroup interface is not implemented it returns an array containing just the given error. +func Errors(err error) []error { + if eg, ok := err.(ErrorGroup); ok { + return eg.Errors() + } + return []error{err} +} + +// ErrorCodes return all errors (from an ErrorGroup) that are of interface ErrorCode. +// It first calls the Errors function. +func ErrorCodes(err error) []ErrorCode { + errors := Errors(err) + errorCodes := make([]ErrorCode, len(errors)) + for i, errItem := range errors { + if errcode, ok := errItem.(ErrorCode); ok { + errorCodes[i] = errcode + } + } + return errorCodes +} + +// A MultiErrCode contains at least one ErrorCode and uses that to satisfy the ErrorCode and related interfaces +// The Error method will produce a string of all the errors with a semi-colon separation. +// Later code (such as a JSON response) needs to look for the ErrorGroup interface. +type MultiErrCode struct { + ErrorCode ErrorCode + all []error +} + +// Combine constructs a MultiErrCode. +// It will combine any other MultiErrCode into just one MultiErrCode. +func Combine(override ErrorCode, others ...ErrorCode) ErrorCode { + all := Errors(override) + for _, other := range others { + all = append(all, Errors(other)...) + } + return MultiErrCode{ + ErrorCode: override, + all: all, + } +} + +var _ ErrorCode = (*MultiErrCode)(nil) // assert implements interface +var _ HasClientData = (*MultiErrCode)(nil) // assert implements interface +var _ Causer = (*MultiErrCode)(nil) // assert implements interface +var _ ErrorGroup = (*MultiErrCode)(nil) // assert implements interface + +func (e MultiErrCode) Error() string { + output := e.ErrorCode.Error() + for _, item := range e.all[1:] { + output += "; " + item.Error() + } + return output +} + +// Errors fullfills the ErrorGroup inteface +func (e MultiErrCode) Errors() []error { + return e.all +} + +// Code fullfills the ErrorCode inteface +func (e MultiErrCode) Code() Code { + return e.ErrorCode.Code() +} + +// Cause fullfills the Causer inteface +func (e MultiErrCode) Cause() error { + return e.ErrorCode +} + +// GetClientData fullfills the HasClientData inteface +func (e MultiErrCode) GetClientData() interface{} { + return ClientData(e.ErrorCode) +} From b0264102aceeb39673375d80d4ded9728857bda7 Mon Sep 17 00:00:00 2001 From: Greg Weber Date: Tue, 11 Sep 2018 09:14:58 -0700 Subject: [PATCH 12/18] upgrade pingcap/errors --- Gopkg.lock | 6 +- Gopkg.toml | 2 +- vendor/github.com/pkg/errors/errors.go | 16 ----- vendor/github.com/pkg/errors/group.go | 9 +++ vendor/github.com/pkg/errors/stack.go | 84 +++++++++++++++++++++----- 5 files changed, 83 insertions(+), 34 deletions(-) diff --git a/Gopkg.lock b/Gopkg.lock index e4dfd892209..6734b7a0b0f 100644 --- a/Gopkg.lock +++ b/Gopkg.lock @@ -326,13 +326,13 @@ revision = "279515615485b0f2d12f1421cc412fe2784e0190" [[projects]] - digest = "1:91fee14a873676b2118c54e08c2e04897ea5c141b55a9b9fb8cf0721458d0d85" + digest = "1:1ddea2d64730f05a189090d765b5108370acf83208d5074b2024a113096f4792" name = "github.com/pkg/errors" packages = ["."] pruneopts = "NUT" - revision = "9316aeb006f59424c65ff505c217f90c43d6445d" + revision = "0c849bcccef2ad68bca21ce43782f28e68996375" source = "https://github.com/pingcap/errors" - version = "v0.9.0" + version = "v0.10.0" [[projects]] digest = "1:8d8f554bbb62fb7aecf661b85b25e227f6ab6cfe2b4395ea65ef478bfc174940" diff --git a/Gopkg.toml b/Gopkg.toml index 5e061473774..152047a511c 100644 --- a/Gopkg.toml +++ b/Gopkg.toml @@ -38,7 +38,7 @@ [[constraint]] name = "github.com/pkg/errors" - version = ">= 0.9.0" + version = ">= 0.10.0" source = "https://github.com/pingcap/errors" [[override]] diff --git a/vendor/github.com/pkg/errors/errors.go b/vendor/github.com/pkg/errors/errors.go index 0168061fd52..4e2e9673779 100644 --- a/vendor/github.com/pkg/errors/errors.go +++ b/vendor/github.com/pkg/errors/errors.go @@ -173,22 +173,6 @@ func AddStack(err error) error { return WithStack(err) } -// GetStackTracer will return the first StackTracer in the causer chain. -// This function is used by AddStack to avoid creating redundant stack traces. -// -// You can also use the StackTracer interface on the returned error to get the stack trace. -func GetStackTracer(origErr error) StackTracer { - var stacked StackTracer - WalkDeep(origErr, func(err error) bool { - if stackTracer, ok := err.(StackTracer); ok { - stacked = stackTracer - return true - } - return false - }) - return stacked -} - type withStack struct { error *stack diff --git a/vendor/github.com/pkg/errors/group.go b/vendor/github.com/pkg/errors/group.go index 003932c95e8..e5a969ab76f 100644 --- a/vendor/github.com/pkg/errors/group.go +++ b/vendor/github.com/pkg/errors/group.go @@ -6,6 +6,15 @@ type ErrorGroup interface { Errors() []error } +// Errors uses the ErrorGroup interface to return a slice of errors. +// If the ErrorGroup interface is not implemented it returns an array containing just the given error. +func Errors(err error) []error { + if eg, ok := err.(ErrorGroup); ok { + return eg.Errors() + } + return []error{err} +} + // WalkDeep does a depth-first traversal of all errors. // Any ErrorGroup is traversed (after going deep). // The visitor function can return true to end the traversal early diff --git a/vendor/github.com/pkg/errors/stack.go b/vendor/github.com/pkg/errors/stack.go index 6edd7e5699f..bb1e6a84f33 100644 --- a/vendor/github.com/pkg/errors/stack.go +++ b/vendor/github.com/pkg/errors/stack.go @@ -1,10 +1,12 @@ package errors import ( + "bytes" "fmt" "io" "path" "runtime" + "strconv" "strings" ) @@ -14,6 +16,22 @@ type StackTracer interface { StackTrace() StackTrace } +// GetStackTracer will return the first StackTracer in the causer chain. +// This function is used by AddStack to avoid creating redundant stack traces. +// +// You can also use the StackTracer interface on the returned error to get the stack trace. +func GetStackTracer(origErr error) StackTracer { + var stacked StackTracer + WalkDeep(origErr, func(err error) bool { + if stackTracer, ok := err.(StackTracer); ok { + stacked = stackTracer + return true + } + return false + }) + return stacked +} + // Frame represents a program counter inside a stack frame. type Frame uintptr @@ -56,6 +74,11 @@ func (f Frame) line() int { // GOPATH separated by \n\t (\n\t) // %+v equivalent to %+s:%d func (f Frame) Format(s fmt.State, verb rune) { + f.format(s, s, verb) +} + +// format allows stack trace printing calls to be made with a bytes.Buffer. +func (f Frame) format(w io.Writer, s fmt.State, verb rune) { switch verb { case 's': switch { @@ -63,23 +86,25 @@ func (f Frame) Format(s fmt.State, verb rune) { pc := f.pc() fn := runtime.FuncForPC(pc) if fn == nil { - io.WriteString(s, "unknown") + io.WriteString(w, "unknown") } else { file, _ := fn.FileLine(pc) - fmt.Fprintf(s, "%s\n\t%s", fn.Name(), file) + io.WriteString(w, fn.Name()) + io.WriteString(w, "\n\t") + io.WriteString(w, file) } default: - io.WriteString(s, path.Base(f.file())) + io.WriteString(w, path.Base(f.file())) } case 'd': - fmt.Fprintf(s, "%d", f.line()) + io.WriteString(w, strconv.Itoa(f.line())) case 'n': name := runtime.FuncForPC(f.pc()).Name() - io.WriteString(s, funcname(name)) + io.WriteString(w, funcname(name)) case 'v': - f.Format(s, 's') - io.WriteString(s, ":") - f.Format(s, 'd') + f.format(w, s, 's') + io.WriteString(w, ":") + f.format(w, s, 'd') } } @@ -95,23 +120,50 @@ type StackTrace []Frame // // %+v Prints filename, function, and line number for each Frame in the stack. func (st StackTrace) Format(s fmt.State, verb rune) { + var b bytes.Buffer switch verb { case 'v': switch { case s.Flag('+'): - for _, f := range st { - fmt.Fprintf(s, "\n%+v", f) + b.Grow(len(st) * stackMinLen) + for _, fr := range st { + b.WriteByte('\n') + fr.format(&b, s, verb) } case s.Flag('#'): - fmt.Fprintf(s, "%#v", []Frame(st)) + fmt.Fprintf(&b, "%#v", []Frame(st)) default: - fmt.Fprintf(s, "%v", []Frame(st)) + st.formatSlice(&b, s, verb) } case 's': - fmt.Fprintf(s, "%s", []Frame(st)) + st.formatSlice(&b, s, verb) } + io.Copy(s, &b) } +// formatSlice will format this StackTrace into the given buffer as a slice of +// Frame, only valid when called with '%s' or '%v'. +func (st StackTrace) formatSlice(b *bytes.Buffer, s fmt.State, verb rune) { + b.WriteByte('[') + if len(st) == 0 { + b.WriteByte(']') + return + } + + b.Grow(len(st) * (stackMinLen / 4)) + st[0].format(b, s, verb) + for _, fr := range st[1:] { + b.WriteByte(' ') + fr.format(b, s, verb) + } + b.WriteByte(']') +} + +// stackMinLen is a best-guess at the minimum length of a stack trace. It +// doesn't need to be exact, just give a good enough head start for the buffer +// to avoid the expensive early growth. +const stackMinLen = 96 + // stack represents a stack of program counters. type stack []uintptr @@ -120,10 +172,14 @@ func (s *stack) Format(st fmt.State, verb rune) { case 'v': switch { case st.Flag('+'): + var b bytes.Buffer + b.Grow(len(*s) * stackMinLen) for _, pc := range *s { f := Frame(pc) - fmt.Fprintf(st, "\n%+v", f) + b.WriteByte('\n') + f.format(&b, st, 'v') } + io.Copy(st, &b) } } } From ff5c0905053be2a816312429832ac183a8cd6fd9 Mon Sep 17 00:00:00 2001 From: Greg Weber Date: Tue, 11 Sep 2018 09:15:19 -0700 Subject: [PATCH 13/18] use the latest from pingcap/errors Get rid of vertical error cobination and just use multi errors. vertical composition is just being used for annotation. For multiple errors, we always use horizontal composition. --- pkg/error_code/error_code.go | 31 ++--- pkg/error_code/error_code_test.go | 1 - pkg/error_code/group.go | 187 ++++++++++++++++++++++++------ pkg/error_code/wrapped.go | 116 ------------------ server/api/util.go | 2 +- 5 files changed, 170 insertions(+), 167 deletions(-) delete mode 100644 pkg/error_code/wrapped.go diff --git a/pkg/error_code/error_code.go b/pkg/error_code/error_code.go index 25203af88c9..c433f57d550 100644 --- a/pkg/error_code/error_code.go +++ b/pkg/error_code/error_code.go @@ -35,7 +35,7 @@ // JSONFormat includes a body of response data (the "data field") that is by default the data from the Error // serialized to JSON. // -// Errors are traced via PreviousErrorCode() which shows up as the Previous field in JSONFormat. +// Errors are traced via ErrorChainContext() which show up as the Others field in JSONFormat. // Stack traces are automatically added by NewInternalErr and show up as the Stack field in JSONFormat. package errcode @@ -190,6 +190,16 @@ type ErrorCode interface { Code() Code } +// Causer allows the abstract retrieval of the underlying error. +// This is the interface that pkg/errors does not export but is considered part of the stable public API. +// TODO: export this from pkg/errors +// +// Types that wrap errors should implement this to allow viewing of the underlying error. +// Generally you would use this via pkg/errors.Cause or pkg/errors.Unwrap. +type Causer interface { + Cause() error +} + // HasClientData is used to defined how to retrieve the data portion of an ErrorCode to be returned to the client. // Otherwise the struct itself will be assumed to be all the data by the ClientData method. // This is provided for exensibility, but may be unnecessary for you. @@ -217,15 +227,15 @@ func ClientData(errCode ErrorCode) interface{} { // The Operation field may be missing, and the Data field may be empty. // // The rest of the fields may be populated sparsely depending on the application: -// * Previous gives JSONFormat data for an ErrorCode that was wrapped by this one. It relies on the PreviousErrorCode function. // * Stack is a stack trace. This is only given for internal errors. +// * Others gives other errors that occurred (perhaps due to parallel requests). type JSONFormat struct { Code CodeStr `json:"code"` Msg string `json:"msg"` Data interface{} `json:"data"` Operation string `json:"operation,omitempty"` - Previous *JSONFormat `json:"previous,omitempty"` Stack errors.StackTrace `json:"stack,omitempty"` + Others []JSONFormat `json:"others,omitempty"` } // OperationClientData gives the results of both the ClientData and Operation functions. @@ -241,24 +251,19 @@ func OperationClientData(errCode ErrorCode) (string, interface{}) { return op, data } -// NewJSONFormat turns an ErrorCode into a JSONFormat +// NewJSONFormat turns an ErrorCode into a JSONFormat. +// If you use ErrorCodeChain first, you will ensure proper population of the Others field. func NewJSONFormat(errCode ErrorCode) JSONFormat { // Gather up multiple errors. // We discard any that are not ErrorCode. errorCodes := ErrorCodes(errCode) - additional := make([]JSONFormat, len(errorCodes)-1) + others := make([]JSONFormat, len(errorCodes)-1) for i, err := range errorCodes[1:] { - additional[i] = NewJSONFormat(err) + others[i] = NewJSONFormat(err) } op, data := OperationClientData(errCode) - var previous *JSONFormat - if prevCode := PreviousErrorCode(errCode); prevCode != nil { - ptrVar := NewJSONFormat(prevCode) - previous = &ptrVar - } - var stack errors.StackTrace if errCode.Code().IsAncestor(InternalCode) { stack = StackTrace(errCode) @@ -270,7 +275,7 @@ func NewJSONFormat(errCode ErrorCode) JSONFormat { Code: errCode.Code().CodeStr(), Operation: op, Stack: stack, - Previous: previous, + Others: others, } } diff --git a/pkg/error_code/error_code_test.go b/pkg/error_code/error_code_test.go index ac5fd7422f7..1be66abf04c 100644 --- a/pkg/error_code/error_code_test.go +++ b/pkg/error_code/error_code_test.go @@ -350,7 +350,6 @@ func ClientDataEquals(t *testing.T, code errcode.ErrorCode, data interface{}, co Stack: stack, } newJSON := errcode.NewJSONFormat(code) - newJSON.Previous = nil jsonEquals(t, "JSONFormat", jsonExpected, newJSON) } diff --git a/pkg/error_code/group.go b/pkg/error_code/group.go index c234b48ef54..2fdbdeda9ca 100644 --- a/pkg/error_code/group.go +++ b/pkg/error_code/group.go @@ -13,29 +13,16 @@ package errcode -// ErrorGroup is the same interface as provided by uber-go/multierr -// Other multi error packages provide similar interfaces. -// -// There are two concepts around multiple errors -// * Wrapping errors (We have this already with Causer) -// * multiple parallel/sibling errors: this is ErrorGroup -type ErrorGroup interface { - Errors() []error -} - -// Errors uses the ErrorGroup interface to return a slice of errors. -// If the ErrorGroup interface is not implemented it returns an array containing just the given error. -func Errors(err error) []error { - if eg, ok := err.(ErrorGroup); ok { - return eg.Errors() - } - return []error{err} -} +import ( + "fmt" + + "github.com/pkg/errors" +) // ErrorCodes return all errors (from an ErrorGroup) that are of interface ErrorCode. // It first calls the Errors function. func ErrorCodes(err error) []ErrorCode { - errors := Errors(err) + errors := errors.Errors(err) errorCodes := make([]ErrorCode, len(errors)) for i, errItem := range errors { if errcode, ok := errItem.(ErrorCode); ok { @@ -49,31 +36,37 @@ func ErrorCodes(err error) []ErrorCode { // The Error method will produce a string of all the errors with a semi-colon separation. // Later code (such as a JSON response) needs to look for the ErrorGroup interface. type MultiErrCode struct { - ErrorCode ErrorCode - all []error + ErrCode ErrorCode + rest []error } // Combine constructs a MultiErrCode. // It will combine any other MultiErrCode into just one MultiErrCode. -func Combine(override ErrorCode, others ...ErrorCode) ErrorCode { - all := Errors(override) +// This is "horizontal" composition. +// If you want normal "vertical" composition use BuildChain. +func Combine(initial ErrorCode, others ...ErrorCode) ErrorCode { + var rest []error + if group, ok := initial.(errors.ErrorGroup); ok { + rest = group.Errors() + } for _, other := range others { - all = append(all, Errors(other)...) + rest = append(rest, errors.Errors(other)...) } return MultiErrCode{ - ErrorCode: override, - all: all, + ErrCode: initial, + rest: rest, } } -var _ ErrorCode = (*MultiErrCode)(nil) // assert implements interface -var _ HasClientData = (*MultiErrCode)(nil) // assert implements interface -var _ Causer = (*MultiErrCode)(nil) // assert implements interface -var _ ErrorGroup = (*MultiErrCode)(nil) // assert implements interface +var _ ErrorCode = (*MultiErrCode)(nil) // assert implements interface +var _ HasClientData = (*MultiErrCode)(nil) // assert implements interface +var _ Causer = (*MultiErrCode)(nil) // assert implements interface +var _ errors.ErrorGroup = (*MultiErrCode)(nil) // assert implements interface +var _ fmt.Formatter = (*MultiErrCode)(nil) // assert implements interface func (e MultiErrCode) Error() string { - output := e.ErrorCode.Error() - for _, item := range e.all[1:] { + output := e.ErrCode.Error() + for _, item := range e.rest { output += "; " + item.Error() } return output @@ -81,20 +74,142 @@ func (e MultiErrCode) Error() string { // Errors fullfills the ErrorGroup inteface func (e MultiErrCode) Errors() []error { - return e.all + return append([]error{e.ErrCode.(error)}, e.rest...) } // Code fullfills the ErrorCode inteface func (e MultiErrCode) Code() Code { - return e.ErrorCode.Code() + return e.ErrCode.Code() } // Cause fullfills the Causer inteface func (e MultiErrCode) Cause() error { - return e.ErrorCode + return e.ErrCode } // GetClientData fullfills the HasClientData inteface func (e MultiErrCode) GetClientData() interface{} { - return ClientData(e.ErrorCode) + return ClientData(e.ErrCode) +} + +// ErrorCodeChain resolves an error chain down to a chain of just error codes +// Any ErrorGroups found are converted to a MultiErrCode. +// Passed over error inforation is retained using ChainContext. +// If a code was overidden in the chain, it will show up as a MultiErrCode. +func ErrorCodeChain(err error) ErrorCode { + var code ErrorCode + currentErr := err + chainErrCode := func(errcode ErrorCode) { + if errcode.(error) != currentErr { + errcode = ChainContext{currentErr, errcode} + } + if code == nil { + code = errcode + } else { + code = MultiErrCode{code, []error{code.(error), errcode.(error)}} + } + currentErr = errcode + } + + for err != nil { + if errcode, ok := err.(ErrorCode); ok { + if code != nil && code.Code() != errcode.Code() { + chainErrCode(errcode) + } + } else if eg, ok := err.(errors.ErrorGroup); ok { + group := []ErrorCode{} + for _, errItem := range eg.Errors() { + group = append(group, ErrorCodeChain(errItem)) + } + if len(group) > 0 { + chainErrCode(Combine(group[0], group[1:]...)) + } + } + err = errors.Unwrap(err) + } + + return code +} + +// ChainContext is returned by ErrorCodeChain +// to retain the full wrapped error message of the error chain. +// If you add context to an error code, this retains that in the Error() message. +type ChainContext struct { + Top error + ErrCode ErrorCode +} + +// Code satisfies the ErrorCode interface +func (err ChainContext) Code() Code { + return err.ErrCode.Code() +} + +// Error satisfies the Error interface +func (err ChainContext) Error() string { + return err.Top.Error() +} + +// Cause satisfies the Causer interface +func (err ChainContext) Cause() error { + if wrapped := errors.Unwrap(err.Top); wrapped != nil { + return wrapped + } + return err.ErrCode +} + +// GetClientData satisfies the HasClientData interface +func (err ChainContext) GetClientData() interface{} { + return ClientData(err.ErrCode) +} + +var _ ErrorCode = (*ChainContext)(nil) +var _ HasClientData = (*ChainContext)(nil) +var _ Causer = (*ChainContext)(nil) + +// Format implements the Formatter interface +func (err ChainContext) Format(s fmt.State, verb rune) { + switch verb { + case 'v': + if s.Flag('+') { + fmt.Fprintf(s, "%+v\n", err.ErrCode) + if errors.HasStack(err.ErrCode) { + fmt.Fprintf(s, "%v", err.Top) + } else { + fmt.Fprintf(s, "%+v", err.Top) + } + return + } + fallthrough + case 's': + fmt.Fprintf(s, "Code: %s. %s", err.ErrCode.Code(), err.Top) + case 'q': + fmt.Fprintf(s, "Code: %q. %q", err.ErrCode.Code(), err.Top) + } +} + +// Format implements the Formatter interface +func (e MultiErrCode) Format(s fmt.State, verb rune) { + switch verb { + case 'v': + if s.Flag('+') { + fmt.Fprintf(s, "%+v\n", e.ErrCode) + if errors.HasStack(e.ErrCode) { + for _, nextErr := range e.rest { + fmt.Fprintf(s, "%v", nextErr) + } + } else { + for _, nextErr := range e.rest { + fmt.Fprintf(s, "%v", nextErr) + } + } + return + } + fallthrough + case 's': + fmt.Fprintf(s, "%s\n", e.ErrCode) + fmt.Fprintf(s, "%s", e.rest) + case 'q': + fmt.Fprintf(s, "%q\n", e.ErrCode) + fmt.Fprintf(s, "%q\n", e.rest) + } } diff --git a/pkg/error_code/wrapped.go b/pkg/error_code/wrapped.go deleted file mode 100644 index e9c935c128a..00000000000 --- a/pkg/error_code/wrapped.go +++ /dev/null @@ -1,116 +0,0 @@ -// Copyright 2018 PingCAP, Inc. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// See the License for the specific language governing permissions and -// limitations under the License. - -package errcode - -import ( - "github.com/pkg/errors" -) - -// Causer allows the abstract retrieval of the underlying error. -// This is the interface that pkg/errors does not export but is considered part of the stable public API. -// -// Types that wrap errors should implement this to allow viewing of the underlying error. -// Generally you would use this via pkg/errors.Cause or pkg/errors.Unwrap. -// PreviousErrorCode and StackTrace use Causer to check if the underlying error is an ErrorCode or a StackTracer. -type Causer interface { - Cause() error -} - -// EmbedErr is designed to be embedded into your existing error structs. -// It provides the Causer interface already, which can reduce your boilerplate. -type EmbedErr struct { - Err error -} - -// Cause implements the Causer interface -func (e EmbedErr) Cause() error { - return e.Err -} - -var _ Causer = (*EmbedErr)(nil) // assert implements interface - -// PreviousErrorCode looks for a previous ErrorCode that has a different code. -// This helps construct a trace of all previous errors. -// It will return nil if no previous ErrorCode is found. -// -// To look for a previous ErrorCode it looks at Causer to see if they are an ErrorCode. -// Wrappers of errors like OpErrCode and CodedError implement Causer. -func PreviousErrorCode(err ErrorCode) ErrorCode { - return previousErrorCodeCompare(err.Code(), err) -} - -func previousErrorCodeCompare(code Code, err error) ErrorCode { - prev := errors.Unwrap(err) - if prev == nil { - return nil - } - if errcode, ok := prev.(ErrorCode); ok { - if errcode.Code() != code { - return errcode - } - } - return previousErrorCodeCompare(code, prev) -} - -// GetErrorCode tries to convert an error to an ErrorCode. -// If the error is not an ErrorCode, -// it looks for the first ErrorCode it can find via Causer. -// In that case it will retain the error message of the original error by returning a WrappedErrorCode. -func GetErrorCode(err error) ErrorCode { - if errcode, ok := err.(ErrorCode); ok { - return errcode - } - prev := errors.Unwrap(err) - for prev != nil { - if errcode, ok := prev.(ErrorCode); ok { - return WrappedErrorCode{errcode, err} - } - prev = errors.Unwrap(err) - } - return nil -} - -// WrappedErrorCode is returned by GetErrorCode to retain the full wrapped error message -type WrappedErrorCode struct { - ErrCode ErrorCode - Top error -} - -// Code satisfies the ErrorCode interface -func (err WrappedErrorCode) Code() Code { - return err.ErrCode.Code() -} - -// Error satisfies the Error interface -func (err WrappedErrorCode) Error() string { - return err.Top.Error() -} - -// Cause satisfies the Causer interface -func (err WrappedErrorCode) Cause() error { - if wrapped := errors.Unwrap(err.Top); wrapped != nil { - return wrapped - } - // There should be a wrapped error and this should not be reached - return err.ErrCode -} - -// GetClientData satisfies the HasClientData interface -func (err WrappedErrorCode) GetClientData() interface{} { - return ClientData(err.ErrCode) -} - -var _ ErrorCode = (*WrappedErrorCode)(nil) -var _ HasClientData = (*WrappedErrorCode)(nil) -var _ Causer = (*WrappedErrorCode)(nil) diff --git a/server/api/util.go b/server/api/util.go index 9cfc17d0be3..a38dc022802 100644 --- a/server/api/util.go +++ b/server/api/util.go @@ -40,7 +40,7 @@ func errorResp(rd *render.Render, w http.ResponseWriter, err error) { rd.JSON(w, http.StatusInternalServerError, "nil error") return } - if errCode := errcode.GetErrorCode(err); errCode != nil { + if errCode := errcode.ErrorCodeChain(err); errCode != nil { w.Header().Set("TiDB-Error-Code", errCode.Code().CodeStr().String()) rd.JSON(w, errCode.Code().HTTPCode(), errcode.NewJSONFormat(errCode)) } else { From 99af76dcd6b257ecdb74b2466c0ec44b93cb7239 Mon Sep 17 00:00:00 2001 From: Greg Weber Date: Tue, 11 Sep 2018 09:53:51 -0700 Subject: [PATCH 14/18] bugfix --- pkg/error_code/group.go | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/pkg/error_code/group.go b/pkg/error_code/group.go index 2fdbdeda9ca..ce351705f77 100644 --- a/pkg/error_code/group.go +++ b/pkg/error_code/group.go @@ -113,7 +113,7 @@ func ErrorCodeChain(err error) ErrorCode { for err != nil { if errcode, ok := err.(ErrorCode); ok { - if code != nil && code.Code() != errcode.Code() { + if code == nil || code.Code() != errcode.Code() { chainErrCode(errcode) } } else if eg, ok := err.(errors.ErrorGroup); ok { @@ -181,9 +181,9 @@ func (err ChainContext) Format(s fmt.State, verb rune) { } fallthrough case 's': - fmt.Fprintf(s, "Code: %s. %s", err.ErrCode.Code(), err.Top) + fmt.Fprintf(s, "Code: %s. %s", err.ErrCode.Code().CodeStr(), err.Top) case 'q': - fmt.Fprintf(s, "Code: %q. %q", err.ErrCode.Code(), err.Top) + fmt.Fprintf(s, "Code: %q. %q", err.ErrCode.Code().CodeStr(), err.Top) } } From dee08a85bb0ab63b539716473153bd2471a751c6 Mon Sep 17 00:00:00 2001 From: Greg Weber Date: Thu, 20 Sep 2018 22:00:28 -0700 Subject: [PATCH 15/18] update to latest pingcap/errors (docs update) --- Gopkg.lock | 6 +++--- Gopkg.toml | 2 +- vendor/github.com/pkg/errors/errors.go | 17 ++++++++++------- vendor/github.com/pkg/errors/juju_adaptor.go | 7 +++++-- 4 files changed, 19 insertions(+), 13 deletions(-) diff --git a/Gopkg.lock b/Gopkg.lock index 6734b7a0b0f..3fb78c6ed26 100644 --- a/Gopkg.lock +++ b/Gopkg.lock @@ -326,13 +326,13 @@ revision = "279515615485b0f2d12f1421cc412fe2784e0190" [[projects]] - digest = "1:1ddea2d64730f05a189090d765b5108370acf83208d5074b2024a113096f4792" + digest = "1:677ff2ee188099669fbecf583de8c23111020adee48d71a6f60c733f6b2fea7a" name = "github.com/pkg/errors" packages = ["."] pruneopts = "NUT" - revision = "0c849bcccef2ad68bca21ce43782f28e68996375" + revision = "31ffda8a65b0f910c6d65f1fef40e761fd606384" source = "https://github.com/pingcap/errors" - version = "v0.10.0" + version = "v0.10.1" [[projects]] digest = "1:8d8f554bbb62fb7aecf661b85b25e227f6ab6cfe2b4395ea65ef478bfc174940" diff --git a/Gopkg.toml b/Gopkg.toml index 152047a511c..bb4ae391c6c 100644 --- a/Gopkg.toml +++ b/Gopkg.toml @@ -38,7 +38,7 @@ [[constraint]] name = "github.com/pkg/errors" - version = ">= 0.10.0" + version = ">= 0.10.1" source = "https://github.com/pingcap/errors" [[override]] diff --git a/vendor/github.com/pkg/errors/errors.go b/vendor/github.com/pkg/errors/errors.go index 4e2e9673779..2e1d3f62896 100644 --- a/vendor/github.com/pkg/errors/errors.go +++ b/vendor/github.com/pkg/errors/errors.go @@ -152,7 +152,8 @@ func (f *fundamental) Format(s fmt.State, verb rune) { // WithStack annotates err with a stack trace at the point WithStack was called. // If err is nil, WithStack returns nil. // -// Deprecated: use AddStack +// For most use cases this is deprecated and AddStack should be used (which will ensure just one stack trace). +// However, one may want to use this in some situations, for example to create a 2nd trace across a goroutine. func WithStack(err error) error { if err == nil { return nil @@ -197,10 +198,11 @@ func (w *withStack) Format(s fmt.State, verb rune) { } // Wrap returns an error annotating err with a stack trace -// at the point Annotate is called, and the supplied message. -// If err is nil, Annotate returns nil. +// at the point Wrap is called, and the supplied message. +// If err is nil, Wrap returns nil. // -// Deprecated: use Annotate instead +// For most use cases this is deprecated in favor of Annotate. +// Annotate avoids creating duplicate stack traces. func Wrap(err error, message string) error { if err == nil { return nil @@ -218,10 +220,11 @@ func Wrap(err error, message string) error { } // Wrapf returns an error annotating err with a stack trace -// at the point Annotatef is call, and the format specifier. -// If err is nil, Annotatef returns nil. +// at the point Wrapf is call, and the format specifier. +// If err is nil, Wrapf returns nil. // -// Deprecated: use Annotatef instead +// For most use cases this is deprecated in favor of Annotatef. +// Annotatef avoids creating duplicate stack traces. func Wrapf(err error, format string, args ...interface{}) error { if err == nil { return nil diff --git a/vendor/github.com/pkg/errors/juju_adaptor.go b/vendor/github.com/pkg/errors/juju_adaptor.go index 773a1970866..0b20f57370c 100644 --- a/vendor/github.com/pkg/errors/juju_adaptor.go +++ b/vendor/github.com/pkg/errors/juju_adaptor.go @@ -6,12 +6,12 @@ import ( // ==================== juju adaptor start ======================== -// Trace annotates err with a stack trace at the point WithStack was called. -// If err is nil or already contain stack trace return directly. +// Trace just calls AddStack. func Trace(err error) error { return AddStack(err) } +// Annotate adds a message and ensures there is a stack trace. func Annotate(err error, message string) error { if err == nil { return nil @@ -31,6 +31,7 @@ func Annotate(err error, message string) error { } } +// Annotatef adds a message and ensures there is a stack trace. func Annotatef(err error, format string, args ...interface{}) error { if err == nil { return nil @@ -51,6 +52,8 @@ func Annotatef(err error, format string, args ...interface{}) error { } // ErrorStack will format a stack trace if it is available, otherwise it will be Error() +// If the error is nil, the empty string is returned +// Note that this just calls fmt.Sprintf("%+v", err) func ErrorStack(err error) string { if err == nil { return "" From 64bcff46b6be9893f26857a72117bc24d39c7ee3 Mon Sep 17 00:00:00 2001 From: Greg Weber Date: Tue, 25 Sep 2018 16:50:53 -0700 Subject: [PATCH 16/18] vendor: errcode is now its own package --- pkg/error_code/error_code_test.go | 390 ------------------ .../github.com/pingcap/errcode}/codes.go | 0 .../github.com/pingcap/errcode}/error_code.go | 26 +- .../github.com/pingcap/errcode}/group.go | 43 +- .../github.com/pingcap/errcode}/operation.go | 4 +- .../github.com/pingcap/errcode}/stack.go | 2 +- vendor/github.com/pingcap/errors/LICENSE | 23 ++ vendor/github.com/pingcap/errors/errors.go | 324 +++++++++++++++ vendor/github.com/pingcap/errors/group.go | 42 ++ .../github.com/pingcap/errors/juju_adaptor.go | 79 ++++ vendor/github.com/pingcap/errors/stack.go | 226 ++++++++++ 11 files changed, 744 insertions(+), 415 deletions(-) delete mode 100644 pkg/error_code/error_code_test.go rename {pkg/error_code => vendor/github.com/pingcap/errcode}/codes.go (100%) rename {pkg/error_code => vendor/github.com/pingcap/errcode}/error_code.go (91%) rename {pkg/error_code => vendor/github.com/pingcap/errcode}/group.go (80%) rename {pkg/error_code => vendor/github.com/pingcap/errcode}/operation.go (95%) rename {pkg/error_code => vendor/github.com/pingcap/errcode}/stack.go (99%) create mode 100644 vendor/github.com/pingcap/errors/LICENSE create mode 100644 vendor/github.com/pingcap/errors/errors.go create mode 100644 vendor/github.com/pingcap/errors/group.go create mode 100644 vendor/github.com/pingcap/errors/juju_adaptor.go create mode 100644 vendor/github.com/pingcap/errors/stack.go diff --git a/pkg/error_code/error_code_test.go b/pkg/error_code/error_code_test.go deleted file mode 100644 index 1be66abf04c..00000000000 --- a/pkg/error_code/error_code_test.go +++ /dev/null @@ -1,390 +0,0 @@ -// Copyright 2018 PingCAP, Inc. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// See the License for the specific language governing permissions and -// limitations under the License. - -package errcode_test - -import ( - "encoding/json" - "fmt" - "reflect" - "testing" - - "github.com/pingcap/pd/pkg/error_code" - "github.com/pkg/errors" -) - -// Test setting the HTTP code -type HTTPError struct{} - -func (e HTTPError) Error() string { return "error" } - -const httpCodeStr = "input.http" - -var codeHttp900 = errcode.InvalidInputCode.Child(httpCodeStr).SetHTTP(900) - -func (e HTTPError) Code() errcode.Code { - return codeHttp900 -} - -func TestHttpErrorCode(t *testing.T) { - http := HTTPError{} - AssertHTTPCode(t, http, 900) - ErrorEquals(t, http, "error") - ClientDataEquals(t, http, http, httpCodeStr) -} - -// Test a very simple error -type MinimalError struct{} - -func (e MinimalError) Error() string { return "error" } - -var _ errcode.ErrorCode = (*MinimalError)(nil) // assert implements interface - -const codeString errcode.CodeStr = "input.testcode" - -var registeredCode errcode.Code = errcode.InvalidInputCode.Child(codeString) - -func (e MinimalError) Code() errcode.Code { return registeredCode } - -func TestMinimalErrorCode(t *testing.T) { - minimal := MinimalError{} - AssertCodes(t, minimal) - ErrorEquals(t, minimal, "error") - ClientDataEquals(t, minimal, minimal) - OpEquals(t, minimal, "") -} - -// We don't prevent duplicate codes -var childPathOnlyCode errcode.Code = errcode.InvalidInputCode.Child("testcode") - -type ChildOnlyError struct{} - -func (e ChildOnlyError) Error() string { return "error" } - -var _ errcode.ErrorCode = (*ChildOnlyError)(nil) // assert implements interface - -func (e ChildOnlyError) Code() errcode.Code { return childPathOnlyCode } - -func TestChildOnlyErrorCode(t *testing.T) { - coe := ChildOnlyError{} - AssertCodes(t, coe) - ErrorEquals(t, coe, "error") - ClientDataEquals(t, coe, coe) -} - -// Test a top-level error -type TopError struct{} - -func (e TopError) Error() string { return "error" } - -var _ errcode.ErrorCode = (*TopError)(nil) // assert implements interface - -const topCodeStr errcode.CodeStr = "top" - -var topCode errcode.Code = errcode.NewCode(topCodeStr) - -func (e TopError) Code() errcode.Code { return topCode } - -func TestTopErrorCode(t *testing.T) { - top := TopError{} - AssertCodes(t, top, topCodeStr) - ErrorEquals(t, top, "error") - ClientDataEquals(t, top, top, topCodeStr) -} - -// Test a deep hierarchy -type DeepError struct{} - -func (e DeepError) Error() string { return "error" } - -var _ errcode.ErrorCode = (*DeepError)(nil) // assert implements interface - -const deepCodeStr errcode.CodeStr = "input.testcode.very.very.deep" - -var intermediateCode = registeredCode.Child("input.testcode.very").SetHTTP(800) -var deepCode errcode.Code = intermediateCode.Child("input.testcode.very.very").Child(deepCodeStr) - -func (e DeepError) Code() errcode.Code { return deepCode } - -func TestDeepErrorCode(t *testing.T) { - deep := DeepError{} - AssertHTTPCode(t, deep, 800) - AssertCode(t, deep, deepCodeStr) - ErrorEquals(t, deep, "error") - ClientDataEquals(t, deep, deep, deepCodeStr) -} - -// Test an ErrorWrapper that has different error types placed into it -type ErrorWrapper struct{ Err error } - -var _ errcode.ErrorCode = (*ErrorWrapper)(nil) // assert implements interface -var _ errcode.HasClientData = (*ErrorWrapper)(nil) // assert implements interface - -func (e ErrorWrapper) Code() errcode.Code { - return registeredCode -} -func (e ErrorWrapper) Error() string { - return e.Err.Error() -} -func (e ErrorWrapper) GetClientData() interface{} { - return e.Err -} - -type Struct1 struct{ A string } -type StructConstError1 struct{ A string } - -func (e Struct1) Error() string { - return e.A -} - -func (e StructConstError1) Error() string { - return "error" -} - -type Struct2 struct { - A string - B string -} - -func (e Struct2) Error() string { - return fmt.Sprintf("error A & B %s & %s", e.A, e.B) -} - -func TestErrorWrapperCode(t *testing.T) { - wrapped := ErrorWrapper{Err: errors.New("error")} - AssertCodes(t, wrapped) - ErrorEquals(t, wrapped, "error") - ClientDataEquals(t, wrapped, errors.New("error")) - s2 := Struct2{A: "A", B: "B"} - wrappedS2 := ErrorWrapper{Err: s2} - AssertCodes(t, wrappedS2) - ErrorEquals(t, wrappedS2, "error A & B A & B") - ClientDataEquals(t, wrappedS2, s2) - s1 := Struct1{A: "A"} - ClientDataEquals(t, ErrorWrapper{Err: s1}, s1) - sconst := StructConstError1{A: "A"} - ClientDataEquals(t, ErrorWrapper{Err: sconst}, sconst) -} - -var internalChildCodeStr errcode.CodeStr = "internal.child.granchild" -var internalChild = errcode.InternalCode.Child("internal.child").SetHTTP(503).Child(internalChildCodeStr) - -type InternalChild struct{} - -func (ic InternalChild) Error() string { return "internal child error" } -func (ic InternalChild) Code() errcode.Code { return internalChild } - -func TestNewInvalidInputErr(t *testing.T) { - err := errcode.NewInvalidInputErr(errors.New("new error")) - AssertCodes(t, err, "input") - ErrorEquals(t, err, "new error") - ClientDataEquals(t, err, errors.New("new error"), "input") - - err = errcode.NewInvalidInputErr(MinimalError{}) - AssertCodes(t, err, "input.testcode") - ErrorEquals(t, err, "error") - ClientDataEquals(t, err, MinimalError{}, errcode.CodeStr("input.testcode")) - - internalErr := errcode.NewInternalErr(MinimalError{}) - err = errcode.NewInvalidInputErr(internalErr) - internalCodeStr := errcode.CodeStr("internal") - AssertCode(t, err, internalCodeStr) - AssertHTTPCode(t, err, 500) - ErrorEquals(t, err, "error") - ClientDataEquals(t, err, MinimalError{}, internalCodeStr) - - wrappedInternalErr := errcode.NewInternalErr(internalErr) - AssertCode(t, err, internalCodeStr) - AssertHTTPCode(t, err, 500) - ErrorEquals(t, err, "error") - ClientDataEquals(t, wrappedInternalErr, MinimalError{}, internalCodeStr) - // It should use the original stack trace, not the wrapped - AssertStackEquals(t, wrappedInternalErr, errcode.StackTrace(internalErr)) - - err = errcode.NewInvalidInputErr(InternalChild{}) - AssertCode(t, err, internalChildCodeStr) - AssertHTTPCode(t, err, 503) - ErrorEquals(t, err, "internal child error") - ClientDataEquals(t, err, InternalChild{}, internalChildCodeStr) -} - -func TestStackTrace(t *testing.T) { - internalCodeStr := errcode.CodeStr("internal") - err := errors.New("errors stack") - wrappedInternalErr := errcode.NewInternalErr(err) - AssertCode(t, wrappedInternalErr, internalCodeStr) - AssertHTTPCode(t, wrappedInternalErr, 500) - ErrorEquals(t, err, "errors stack") - ClientDataEquals(t, wrappedInternalErr, err, internalCodeStr) - // It should use the original stack trace, not the wrapped - AssertStackEquals(t, wrappedInternalErr, errcode.StackTrace(err)) -} - -func TestNewInternalErr(t *testing.T) { - internalCodeStr := errcode.CodeStr("internal") - err := errcode.NewInternalErr(errors.New("new error")) - AssertCode(t, err, internalCodeStr) - AssertHTTPCode(t, err, 500) - ErrorEquals(t, err, "new error") - ClientDataEquals(t, err, errors.New("new error"), "internal") - - err = errcode.NewInternalErr(MinimalError{}) - AssertCode(t, err, internalCodeStr) - AssertHTTPCode(t, err, 500) - ErrorEquals(t, err, "error") - ClientDataEquals(t, err, MinimalError{}, internalCodeStr) - - invalidErr := errcode.NewInvalidInputErr(MinimalError{}) - err = errcode.NewInternalErr(invalidErr) - AssertCode(t, err, internalCodeStr) - AssertHTTPCode(t, err, 500) - ErrorEquals(t, err, "error") - ClientDataEquals(t, err, MinimalError{}, internalCodeStr) -} - -// Test Operation -type OpErrorHas struct{ MinimalError } - -func (e OpErrorHas) GetOperation() string { return "has" } - -type OpErrorEmbed struct { - errcode.EmbedOp - MinimalError -} - -var _ errcode.ErrorCode = (*OpErrorHas)(nil) // assert implements interface -var _ errcode.HasOperation = (*OpErrorHas)(nil) // assert implements interface -var _ errcode.ErrorCode = (*OpErrorEmbed)(nil) // assert implements interface -var _ errcode.HasOperation = (*OpErrorEmbed)(nil) // assert implements interface - -func TestOpErrorCode(t *testing.T) { - AssertOperation(t, "foo", "") - has := OpErrorHas{} - AssertOperation(t, has, "has") - AssertCodes(t, has) - ErrorEquals(t, has, "error") - ClientDataEquals(t, has, has) - OpEquals(t, has, "has") - - OpEquals(t, OpErrorEmbed{}, "") - OpEquals(t, OpErrorEmbed{EmbedOp: errcode.EmbedOp{Op: "field"}}, "field") - - opEmpty := errcode.Op("") - op := errcode.Op("modify") - OpEquals(t, opEmpty.AddTo(MinimalError{}), "") - OpEquals(t, op.AddTo(MinimalError{}), "modify") - - OpEquals(t, ErrorWrapper{Err: has}, "has") - OpEquals(t, ErrorWrapper{Err: OpErrorEmbed{EmbedOp: errcode.EmbedOp{Op: "field"}}}, "field") - - opErrCode := errcode.OpErrCode{Operation: "opcode", Err: MinimalError{}} - AssertOperation(t, opErrCode, "opcode") - OpEquals(t, opErrCode, "opcode") - - OpEquals(t, ErrorWrapper{Err: opErrCode}, "opcode") - wrappedHas := ErrorWrapper{Err: errcode.OpErrCode{Operation: "opcode", Err: has}} - AssertOperation(t, wrappedHas, "") - OpEquals(t, wrappedHas, "opcode") - OpEquals(t, errcode.OpErrCode{Operation: "opcode", Err: has}, "opcode") -} - -func AssertCodes(t *testing.T, code errcode.ErrorCode, codeStrs ...errcode.CodeStr) { - t.Helper() - AssertCode(t, code, codeStrs...) - AssertHTTPCode(t, code, 400) -} - -func AssertCode(t *testing.T, code errcode.ErrorCode, codeStrs ...errcode.CodeStr) { - t.Helper() - codeStr := codeString - if len(codeStrs) > 0 { - codeStr = codeStrs[0] - } - if code.Code().CodeStr() != codeStr { - t.Errorf("code expected %v\ncode but got %v", codeStr, code.Code().CodeStr()) - } -} - -func AssertHTTPCode(t *testing.T, code errcode.ErrorCode, httpCode int) { - t.Helper() - expected := code.Code().HTTPCode() - if expected != httpCode { - t.Errorf("excpected HTTP Code %v but got %v", httpCode, expected) - } -} - -func ErrorEquals(t *testing.T, err error, msg string) { - if err.Error() != msg { - t.Errorf("Expected error %v. Got error %v", msg, err.Error()) - } -} - -func ClientDataEquals(t *testing.T, code errcode.ErrorCode, data interface{}, codeStrs ...errcode.CodeStr) { - codeStr := codeString - var stack errors.StackTrace - if len(codeStrs) > 0 { - codeStr = codeStrs[0] - if code.Code().IsAncestor(errcode.InternalCode) { - stack = errcode.StackTrace(code) - } - } - t.Helper() - - jsonEquals(t, "ClientData", data, errcode.ClientData(code)) - - jsonExpected := errcode.JSONFormat{ - Data: data, - Msg: code.Error(), - Code: codeStr, - Operation: errcode.Operation(data), - Stack: stack, - } - newJSON := errcode.NewJSONFormat(code) - jsonEquals(t, "JSONFormat", jsonExpected, newJSON) -} - -func jsonEquals(t *testing.T, errPrefix string, expectedIn interface{}, gotIn interface{}) { - t.Helper() - got, err1 := json.Marshal(gotIn) - expected, err2 := json.Marshal(expectedIn) - if err1 != nil || err2 != nil { - t.Errorf("%v could not serialize to json", errPrefix) - } - if !reflect.DeepEqual(expected, got) { - t.Errorf("%v\nClientData expected: %#v\n ClientData but got: %#v", errPrefix, expected, got) - } -} - -func OpEquals(t *testing.T, code errcode.ErrorCode, op string) { - t.Helper() - opGot, _ := errcode.OperationClientData(code) - if opGot != op { - t.Errorf("\nOp expected: %#v\n Op but got: %#v", op, opGot) - } -} - -func AssertOperation(t *testing.T, v interface{}, op string) { - t.Helper() - opGot := errcode.Operation(v) - if opGot != op { - t.Errorf("\nOp expected: %#v\n Op but got: %#v", op, opGot) - } -} - -func AssertStackEquals(t *testing.T, given errcode.ErrorCode, stExpected errors.StackTrace) { - t.Helper() - stGiven := errcode.StackTrace(given) - if stGiven == nil || stExpected == nil || stGiven[0] != stExpected[0] { - t.Errorf("\nStack expected: %#v\n Stack but got: %#v", stExpected[0], stGiven[0]) - } -} diff --git a/pkg/error_code/codes.go b/vendor/github.com/pingcap/errcode/codes.go similarity index 100% rename from pkg/error_code/codes.go rename to vendor/github.com/pingcap/errcode/codes.go diff --git a/pkg/error_code/error_code.go b/vendor/github.com/pingcap/errcode/error_code.go similarity index 91% rename from pkg/error_code/error_code.go rename to vendor/github.com/pingcap/errcode/error_code.go index c433f57d550..86056e7b7ab 100644 --- a/pkg/error_code/error_code.go +++ b/vendor/github.com/pingcap/errcode/error_code.go @@ -13,8 +13,8 @@ // Package errcode facilitates standardized API error codes. // The goal is that clients can reliably understand errors by checking against immutable error codes -// A Code should never be modified once committed (and released for use by clients). -// Instead a new Code should be created. +// +// This godoc documents usage. For broader context, see https://github.com/pingcap/errcode/tree/master/README.md // // Error codes are represented as strings by CodeStr (see CodeStr documentation). // @@ -28,15 +28,18 @@ // This is used in the HTTPCode implementation to inherit HTTP codes found with MetaDataFromAncestors. // The hierarchy is present in the Code's string representation with a dot separation. // -// A few generic top-level error codes are provided here. -// You are encouraged to create your own application customized error codes rather than just using generic errors. +// A few generic top-level error codes are provided (see the variables section of the doc). +// You are encouraged to create your own error codes customized to your application rather than solely using generic errors. // // See NewJSONFormat for an opinion on how to send back meta data about errors with the error data to a client. // JSONFormat includes a body of response data (the "data field") that is by default the data from the Error // serialized to JSON. // -// Errors are traced via ErrorChainContext() which show up as the Others field in JSONFormat. // Stack traces are automatically added by NewInternalErr and show up as the Stack field in JSONFormat. +// Errors can be grouped with Combine() and ungrouped via Errors() which show up as the Others field in JSONFormat. +// +// To extract any ErrorCodes from an error, use CodeChain(). +// This extracts error codes without information loss (using ChainContext). package errcode import ( @@ -44,20 +47,23 @@ import ( "net/http" "strings" - "github.com/pkg/errors" + "github.com/pingcap/errors" ) -// CodeStr is a representation of the type of a particular error. +// CodeStr is the name of the error code. +// It is a representation of the type of a particular error. // The underlying type is string rather than int. // This enhances both extensibility (avoids merge conflicts) and user-friendliness. // A CodeStr can have dot separators indicating a hierarchy. +// +// Generally a CodeStr should never be modified once used by clients. +// Instead a new CodeStr should be created. type CodeStr string func (str CodeStr) String() string { return string(str) } // A Code has a CodeStr representation. // It is attached to a Parent to find metadata from it. -// The Meta field is provided for extensibility: e.g. attaching HTTP codes. type Code struct { // codeStr does not include parent paths // The full code (with parent paths) is accessed with CodeStr @@ -115,9 +121,9 @@ func (code Code) IsAncestor(ancestorCode Code) bool { return nil != code.findAncestor(func(an Code) bool { return an == ancestorCode }) } -// MetaData is a pattern for attaching meta data to codes and inheriting it from a parent. +// MetaData is used in a pattern for attaching meta data to codes and inheriting it from a parent. // See MetaDataFromAncestors. -// This is used to attach an HTTP code to a Code. +// This is used to attach an HTTP code to a Code as meta data. type MetaData map[CodeStr]interface{} // MetaDataFromAncestors looks for meta data starting at the current code. diff --git a/pkg/error_code/group.go b/vendor/github.com/pingcap/errcode/group.go similarity index 80% rename from pkg/error_code/group.go rename to vendor/github.com/pingcap/errcode/group.go index ce351705f77..45b4c4dca80 100644 --- a/pkg/error_code/group.go +++ b/vendor/github.com/pingcap/errcode/group.go @@ -16,7 +16,7 @@ package errcode import ( "fmt" - "github.com/pkg/errors" + "github.com/pingcap/errors" ) // ErrorCodes return all errors (from an ErrorGroup) that are of interface ErrorCode. @@ -44,7 +44,7 @@ type MultiErrCode struct { // It will combine any other MultiErrCode into just one MultiErrCode. // This is "horizontal" composition. // If you want normal "vertical" composition use BuildChain. -func Combine(initial ErrorCode, others ...ErrorCode) ErrorCode { +func Combine(initial ErrorCode, others ...ErrorCode) MultiErrCode { var rest []error if group, ok := initial.(errors.ErrorGroup); ok { rest = group.Errors() @@ -92,23 +92,29 @@ func (e MultiErrCode) GetClientData() interface{} { return ClientData(e.ErrCode) } -// ErrorCodeChain resolves an error chain down to a chain of just error codes +// CodeChain resolves an error chain down to a chain of just error codes // Any ErrorGroups found are converted to a MultiErrCode. // Passed over error inforation is retained using ChainContext. // If a code was overidden in the chain, it will show up as a MultiErrCode. -func ErrorCodeChain(err error) ErrorCode { +func CodeChain(err error) ErrorCode { var code ErrorCode currentErr := err chainErrCode := func(errcode ErrorCode) { if errcode.(error) != currentErr { - errcode = ChainContext{currentErr, errcode} + if chained, ok := errcode.(ChainContext); ok { + // Perhaps this is a hack because we should be passing the context to recursive CodeChain calls + chained.Top = currentErr + errcode = chained + } else { + errcode = ChainContext{currentErr, errcode} + } } if code == nil { code = errcode } else { code = MultiErrCode{code, []error{code.(error), errcode.(error)}} } - currentErr = errcode + currentErr = errcode.(error) } for err != nil { @@ -119,10 +125,18 @@ func ErrorCodeChain(err error) ErrorCode { } else if eg, ok := err.(errors.ErrorGroup); ok { group := []ErrorCode{} for _, errItem := range eg.Errors() { - group = append(group, ErrorCodeChain(errItem)) + if itemCode := CodeChain(errItem); itemCode != nil { + group = append(group, itemCode) + } } if len(group) > 0 { - chainErrCode(Combine(group[0], group[1:]...)) + var codeGroup ErrorCode + if len(group) == 1 { + codeGroup = group[0] + } else { + codeGroup = Combine(group[0], group[1:]...) + } + chainErrCode(codeGroup) } } err = errors.Unwrap(err) @@ -133,7 +147,8 @@ func ErrorCodeChain(err error) ErrorCode { // ChainContext is returned by ErrorCodeChain // to retain the full wrapped error message of the error chain. -// If you add context to an error code, this retains that in the Error() message. +// If you annotated an ErrorCode with additional information, it is retained in the Top field. +// The Top field is used for the Error() and Cause() methods. type ChainContext struct { Top error ErrCode ErrorCode @@ -179,11 +194,15 @@ func (err ChainContext) Format(s fmt.State, verb rune) { } return } + if s.Flag('#') { + fmt.Fprintf(s, "ChainContext{Code: %#v, Top: %#v}", err.ErrCode, err.Top) + return + } fallthrough case 's': - fmt.Fprintf(s, "Code: %s. %s", err.ErrCode.Code().CodeStr(), err.Top) + fmt.Fprintf(s, "Code: %s. Top Error: %s", err.ErrCode.Code().CodeStr(), err.Top) case 'q': - fmt.Fprintf(s, "Code: %q. %q", err.ErrCode.Code().CodeStr(), err.Top) + fmt.Fprintf(s, "Code: %q. Top Error: %q", err.ErrCode.Code().CodeStr(), err.Top) } } @@ -199,7 +218,7 @@ func (e MultiErrCode) Format(s fmt.State, verb rune) { } } else { for _, nextErr := range e.rest { - fmt.Fprintf(s, "%v", nextErr) + fmt.Fprintf(s, "%+v", nextErr) } } return diff --git a/pkg/error_code/operation.go b/vendor/github.com/pingcap/errcode/operation.go similarity index 95% rename from pkg/error_code/operation.go rename to vendor/github.com/pingcap/errcode/operation.go index 3c25d9dd2b3..30200272f72 100644 --- a/pkg/error_code/operation.go +++ b/vendor/github.com/pingcap/errcode/operation.go @@ -48,8 +48,8 @@ func (e EmbedOp) GetOperation() string { return e.Op } -// OpErrCode is an ErrorCode with an "Operation" field attached. -// This may be used as a convenience to record the operation information for the error. +// OpErrCode is an ErrorCode with an Operation field attached. +// This can be conveniently constructed with Op() and AddTo() to record the operation information for the error. // However, it isn't required to be used, see the HasOperation documentation for alternatives. type OpErrCode struct { Operation string diff --git a/pkg/error_code/stack.go b/vendor/github.com/pingcap/errcode/stack.go similarity index 99% rename from pkg/error_code/stack.go rename to vendor/github.com/pingcap/errcode/stack.go index 3866904604e..13343162d60 100644 --- a/pkg/error_code/stack.go +++ b/vendor/github.com/pingcap/errcode/stack.go @@ -14,7 +14,7 @@ package errcode import ( - "github.com/pkg/errors" + "github.com/pingcap/errors" ) // StackTrace retrieves the errors.StackTrace from the error if it is present. diff --git a/vendor/github.com/pingcap/errors/LICENSE b/vendor/github.com/pingcap/errors/LICENSE new file mode 100644 index 00000000000..835ba3e755c --- /dev/null +++ b/vendor/github.com/pingcap/errors/LICENSE @@ -0,0 +1,23 @@ +Copyright (c) 2015, Dave Cheney +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + +* Redistributions of source code must retain the above copyright notice, this + list of conditions and the following disclaimer. + +* Redistributions in binary form must reproduce the above copyright notice, + this list of conditions and the following disclaimer in the documentation + and/or other materials provided with the distribution. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE +FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER +CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, +OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. diff --git a/vendor/github.com/pingcap/errors/errors.go b/vendor/github.com/pingcap/errors/errors.go new file mode 100644 index 00000000000..2e1d3f62896 --- /dev/null +++ b/vendor/github.com/pingcap/errors/errors.go @@ -0,0 +1,324 @@ +// Package errors provides simple error handling primitives. +// +// The traditional error handling idiom in Go is roughly akin to +// +// if err != nil { +// return err +// } +// +// which applied recursively up the call stack results in error reports +// without context or debugging information. The errors package allows +// programmers to add context to the failure path in their code in a way +// that does not destroy the original value of the error. +// +// Adding context to an error +// +// The errors.Annotate function returns a new error that adds context to the +// original error by recording a stack trace at the point Annotate is called, +// and the supplied message. For example +// +// _, err := ioutil.ReadAll(r) +// if err != nil { +// return errors.Annotate(err, "read failed") +// } +// +// If additional control is required the errors.AddStack and errors.WithMessage +// functions destructure errors.Annotate into its component operations of annotating +// an error with a stack trace and an a message, respectively. +// +// Retrieving the cause of an error +// +// Using errors.Annotate constructs a stack of errors, adding context to the +// preceding error. Depending on the nature of the error it may be necessary +// to reverse the operation of errors.Annotate to retrieve the original error +// for inspection. Any error value which implements this interface +// +// type causer interface { +// Cause() error +// } +// +// can be inspected by errors.Cause. errors.Cause will recursively retrieve +// the topmost error which does not implement causer, which is assumed to be +// the original cause. For example: +// +// switch err := errors.Cause(err).(type) { +// case *MyError: +// // handle specifically +// default: +// // unknown error +// } +// +// causer interface is not exported by this package, but is considered a part +// of stable public API. +// errors.Unwrap is also available: this will retrieve the next error in the chain. +// +// Formatted printing of errors +// +// All error values returned from this package implement fmt.Formatter and can +// be formatted by the fmt package. The following verbs are supported +// +// %s print the error. If the error has a Cause it will be +// printed recursively +// %v see %s +// %+v extended format. Each Frame of the error's StackTrace will +// be printed in detail. +// +// Retrieving the stack trace of an error or wrapper +// +// New, Errorf, Annotate, and Annotatef record a stack trace at the point they are invoked. +// This information can be retrieved with the StackTracer interface that returns +// a StackTrace. Where errors.StackTrace is defined as +// +// type StackTrace []Frame +// +// The Frame type represents a call site in the stack trace. Frame supports +// the fmt.Formatter interface that can be used for printing information about +// the stack trace of this error. For example: +// +// if stacked := errors.GetStackTracer(err); stacked != nil { +// for _, f := range stacked.StackTrace() { +// fmt.Printf("%+s:%d", f) +// } +// } +// +// See the documentation for Frame.Format for more details. +// +// errors.Find can be used to search for an error in the error chain. +package errors + +import ( + "fmt" + "io" +) + +// New returns an error with the supplied message. +// New also records the stack trace at the point it was called. +func New(message string) error { + return &fundamental{ + msg: message, + stack: callers(), + } +} + +// Errorf formats according to a format specifier and returns the string +// as a value that satisfies error. +// Errorf also records the stack trace at the point it was called. +func Errorf(format string, args ...interface{}) error { + return &fundamental{ + msg: fmt.Sprintf(format, args...), + stack: callers(), + } +} + +// StackTraceAware is an optimization to avoid repetitive traversals of an error chain. +// HasStack checks for this marker first. +// Annotate/Wrap and Annotatef/Wrapf will produce this marker. +type StackTraceAware interface { + HasStack() bool +} + +// HasStack tells whether a StackTracer exists in the error chain +func HasStack(err error) bool { + if errWithStack, ok := err.(StackTraceAware); ok { + return errWithStack.HasStack() + } + return GetStackTracer(err) != nil +} + +// fundamental is an error that has a message and a stack, but no caller. +type fundamental struct { + msg string + *stack +} + +func (f *fundamental) Error() string { return f.msg } + +func (f *fundamental) Format(s fmt.State, verb rune) { + switch verb { + case 'v': + if s.Flag('+') { + io.WriteString(s, f.msg) + f.stack.Format(s, verb) + return + } + fallthrough + case 's': + io.WriteString(s, f.msg) + case 'q': + fmt.Fprintf(s, "%q", f.msg) + } +} + +// WithStack annotates err with a stack trace at the point WithStack was called. +// If err is nil, WithStack returns nil. +// +// For most use cases this is deprecated and AddStack should be used (which will ensure just one stack trace). +// However, one may want to use this in some situations, for example to create a 2nd trace across a goroutine. +func WithStack(err error) error { + if err == nil { + return nil + } + + return &withStack{ + err, + callers(), + } +} + +// AddStack is similar to WithStack. +// However, it will first check with HasStack to see if a stack trace already exists in the causer chain before creating another one. +func AddStack(err error) error { + if HasStack(err) { + return err + } + return WithStack(err) +} + +type withStack struct { + error + *stack +} + +func (w *withStack) Cause() error { return w.error } + +func (w *withStack) Format(s fmt.State, verb rune) { + switch verb { + case 'v': + if s.Flag('+') { + fmt.Fprintf(s, "%+v", w.Cause()) + w.stack.Format(s, verb) + return + } + fallthrough + case 's': + io.WriteString(s, w.Error()) + case 'q': + fmt.Fprintf(s, "%q", w.Error()) + } +} + +// Wrap returns an error annotating err with a stack trace +// at the point Wrap is called, and the supplied message. +// If err is nil, Wrap returns nil. +// +// For most use cases this is deprecated in favor of Annotate. +// Annotate avoids creating duplicate stack traces. +func Wrap(err error, message string) error { + if err == nil { + return nil + } + hasStack := HasStack(err) + err = &withMessage{ + cause: err, + msg: message, + causeHasStack: hasStack, + } + return &withStack{ + err, + callers(), + } +} + +// Wrapf returns an error annotating err with a stack trace +// at the point Wrapf is call, and the format specifier. +// If err is nil, Wrapf returns nil. +// +// For most use cases this is deprecated in favor of Annotatef. +// Annotatef avoids creating duplicate stack traces. +func Wrapf(err error, format string, args ...interface{}) error { + if err == nil { + return nil + } + hasStack := HasStack(err) + err = &withMessage{ + cause: err, + msg: fmt.Sprintf(format, args...), + causeHasStack: hasStack, + } + return &withStack{ + err, + callers(), + } +} + +// WithMessage annotates err with a new message. +// If err is nil, WithMessage returns nil. +func WithMessage(err error, message string) error { + if err == nil { + return nil + } + return &withMessage{ + cause: err, + msg: message, + causeHasStack: HasStack(err), + } +} + +type withMessage struct { + cause error + msg string + causeHasStack bool +} + +func (w *withMessage) Error() string { return w.msg + ": " + w.cause.Error() } +func (w *withMessage) Cause() error { return w.cause } +func (w *withMessage) HasStack() bool { return w.causeHasStack } + +func (w *withMessage) Format(s fmt.State, verb rune) { + switch verb { + case 'v': + if s.Flag('+') { + fmt.Fprintf(s, "%+v\n", w.Cause()) + io.WriteString(s, w.msg) + return + } + fallthrough + case 's', 'q': + io.WriteString(s, w.Error()) + } +} + +// Cause returns the underlying cause of the error, if possible. +// An error value has a cause if it implements the following +// interface: +// +// type causer interface { +// Cause() error +// } +// +// If the error does not implement Cause, the original error will +// be returned. If the error is nil, nil will be returned without further +// investigation. +func Cause(err error) error { + cause := Unwrap(err) + if cause == nil { + return err + } + return Cause(cause) +} + +// Unwrap uses causer to return the next error in the chain or nil. +// This goes one-level deeper, whereas Cause goes as far as possible +func Unwrap(err error) error { + type causer interface { + Cause() error + } + if unErr, ok := err.(causer); ok { + return unErr.Cause() + } + return nil +} + +// Find an error in the chain that matches a test function. +// returns nil if no error is found. +func Find(origErr error, test func(error) bool) error { + var foundErr error + WalkDeep(origErr, func(err error) bool { + if test(err) { + foundErr = err + return true + } + return false + }) + return foundErr +} diff --git a/vendor/github.com/pingcap/errors/group.go b/vendor/github.com/pingcap/errors/group.go new file mode 100644 index 00000000000..e5a969ab76f --- /dev/null +++ b/vendor/github.com/pingcap/errors/group.go @@ -0,0 +1,42 @@ +package errors + +// ErrorGroup is an interface for multiple errors that are not a chain. +// This happens for example when executing multiple operations in parallel. +type ErrorGroup interface { + Errors() []error +} + +// Errors uses the ErrorGroup interface to return a slice of errors. +// If the ErrorGroup interface is not implemented it returns an array containing just the given error. +func Errors(err error) []error { + if eg, ok := err.(ErrorGroup); ok { + return eg.Errors() + } + return []error{err} +} + +// WalkDeep does a depth-first traversal of all errors. +// Any ErrorGroup is traversed (after going deep). +// The visitor function can return true to end the traversal early +// In that case, WalkDeep will return true, otherwise false. +func WalkDeep(err error, visitor func(err error) bool) bool { + // Go deep + unErr := err + for unErr != nil { + if done := visitor(unErr); done { + return true + } + unErr = Unwrap(unErr) + } + + // Go wide + if group, ok := err.(ErrorGroup); ok { + for _, err := range group.Errors() { + if early := WalkDeep(err, visitor); early { + return true + } + } + } + + return false +} diff --git a/vendor/github.com/pingcap/errors/juju_adaptor.go b/vendor/github.com/pingcap/errors/juju_adaptor.go new file mode 100644 index 00000000000..0b20f57370c --- /dev/null +++ b/vendor/github.com/pingcap/errors/juju_adaptor.go @@ -0,0 +1,79 @@ +package errors + +import ( + "fmt" +) + +// ==================== juju adaptor start ======================== + +// Trace just calls AddStack. +func Trace(err error) error { + return AddStack(err) +} + +// Annotate adds a message and ensures there is a stack trace. +func Annotate(err error, message string) error { + if err == nil { + return nil + } + hasStack := HasStack(err) + err = &withMessage{ + cause: err, + msg: message, + causeHasStack: hasStack, + } + if hasStack { + return err + } + return &withStack{ + err, + callers(), + } +} + +// Annotatef adds a message and ensures there is a stack trace. +func Annotatef(err error, format string, args ...interface{}) error { + if err == nil { + return nil + } + hasStack := HasStack(err) + err = &withMessage{ + cause: err, + msg: fmt.Sprintf(format, args...), + causeHasStack: hasStack, + } + if hasStack { + return err + } + return &withStack{ + err, + callers(), + } +} + +// ErrorStack will format a stack trace if it is available, otherwise it will be Error() +// If the error is nil, the empty string is returned +// Note that this just calls fmt.Sprintf("%+v", err) +func ErrorStack(err error) string { + if err == nil { + return "" + } + return fmt.Sprintf("%+v", err) +} + +// NotFoundf represents an error with not found message. +func NotFoundf(format string, args ...interface{}) error { + return Errorf(format+" not found", args...) +} + +// BadRequestf represents an error with bad request message. +func BadRequestf(format string, args ...interface{}) error { + return Errorf(format+" bad request", args...) +} + +// NotSupportedf represents an error with not supported message. +func NotSupportedf(format string, args ...interface{}) error { + return Errorf(format+" not supported", args...) +} + +// ==================== juju adaptor end ======================== diff --git a/vendor/github.com/pingcap/errors/stack.go b/vendor/github.com/pingcap/errors/stack.go new file mode 100644 index 00000000000..bb1e6a84f33 --- /dev/null +++ b/vendor/github.com/pingcap/errors/stack.go @@ -0,0 +1,226 @@ +package errors + +import ( + "bytes" + "fmt" + "io" + "path" + "runtime" + "strconv" + "strings" +) + +// StackTracer retrieves the StackTrace +// Generally you would want to use the GetStackTracer function to do that. +type StackTracer interface { + StackTrace() StackTrace +} + +// GetStackTracer will return the first StackTracer in the causer chain. +// This function is used by AddStack to avoid creating redundant stack traces. +// +// You can also use the StackTracer interface on the returned error to get the stack trace. +func GetStackTracer(origErr error) StackTracer { + var stacked StackTracer + WalkDeep(origErr, func(err error) bool { + if stackTracer, ok := err.(StackTracer); ok { + stacked = stackTracer + return true + } + return false + }) + return stacked +} + +// Frame represents a program counter inside a stack frame. +type Frame uintptr + +// pc returns the program counter for this frame; +// multiple frames may have the same PC value. +func (f Frame) pc() uintptr { return uintptr(f) - 1 } + +// file returns the full path to the file that contains the +// function for this Frame's pc. +func (f Frame) file() string { + fn := runtime.FuncForPC(f.pc()) + if fn == nil { + return "unknown" + } + file, _ := fn.FileLine(f.pc()) + return file +} + +// line returns the line number of source code of the +// function for this Frame's pc. +func (f Frame) line() int { + fn := runtime.FuncForPC(f.pc()) + if fn == nil { + return 0 + } + _, line := fn.FileLine(f.pc()) + return line +} + +// Format formats the frame according to the fmt.Formatter interface. +// +// %s source file +// %d source line +// %n function name +// %v equivalent to %s:%d +// +// Format accepts flags that alter the printing of some verbs, as follows: +// +// %+s function name and path of source file relative to the compile time +// GOPATH separated by \n\t (\n\t) +// %+v equivalent to %+s:%d +func (f Frame) Format(s fmt.State, verb rune) { + f.format(s, s, verb) +} + +// format allows stack trace printing calls to be made with a bytes.Buffer. +func (f Frame) format(w io.Writer, s fmt.State, verb rune) { + switch verb { + case 's': + switch { + case s.Flag('+'): + pc := f.pc() + fn := runtime.FuncForPC(pc) + if fn == nil { + io.WriteString(w, "unknown") + } else { + file, _ := fn.FileLine(pc) + io.WriteString(w, fn.Name()) + io.WriteString(w, "\n\t") + io.WriteString(w, file) + } + default: + io.WriteString(w, path.Base(f.file())) + } + case 'd': + io.WriteString(w, strconv.Itoa(f.line())) + case 'n': + name := runtime.FuncForPC(f.pc()).Name() + io.WriteString(w, funcname(name)) + case 'v': + f.format(w, s, 's') + io.WriteString(w, ":") + f.format(w, s, 'd') + } +} + +// StackTrace is stack of Frames from innermost (newest) to outermost (oldest). +type StackTrace []Frame + +// Format formats the stack of Frames according to the fmt.Formatter interface. +// +// %s lists source files for each Frame in the stack +// %v lists the source file and line number for each Frame in the stack +// +// Format accepts flags that alter the printing of some verbs, as follows: +// +// %+v Prints filename, function, and line number for each Frame in the stack. +func (st StackTrace) Format(s fmt.State, verb rune) { + var b bytes.Buffer + switch verb { + case 'v': + switch { + case s.Flag('+'): + b.Grow(len(st) * stackMinLen) + for _, fr := range st { + b.WriteByte('\n') + fr.format(&b, s, verb) + } + case s.Flag('#'): + fmt.Fprintf(&b, "%#v", []Frame(st)) + default: + st.formatSlice(&b, s, verb) + } + case 's': + st.formatSlice(&b, s, verb) + } + io.Copy(s, &b) +} + +// formatSlice will format this StackTrace into the given buffer as a slice of +// Frame, only valid when called with '%s' or '%v'. +func (st StackTrace) formatSlice(b *bytes.Buffer, s fmt.State, verb rune) { + b.WriteByte('[') + if len(st) == 0 { + b.WriteByte(']') + return + } + + b.Grow(len(st) * (stackMinLen / 4)) + st[0].format(b, s, verb) + for _, fr := range st[1:] { + b.WriteByte(' ') + fr.format(b, s, verb) + } + b.WriteByte(']') +} + +// stackMinLen is a best-guess at the minimum length of a stack trace. It +// doesn't need to be exact, just give a good enough head start for the buffer +// to avoid the expensive early growth. +const stackMinLen = 96 + +// 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('+'): + var b bytes.Buffer + b.Grow(len(*s) * stackMinLen) + for _, pc := range *s { + f := Frame(pc) + b.WriteByte('\n') + f.format(&b, st, 'v') + } + io.Copy(st, &b) + } + } +} + +func (s *stack) StackTrace() StackTrace { + f := make([]Frame, len(*s)) + for i := 0; i < len(f); i++ { + f[i] = Frame((*s)[i]) + } + return f +} + +func callers() *stack { + return callersSkip(4) +} + +func callersSkip(skip int) *stack { + const depth = 32 + var pcs [depth]uintptr + n := runtime.Callers(skip, pcs[:]) + var st stack = pcs[0:n] + return &st +} + +// funcname removes the path prefix component of a function's name reported by func.Name(). +func funcname(name string) string { + i := strings.LastIndex(name, "/") + name = name[i+1:] + i = strings.Index(name, ".") + return name[i+1:] +} + +// NewStack is for library implementers that want to generate a stack trace. +// Normally you should insted use AddStack to get an error with a stack trace. +// +// The result of this function can be turned into a stack trace by calling .StackTrace() +// +// This function takes an argument for the number of stack frames to skip. +// This avoids putting stack generation function calls like this one in the stack trace. +// A value of 0 will give you the line that called NewStack(0) +// A library author wrapping this in their own function will want to use a value of at least 1. +func NewStack(skip int) StackTracer { + return callersSkip(skip + 3) +} From 8fcb0b4d4d79d98468c8db06cd726352436e345e Mon Sep 17 00:00:00 2001 From: Greg Weber Date: Tue, 25 Sep 2018 16:51:42 -0700 Subject: [PATCH 17/18] switch to separate package for errcode --- Gopkg.lock | 24 ++- Gopkg.toml | 7 +- docs/development.md | 2 +- server/api/config.go | 2 +- server/api/store.go | 2 +- server/api/util.go | 4 +- server/cluster.go | 2 +- server/core/errors.go | 2 +- server/core/store.go | 2 +- vendor/github.com/pkg/errors/errors.go | 129 +++++---------- vendor/github.com/pkg/errors/group.go | 42 ----- vendor/github.com/pkg/errors/juju_adaptor.go | 79 ---------- vendor/github.com/pkg/errors/stack.go | 156 +++++++------------ 13 files changed, 124 insertions(+), 329 deletions(-) delete mode 100644 vendor/github.com/pkg/errors/group.go delete mode 100644 vendor/github.com/pkg/errors/juju_adaptor.go diff --git a/Gopkg.lock b/Gopkg.lock index 3fb78c6ed26..31cbe9a70d0 100644 --- a/Gopkg.lock +++ b/Gopkg.lock @@ -313,6 +313,22 @@ pruneopts = "NUT" revision = "1c287c953996ab3a0bf535dba9d53d809d3dc0b6" +[[projects]] + digest = "1:cffc7d72991e2d84e164a07a0fabbc67a3fce70745d425e41245358a0c107d66" + name = "github.com/pingcap/errcode" + packages = ["."] + pruneopts = "NUT" + revision = "a1a7271709d9bea2d0a0d2aab8a97d7a2ce0ea51" + version = "v0.1" + +[[projects]] + digest = "1:677ff2ee188099669fbecf583de8c23111020adee48d71a6f60c733f6b2fea7a" + name = "github.com/pingcap/errors" + packages = ["."] + pruneopts = "NUT" + revision = "31ffda8a65b0f910c6d65f1fef40e761fd606384" + version = "v0.10.1" + [[projects]] branch = "master" digest = "1:3be99496a5d1fc018eb176c53a81df986408af94bece871d829eaceddf7c5325" @@ -326,13 +342,12 @@ revision = "279515615485b0f2d12f1421cc412fe2784e0190" [[projects]] - digest = "1:677ff2ee188099669fbecf583de8c23111020adee48d71a6f60c733f6b2fea7a" + digest = "1:5cf3f025cbee5951a4ee961de067c8a89fc95a5adabead774f82822efabab121" name = "github.com/pkg/errors" packages = ["."] pruneopts = "NUT" - revision = "31ffda8a65b0f910c6d65f1fef40e761fd606384" - source = "https://github.com/pingcap/errors" - version = "v0.10.1" + revision = "645ef00459ed84a119197bfb8d8205042c6df63d" + version = "v0.8.0" [[projects]] digest = "1:8d8f554bbb62fb7aecf661b85b25e227f6ab6cfe2b4395ea65ef478bfc174940" @@ -641,6 +656,7 @@ "github.com/montanaflynn/stats", "github.com/opentracing/opentracing-go", "github.com/pingcap/check", + "github.com/pingcap/errcode", "github.com/pingcap/kvproto/pkg/eraftpb", "github.com/pingcap/kvproto/pkg/metapb", "github.com/pingcap/kvproto/pkg/pdpb", diff --git a/Gopkg.toml b/Gopkg.toml index bb4ae391c6c..3bec080f2e3 100644 --- a/Gopkg.toml +++ b/Gopkg.toml @@ -38,8 +38,11 @@ [[constraint]] name = "github.com/pkg/errors" - version = ">= 0.10.1" - source = "https://github.com/pingcap/errors" + version = ">= 0.8.0" + +[[constraint]] + name = "github.com/pingcap/errcode" + version = ">= 0.1.0" [[override]] name = "github.com/BurntSushi/toml" diff --git a/docs/development.md b/docs/development.md index eda2905af1f..607946e057a 100644 --- a/docs/development.md +++ b/docs/development.md @@ -42,5 +42,5 @@ Consider that raml2html depends on various npm packages and can only be run unde ## Error responses -Error responses from the server are switching to using [error codes](../pkg/error_code/error_code.go). +Error responses from the server are switching to using [errcode codes](https://github.com/pingcap/errcode). The should use the `errorResp` function. Please look at other places in the codebase that use `errorResp`. diff --git a/server/api/config.go b/server/api/config.go index d05b07042c1..31a686ef515 100644 --- a/server/api/config.go +++ b/server/api/config.go @@ -20,7 +20,7 @@ import ( "net/http" "github.com/gorilla/mux" - "github.com/pingcap/pd/pkg/error_code" + "github.com/pingcap/errcode" "github.com/pingcap/pd/server" "github.com/pkg/errors" "github.com/unrolled/render" diff --git a/server/api/store.go b/server/api/store.go index 891cc8f7801..373f61cdf08 100644 --- a/server/api/store.go +++ b/server/api/store.go @@ -22,7 +22,7 @@ import ( "github.com/gorilla/mux" "github.com/pingcap/kvproto/pkg/metapb" "github.com/pingcap/pd/pkg/apiutil" - "github.com/pingcap/pd/pkg/error_code" + "github.com/pingcap/errcode" "github.com/pingcap/pd/pkg/typeutil" "github.com/pingcap/pd/server" "github.com/pingcap/pd/server/core" diff --git a/server/api/util.go b/server/api/util.go index a38dc022802..50c894526ad 100644 --- a/server/api/util.go +++ b/server/api/util.go @@ -21,7 +21,7 @@ import ( "net/http" "github.com/pingcap/pd/pkg/apiutil" - "github.com/pingcap/pd/pkg/error_code" + "github.com/pingcap/errcode" "github.com/pingcap/pd/server" "github.com/pkg/errors" log "github.com/sirupsen/logrus" @@ -40,7 +40,7 @@ func errorResp(rd *render.Render, w http.ResponseWriter, err error) { rd.JSON(w, http.StatusInternalServerError, "nil error") return } - if errCode := errcode.ErrorCodeChain(err); errCode != nil { + if errCode := errcode.CodeChain(err); errCode != nil { w.Header().Set("TiDB-Error-Code", errCode.Code().CodeStr().String()) rd.JSON(w, errCode.Code().HTTPCode(), errcode.NewJSONFormat(errCode)) } else { diff --git a/server/cluster.go b/server/cluster.go index f4e849391d8..f7e940149cb 100644 --- a/server/cluster.go +++ b/server/cluster.go @@ -21,7 +21,7 @@ import ( "github.com/pingcap/kvproto/pkg/metapb" "github.com/pingcap/kvproto/pkg/pdpb" - "github.com/pingcap/pd/pkg/error_code" + "github.com/pingcap/errcode" "github.com/pingcap/pd/pkg/logutil" "github.com/pingcap/pd/server/core" "github.com/pingcap/pd/server/namespace" diff --git a/server/core/errors.go b/server/core/errors.go index 30de0dba901..a58b9adcc53 100644 --- a/server/core/errors.go +++ b/server/core/errors.go @@ -20,7 +20,7 @@ import ( "fmt" "net/http" - "github.com/pingcap/pd/pkg/error_code" + "github.com/pingcap/errcode" ) var ( diff --git a/server/core/store.go b/server/core/store.go index e619201e881..bc3578de04b 100644 --- a/server/core/store.go +++ b/server/core/store.go @@ -23,7 +23,7 @@ import ( "github.com/gogo/protobuf/proto" "github.com/pingcap/kvproto/pkg/metapb" "github.com/pingcap/kvproto/pkg/pdpb" - "github.com/pingcap/pd/pkg/error_code" + "github.com/pingcap/errcode" log "github.com/sirupsen/logrus" ) diff --git a/vendor/github.com/pkg/errors/errors.go b/vendor/github.com/pkg/errors/errors.go index 2e1d3f62896..842ee80456d 100644 --- a/vendor/github.com/pkg/errors/errors.go +++ b/vendor/github.com/pkg/errors/errors.go @@ -13,24 +13,24 @@ // // Adding context to an error // -// The errors.Annotate function returns a new error that adds context to the -// original error by recording a stack trace at the point Annotate is called, +// The errors.Wrap function returns a new error that adds context to the +// original error by recording a stack trace at the point Wrap is called, // and the supplied message. For example // // _, err := ioutil.ReadAll(r) // if err != nil { -// return errors.Annotate(err, "read failed") +// return errors.Wrap(err, "read failed") // } // -// If additional control is required the errors.AddStack and errors.WithMessage -// functions destructure errors.Annotate into its component operations of annotating +// If additional control is required the errors.WithStack and errors.WithMessage +// functions destructure errors.Wrap into its component operations of annotating // an error with a stack trace and an a message, respectively. // // Retrieving the cause of an error // -// Using errors.Annotate constructs a stack of errors, adding context to the +// Using errors.Wrap constructs a stack of errors, adding context to the // preceding error. Depending on the nature of the error it may be necessary -// to reverse the operation of errors.Annotate to retrieve the original error +// to reverse the operation of errors.Wrap to retrieve the original error // for inspection. Any error value which implements this interface // // type causer interface { @@ -50,7 +50,6 @@ // // causer interface is not exported by this package, but is considered a part // of stable public API. -// errors.Unwrap is also available: this will retrieve the next error in the chain. // // Formatted printing of errors // @@ -65,9 +64,14 @@ // // Retrieving the stack trace of an error or wrapper // -// New, Errorf, Annotate, and Annotatef record a stack trace at the point they are invoked. -// This information can be retrieved with the StackTracer interface that returns -// a StackTrace. Where errors.StackTrace is defined as +// New, Errorf, Wrap, and Wrapf record a stack trace at the point they are +// invoked. This information can be retrieved with the following interface. +// +// type stackTracer interface { +// StackTrace() errors.StackTrace +// } +// +// Where errors.StackTrace is defined as // // type StackTrace []Frame // @@ -75,15 +79,16 @@ // the fmt.Formatter interface that can be used for printing information about // the stack trace of this error. For example: // -// if stacked := errors.GetStackTracer(err); stacked != nil { -// for _, f := range stacked.StackTrace() { +// if err, ok := err.(stackTracer); ok { +// for _, f := range err.StackTrace() { // fmt.Printf("%+s:%d", f) // } // } // -// See the documentation for Frame.Format for more details. +// stackTracer interface is not exported by this package, but is considered a part +// of stable public API. // -// errors.Find can be used to search for an error in the error chain. +// See the documentation for Frame.Format for more details. package errors import ( @@ -110,21 +115,6 @@ func Errorf(format string, args ...interface{}) error { } } -// StackTraceAware is an optimization to avoid repetitive traversals of an error chain. -// HasStack checks for this marker first. -// Annotate/Wrap and Annotatef/Wrapf will produce this marker. -type StackTraceAware interface { - HasStack() bool -} - -// HasStack tells whether a StackTracer exists in the error chain -func HasStack(err error) bool { - if errWithStack, ok := err.(StackTraceAware); ok { - return errWithStack.HasStack() - } - return GetStackTracer(err) != nil -} - // fundamental is an error that has a message and a stack, but no caller. type fundamental struct { msg string @@ -151,29 +141,16 @@ func (f *fundamental) Format(s fmt.State, verb rune) { // WithStack annotates err with a stack trace at the point WithStack was called. // If err is nil, WithStack returns nil. -// -// For most use cases this is deprecated and AddStack should be used (which will ensure just one stack trace). -// However, one may want to use this in some situations, for example to create a 2nd trace across a goroutine. func WithStack(err error) error { if err == nil { return nil } - return &withStack{ err, callers(), } } -// AddStack is similar to WithStack. -// However, it will first check with HasStack to see if a stack trace already exists in the causer chain before creating another one. -func AddStack(err error) error { - if HasStack(err) { - return err - } - return WithStack(err) -} - type withStack struct { error *stack @@ -200,18 +177,13 @@ func (w *withStack) Format(s fmt.State, verb rune) { // Wrap returns an error annotating err with a stack trace // at the point Wrap is called, and the supplied message. // If err is nil, Wrap returns nil. -// -// For most use cases this is deprecated in favor of Annotate. -// Annotate avoids creating duplicate stack traces. func Wrap(err error, message string) error { if err == nil { return nil } - hasStack := HasStack(err) err = &withMessage{ - cause: err, - msg: message, - causeHasStack: hasStack, + cause: err, + msg: message, } return &withStack{ err, @@ -222,18 +194,13 @@ func Wrap(err error, message string) error { // Wrapf returns an error annotating err with a stack trace // at the point Wrapf is call, and the format specifier. // If err is nil, Wrapf returns nil. -// -// For most use cases this is deprecated in favor of Annotatef. -// Annotatef avoids creating duplicate stack traces. func Wrapf(err error, format string, args ...interface{}) error { if err == nil { return nil } - hasStack := HasStack(err) err = &withMessage{ - cause: err, - msg: fmt.Sprintf(format, args...), - causeHasStack: hasStack, + cause: err, + msg: fmt.Sprintf(format, args...), } return &withStack{ err, @@ -248,21 +215,18 @@ func WithMessage(err error, message string) error { return nil } return &withMessage{ - cause: err, - msg: message, - causeHasStack: HasStack(err), + cause: err, + msg: message, } } type withMessage struct { - cause error - msg string - causeHasStack bool + cause error + msg string } -func (w *withMessage) Error() string { return w.msg + ": " + w.cause.Error() } -func (w *withMessage) Cause() error { return w.cause } -func (w *withMessage) HasStack() bool { return w.causeHasStack } +func (w *withMessage) Error() string { return w.msg + ": " + w.cause.Error() } +func (w *withMessage) Cause() error { return w.cause } func (w *withMessage) Format(s fmt.State, verb rune) { switch verb { @@ -290,35 +254,16 @@ func (w *withMessage) Format(s fmt.State, verb rune) { // be returned. If the error is nil, nil will be returned without further // investigation. func Cause(err error) error { - cause := Unwrap(err) - if cause == nil { - return err - } - return Cause(cause) -} - -// Unwrap uses causer to return the next error in the chain or nil. -// This goes one-level deeper, whereas Cause goes as far as possible -func Unwrap(err error) error { type causer interface { Cause() error } - if unErr, ok := err.(causer); ok { - return unErr.Cause() - } - return nil -} -// Find an error in the chain that matches a test function. -// returns nil if no error is found. -func Find(origErr error, test func(error) bool) error { - var foundErr error - WalkDeep(origErr, func(err error) bool { - if test(err) { - foundErr = err - return true + for err != nil { + cause, ok := err.(causer) + if !ok { + break } - return false - }) - return foundErr + err = cause.Cause() + } + return err } diff --git a/vendor/github.com/pkg/errors/group.go b/vendor/github.com/pkg/errors/group.go deleted file mode 100644 index e5a969ab76f..00000000000 --- a/vendor/github.com/pkg/errors/group.go +++ /dev/null @@ -1,42 +0,0 @@ -package errors - -// ErrorGroup is an interface for multiple errors that are not a chain. -// This happens for example when executing multiple operations in parallel. -type ErrorGroup interface { - Errors() []error -} - -// Errors uses the ErrorGroup interface to return a slice of errors. -// If the ErrorGroup interface is not implemented it returns an array containing just the given error. -func Errors(err error) []error { - if eg, ok := err.(ErrorGroup); ok { - return eg.Errors() - } - return []error{err} -} - -// WalkDeep does a depth-first traversal of all errors. -// Any ErrorGroup is traversed (after going deep). -// The visitor function can return true to end the traversal early -// In that case, WalkDeep will return true, otherwise false. -func WalkDeep(err error, visitor func(err error) bool) bool { - // Go deep - unErr := err - for unErr != nil { - if done := visitor(unErr); done { - return true - } - unErr = Unwrap(unErr) - } - - // Go wide - if group, ok := err.(ErrorGroup); ok { - for _, err := range group.Errors() { - if early := WalkDeep(err, visitor); early { - return true - } - } - } - - return false -} diff --git a/vendor/github.com/pkg/errors/juju_adaptor.go b/vendor/github.com/pkg/errors/juju_adaptor.go deleted file mode 100644 index 0b20f57370c..00000000000 --- a/vendor/github.com/pkg/errors/juju_adaptor.go +++ /dev/null @@ -1,79 +0,0 @@ -package errors - -import ( - "fmt" -) - -// ==================== juju adaptor start ======================== - -// Trace just calls AddStack. -func Trace(err error) error { - return AddStack(err) -} - -// Annotate adds a message and ensures there is a stack trace. -func Annotate(err error, message string) error { - if err == nil { - return nil - } - hasStack := HasStack(err) - err = &withMessage{ - cause: err, - msg: message, - causeHasStack: hasStack, - } - if hasStack { - return err - } - return &withStack{ - err, - callers(), - } -} - -// Annotatef adds a message and ensures there is a stack trace. -func Annotatef(err error, format string, args ...interface{}) error { - if err == nil { - return nil - } - hasStack := HasStack(err) - err = &withMessage{ - cause: err, - msg: fmt.Sprintf(format, args...), - causeHasStack: hasStack, - } - if hasStack { - return err - } - return &withStack{ - err, - callers(), - } -} - -// ErrorStack will format a stack trace if it is available, otherwise it will be Error() -// If the error is nil, the empty string is returned -// Note that this just calls fmt.Sprintf("%+v", err) -func ErrorStack(err error) string { - if err == nil { - return "" - } - return fmt.Sprintf("%+v", err) -} - -// NotFoundf represents an error with not found message. -func NotFoundf(format string, args ...interface{}) error { - return Errorf(format+" not found", args...) -} - -// BadRequestf represents an error with bad request message. -func BadRequestf(format string, args ...interface{}) error { - return Errorf(format+" bad request", args...) -} - -// NotSupportedf represents an error with not supported message. -func NotSupportedf(format string, args ...interface{}) error { - return Errorf(format+" not supported", args...) -} - -// ==================== juju adaptor end ======================== diff --git a/vendor/github.com/pkg/errors/stack.go b/vendor/github.com/pkg/errors/stack.go index bb1e6a84f33..6b1f2891a5a 100644 --- a/vendor/github.com/pkg/errors/stack.go +++ b/vendor/github.com/pkg/errors/stack.go @@ -1,37 +1,13 @@ package errors import ( - "bytes" "fmt" "io" "path" "runtime" - "strconv" "strings" ) -// StackTracer retrieves the StackTrace -// Generally you would want to use the GetStackTracer function to do that. -type StackTracer interface { - StackTrace() StackTrace -} - -// GetStackTracer will return the first StackTracer in the causer chain. -// This function is used by AddStack to avoid creating redundant stack traces. -// -// You can also use the StackTracer interface on the returned error to get the stack trace. -func GetStackTracer(origErr error) StackTracer { - var stacked StackTracer - WalkDeep(origErr, func(err error) bool { - if stackTracer, ok := err.(StackTracer); ok { - stacked = stackTracer - return true - } - return false - }) - return stacked -} - // Frame represents a program counter inside a stack frame. type Frame uintptr @@ -70,15 +46,9 @@ func (f Frame) line() int { // // Format accepts flags that alter the printing of some verbs, as follows: // -// %+s function name and path of source file relative to the compile time -// GOPATH separated by \n\t (\n\t) +// %+s path of source file relative to the compile time GOPATH // %+v equivalent to %+s:%d func (f Frame) Format(s fmt.State, verb rune) { - f.format(s, s, verb) -} - -// format allows stack trace printing calls to be made with a bytes.Buffer. -func (f Frame) format(w io.Writer, s fmt.State, verb rune) { switch verb { case 's': switch { @@ -86,84 +56,47 @@ func (f Frame) format(w io.Writer, s fmt.State, verb rune) { pc := f.pc() fn := runtime.FuncForPC(pc) if fn == nil { - io.WriteString(w, "unknown") + io.WriteString(s, "unknown") } else { file, _ := fn.FileLine(pc) - io.WriteString(w, fn.Name()) - io.WriteString(w, "\n\t") - io.WriteString(w, file) + fmt.Fprintf(s, "%s\n\t%s", fn.Name(), file) } default: - io.WriteString(w, path.Base(f.file())) + io.WriteString(s, path.Base(f.file())) } case 'd': - io.WriteString(w, strconv.Itoa(f.line())) + fmt.Fprintf(s, "%d", f.line()) case 'n': name := runtime.FuncForPC(f.pc()).Name() - io.WriteString(w, funcname(name)) + io.WriteString(s, funcname(name)) case 'v': - f.format(w, s, 's') - io.WriteString(w, ":") - f.format(w, s, 'd') + f.Format(s, 's') + io.WriteString(s, ":") + f.Format(s, 'd') } } // StackTrace is stack of Frames from innermost (newest) to outermost (oldest). type StackTrace []Frame -// Format formats the stack of Frames according to the fmt.Formatter interface. -// -// %s lists source files for each Frame in the stack -// %v lists the source file and line number for each Frame in the stack -// -// Format accepts flags that alter the printing of some verbs, as follows: -// -// %+v Prints filename, function, and line number for each Frame in the stack. func (st StackTrace) Format(s fmt.State, verb rune) { - var b bytes.Buffer switch verb { case 'v': switch { case s.Flag('+'): - b.Grow(len(st) * stackMinLen) - for _, fr := range st { - b.WriteByte('\n') - fr.format(&b, s, verb) + for _, f := range st { + fmt.Fprintf(s, "\n%+v", f) } case s.Flag('#'): - fmt.Fprintf(&b, "%#v", []Frame(st)) + fmt.Fprintf(s, "%#v", []Frame(st)) default: - st.formatSlice(&b, s, verb) + fmt.Fprintf(s, "%v", []Frame(st)) } case 's': - st.formatSlice(&b, s, verb) + fmt.Fprintf(s, "%s", []Frame(st)) } - io.Copy(s, &b) } -// formatSlice will format this StackTrace into the given buffer as a slice of -// Frame, only valid when called with '%s' or '%v'. -func (st StackTrace) formatSlice(b *bytes.Buffer, s fmt.State, verb rune) { - b.WriteByte('[') - if len(st) == 0 { - b.WriteByte(']') - return - } - - b.Grow(len(st) * (stackMinLen / 4)) - st[0].format(b, s, verb) - for _, fr := range st[1:] { - b.WriteByte(' ') - fr.format(b, s, verb) - } - b.WriteByte(']') -} - -// stackMinLen is a best-guess at the minimum length of a stack trace. It -// doesn't need to be exact, just give a good enough head start for the buffer -// to avoid the expensive early growth. -const stackMinLen = 96 - // stack represents a stack of program counters. type stack []uintptr @@ -172,14 +105,10 @@ func (s *stack) Format(st fmt.State, verb rune) { case 'v': switch { case st.Flag('+'): - var b bytes.Buffer - b.Grow(len(*s) * stackMinLen) for _, pc := range *s { f := Frame(pc) - b.WriteByte('\n') - f.format(&b, st, 'v') + fmt.Fprintf(st, "\n%+v", f) } - io.Copy(st, &b) } } } @@ -193,13 +122,9 @@ func (s *stack) StackTrace() StackTrace { } func callers() *stack { - return callersSkip(4) -} - -func callersSkip(skip int) *stack { const depth = 32 var pcs [depth]uintptr - n := runtime.Callers(skip, pcs[:]) + n := runtime.Callers(3, pcs[:]) var st stack = pcs[0:n] return &st } @@ -212,15 +137,42 @@ func funcname(name string) string { return name[i+1:] } -// NewStack is for library implementers that want to generate a stack trace. -// Normally you should insted use AddStack to get an error with a stack trace. -// -// The result of this function can be turned into a stack trace by calling .StackTrace() -// -// This function takes an argument for the number of stack frames to skip. -// This avoids putting stack generation function calls like this one in the stack trace. -// A value of 0 will give you the line that called NewStack(0) -// A library author wrapping this in their own function will want to use a value of at least 1. -func NewStack(skip int) StackTracer { - return callersSkip(skip + 3) +func trimGOPATH(name, file string) string { + // Here we want to get the source file path relative to the compile time + // GOPATH. As of Go 1.6.x there is no direct way to know the compiled + // GOPATH at runtime, but we can infer the number of path segments in the + // GOPATH. We note that fn.Name() returns the function name qualified by + // the import path, which does not include the GOPATH. Thus we can trim + // segments from the beginning of the file path until the number of path + // separators remaining is one more than the number of path separators in + // the function name. For example, given: + // + // GOPATH /home/user + // file /home/user/src/pkg/sub/file.go + // fn.Name() pkg/sub.Type.Method + // + // We want to produce: + // + // pkg/sub/file.go + // + // From this we can easily see that fn.Name() has one less path separator + // than our desired output. We count separators from the end of the file + // path until it finds two more than in the function name and then move + // one character forward to preserve the initial path segment without a + // leading separator. + const sep = "/" + goal := strings.Count(name, sep) + 2 + i := len(file) + for n := 0; n < goal; n++ { + i = strings.LastIndex(file[:i], sep) + if i == -1 { + // not enough separators found, set i so that the slice expression + // below leaves file unmodified + i = -len(sep) + break + } + } + // get back to 0 or trim the leading separator + file = file[i+len(sep):] + return file } From 55f396717d11dbbbdbadd4af187ac30e50515531 Mon Sep 17 00:00:00 2001 From: Greg Weber Date: Tue, 25 Sep 2018 17:03:27 -0700 Subject: [PATCH 18/18] fix gofmt --- server/api/store.go | 2 +- server/api/util.go | 2 +- server/cluster.go | 2 +- server/core/store.go | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/server/api/store.go b/server/api/store.go index 373f61cdf08..267088c3bb8 100644 --- a/server/api/store.go +++ b/server/api/store.go @@ -20,9 +20,9 @@ import ( "time" "github.com/gorilla/mux" + "github.com/pingcap/errcode" "github.com/pingcap/kvproto/pkg/metapb" "github.com/pingcap/pd/pkg/apiutil" - "github.com/pingcap/errcode" "github.com/pingcap/pd/pkg/typeutil" "github.com/pingcap/pd/server" "github.com/pingcap/pd/server/core" diff --git a/server/api/util.go b/server/api/util.go index 50c894526ad..d8b5ae5fb6a 100644 --- a/server/api/util.go +++ b/server/api/util.go @@ -20,8 +20,8 @@ import ( "io/ioutil" "net/http" - "github.com/pingcap/pd/pkg/apiutil" "github.com/pingcap/errcode" + "github.com/pingcap/pd/pkg/apiutil" "github.com/pingcap/pd/server" "github.com/pkg/errors" log "github.com/sirupsen/logrus" diff --git a/server/cluster.go b/server/cluster.go index f7e940149cb..a8926060f3b 100644 --- a/server/cluster.go +++ b/server/cluster.go @@ -19,9 +19,9 @@ import ( "sync" "time" + "github.com/pingcap/errcode" "github.com/pingcap/kvproto/pkg/metapb" "github.com/pingcap/kvproto/pkg/pdpb" - "github.com/pingcap/errcode" "github.com/pingcap/pd/pkg/logutil" "github.com/pingcap/pd/server/core" "github.com/pingcap/pd/server/namespace" diff --git a/server/core/store.go b/server/core/store.go index bc3578de04b..767f6e8c521 100644 --- a/server/core/store.go +++ b/server/core/store.go @@ -21,9 +21,9 @@ import ( "time" "github.com/gogo/protobuf/proto" + "github.com/pingcap/errcode" "github.com/pingcap/kvproto/pkg/metapb" "github.com/pingcap/kvproto/pkg/pdpb" - "github.com/pingcap/errcode" log "github.com/sirupsen/logrus" )