| // Copyright 2015 The Vanadium Authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style |
| // license that can be found in the LICENSE file. |
| |
| // Package verror implements an error reporting mechanism that works across |
| // programming environments, and a set of common errors. |
| // |
| // Each error has an identifier string, which is used for equality checks. |
| // E.g. a Javascript client can check if a Go server returned a NoExist error by |
| // checking the string identifier. Error identifier strings start with the VDL |
| // package path to ensure uniqueness, e.g. "v.io/v23/verror.NoExist". |
| // |
| // Each error contains an action, which is the suggested action for a typical |
| // client to perform upon receiving the error. E.g. some action codes represent |
| // whether to retry the operation after receiving the error. |
| // |
| // Each error also contains a list of typed parameters, and an error message. |
| // The error message is created by looking up a format string keyed on the error |
| // identifier, and applying the parameters to the format string. This enables |
| // error messages to be generated in different languages. |
| // |
| // Examples |
| // |
| // To define a new error identifier, for example "someNewError", client code is |
| // expected to declare a variable like this: |
| // var someNewError = verror.Register("my/package/name.someNewError", NoRetry, |
| // "{1} {2} English text for new error") |
| // Text for other languages can be added to the default i18n Catalogue. |
| // |
| // If the error should cause a client to retry, consider replacing "NoRetry" with |
| // one of the other Action codes below. |
| // |
| // Errors are given parameters when used. Conventionally, the first parameter |
| // is the name of the component (typically server or binary name), and the |
| // second is the name of the operation (such as an RPC or subcommand) that |
| // encountered the error. Other parameters typically identify the object(s) on |
| // which the error occurred. This convention is normally applied by New(), |
| // which fetches the language, component name and operation name from the |
| // context.T: |
| // err = verror.New(someNewError, ctx, "object_on_which_error_occurred") |
| // |
| // The ExplicitNew() call can be used to specify these things explicitly: |
| // err = verror.ExplicitNew(someNewError, i18n.GetLangID(ctx), |
| // "my_component", "op_name", "procedure_name", "object_name") |
| // If the language, component and/or operation name are unknown, use i18n.NoLangID |
| // or the empty string, respectively. |
| // |
| // Because of the convention for the first two parameters, messages in the |
| // catalogue typically look like this (at least for left-to-right languages): |
| // {1} {2} The new error {_} |
| // The tokens {1}, {2}, etc. refer to the first and second positional parameters |
| // respectively, while {_} is replaced by the positional parameters not |
| // explicitly referred to elsewhere in the message. Thus, given the parameters |
| // above, this would lead to the output: |
| // my_component op_name The new error object_name |
| // |
| // If a substring is of the form {:<number>}, {<number>:}, {:<number>:}, {:_}, |
| // {_:}, or {:_:}, and the corresponding parameters are not the empty string, |
| // the parameter is preceded by ": " or followed by ":" or both, |
| // respectively. For example, if the format: |
| // {3:} foo {2} bar{:_} ({3}) |
| // is used with the cat.Format example above, it yields: |
| // 3rd: foo 2nd bar: 1st 4th (3rd) |
| // |
| // The Convert() and ExplicitConvert() calls are like New() and ExplicitNew(), |
| // but convert existing errors (with their parameters, if applicable) |
| // to verror errors with given language, component name, and operation name, |
| // if non-empty values for these are provided. They also add a PC to a |
| // list of PC values to assist developers hunting for the error. |
| // |
| // If the context.T specified with New() or Convert() is nil, a default |
| // context is used, set by SetDefaultContext(). This can be used in standalone |
| // programmes, or in anciliary threads not associated with an RPC. The user |
| // might do the following to get the language from the environment, and the |
| // programme name from Args[0]: |
| // ctx := runtime.NewContext() |
| // ctx = i18n.WithLangID(ctx, i18n.LangIDFromEnv()) |
| // ctx = verror.WithComponentName(ctx, os.Args[0]) |
| // verror.SetDefaultContext(ctx) |
| // A standalone tool might set the operation name to be a subcommand name, if |
| // any. If the default context has not been set, the error generated has no |
| // language, component and operation values; they will be filled in by the |
| // first Convert() call that does have these values. |
| package verror |
| |
| import "bytes" |
| import "fmt" |
| import "io" |
| import "os" |
| import "path/filepath" |
| import "runtime" |
| |
| import "sync" |
| |
| import "v.io/v23/context" |
| import "v.io/v23/i18n" |
| import "v.io/v23/vtrace" |
| |
| // ID is a unique identifier for errors. |
| type ID string |
| |
| // An ActionCode represents the action expected to be performed by a typical client |
| // receiving an error that perhaps it does not understand. |
| type ActionCode uint32 |
| |
| // Codes for ActionCode. |
| const ( |
| // Retry actions are encoded in the bottom few bits. |
| RetryActionMask ActionCode = 3 |
| |
| NoRetry ActionCode = 0 // Do not retry. |
| RetryConnection ActionCode = 1 // Renew high-level connection/context. |
| RetryRefetch ActionCode = 2 // Refetch and retry (e.g., out of date HTTP ETag) |
| RetryBackoff ActionCode = 3 // Backoff and retry a finite number of times. |
| ) |
| |
| // RetryAction returns the part of the ActionCode that indicates retry behaviour. |
| func (ac ActionCode) RetryAction() ActionCode { |
| return ac & RetryActionMask |
| } |
| |
| // An IDAction combines a unique identifier ID for errors with an ActionCode. |
| // The ID allows stable error checking across different error messages and |
| // different address spaces. By convention the format for the identifier is |
| // "PKGPATH.NAME" - e.g. ErrIDFoo defined in the "v23/verror" package has id |
| // "v23/verror.ErrIDFoo". It is unwise ever to create two IDActions that |
| // associate different ActionCodes with the same ID. |
| type IDAction struct { |
| ID ID |
| Action ActionCode |
| } |
| |
| // Register returns a IDAction with the given ID and Action fields, and |
| // inserts a message into the default i18n Catalogue in US English. |
| // Other languages can be added by adding to the Catalogue. |
| func Register(id ID, action ActionCode, englishText string) IDAction { |
| i18n.Cat().SetWithBase(defaultLangID(i18n.NoLangID), i18n.MsgID(id), englishText) |
| return IDAction{id, action} |
| } |
| |
| // E is the in-memory representation of a verror error. |
| // |
| // The wire representation is defined as vdl.WireError; values of E |
| // type are automatically converted to/from vdl.WireError by VDL and VOM. |
| type E struct { |
| ID ID // The identity of the error. |
| Action ActionCode // Default action to take on error. |
| Msg string // Error message; empty if no language known. |
| ParamList []interface{} // The variadic parameters given to ExplicitNew(). |
| stackPCs []uintptr // PCs of creators of E |
| chainedPCs []uintptr // PCs of a chained E |
| } |
| |
| const maxPCs = 40 // Maximum number of PC values we'll include in a stack trace. |
| |
| // A SubErrs is a special type that allows clients to include a list of |
| // subordinate errors to an error's parameter list. Clients can add a SubErrs |
| // to the parameter list directly, via New() of include one in an existing |
| // error using AddSubErrs(). Each element of the slice has a name, an error, |
| // and an integer that encodes options such as verror.Print as bits set within |
| // it. By convention, clients are expected to use name of the form "X=Y" to |
| // distinguish their subordinate errors from those of other abstraction layers. |
| // For example, a layer reporting on errors in individual blessings in an RPC |
| // might use strings like "blessing=<blessing_name>". |
| type SubErrs []SubErr |
| |
| type SubErrOpts uint32 |
| |
| // A SubErr represents a (string, error, int32) triple, It is the element type for SubErrs. |
| type SubErr struct { |
| Name string |
| Err error |
| Options SubErrOpts |
| } |
| |
| const ( |
| // Print, when set in SubErr.Options, tells Error() to print this SubErr. |
| Print SubErrOpts = 0x1 |
| ) |
| |
| func assertIsE(err error) (E, bool) { |
| if e, ok := err.(E); ok { |
| return e, true |
| } |
| if e, ok := err.(*E); ok && e != nil { |
| return *e, true |
| } |
| return E{}, false |
| } |
| |
| // ErrorID returns the ID of the given err, or Unknown if the err has no ID. |
| // If err is nil then ErrorID returns "". |
| func ErrorID(err error) ID { |
| if err == nil { |
| return "" |
| } |
| if e, ok := assertIsE(err); ok { |
| return e.ID |
| } |
| return ErrUnknown.ID |
| } |
| |
| // Action returns the action of the given err, or NoRetry if the err has no Action. |
| func Action(err error) ActionCode { |
| if err == nil { |
| return NoRetry |
| } |
| if e, ok := assertIsE(err); ok { |
| return e.Action |
| } |
| return NoRetry |
| } |
| |
| // PCs represents a list of PC locations |
| type PCs []uintptr |
| |
| // Stack returns the list of PC locations on the stack when this error was |
| // first generated within this address space, or an empty list if err is not an |
| // E. |
| func Stack(err error) PCs { |
| if err != nil { |
| if e, ok := assertIsE(err); ok { |
| stackIntPtr := make([]uintptr, len(e.stackPCs)) |
| copy(stackIntPtr, e.stackPCs) |
| if e.chainedPCs != nil { |
| stackIntPtr = append(stackIntPtr, uintptr(0)) |
| stackIntPtr = append(stackIntPtr, e.chainedPCs...) |
| } |
| return stackIntPtr |
| } |
| } |
| return nil |
| } |
| |
| func (st PCs) String() string { |
| buf := bytes.NewBufferString("") |
| StackToText(buf, st) |
| return buf.String() |
| } |
| |
| // stackToTextIndent emits on w a text representation of stack, which is typically |
| // obtained from Stack() and represents the source location(s) where an |
| // error was generated or passed through in the local address space. |
| // indent is added to a prefix of each line printed. |
| func stackToTextIndent(w io.Writer, stack []uintptr, indent string) (err error) { |
| for i := 0; i != len(stack) && err == nil; i++ { |
| if stack[i] == 0 { |
| _, err = fmt.Fprintf(w, "%s----- chained verror -----\n", indent) |
| } else { |
| fnc := runtime.FuncForPC(stack[i]) |
| file, line := fnc.FileLine(stack[i]) |
| _, err = fmt.Fprintf(w, "%s%s:%d: %s\n", indent, file, line, fnc.Name()) |
| } |
| } |
| return err |
| } |
| |
| // StackToText emits on w a text representation of stack, which is typically |
| // obtained from Stack() and represents the source location(s) where an |
| // error was generated or passed through in the local address space. |
| func StackToText(w io.Writer, stack []uintptr) error { |
| return stackToTextIndent(w, stack, "") |
| } |
| |
| // defaultLangID returns langID is it is not i18n.NoLangID, and the default |
| // value of US English otherwise. |
| func defaultLangID(langID i18n.LangID) i18n.LangID { |
| if langID == i18n.NoLangID { |
| langID = "en-US" |
| } |
| return langID |
| } |
| |
| func isDefaultIDAction(id ID, action ActionCode) bool { |
| return id == "" && action == 0 |
| } |
| |
| // makeInternal is like ExplicitNew(), but takes a slice of PC values as an argument, |
| // rather than constructing one from the caller's PC. |
| func makeInternal(idAction IDAction, langID i18n.LangID, componentName string, opName string, stack []uintptr, v ...interface{}) E { |
| msg := "" |
| var chainedPCs []uintptr |
| for _, par := range v { |
| if err, ok := par.(error); ok { |
| if _, ok := assertIsE(err); ok { |
| chainedPCs = Stack(err) |
| break |
| } |
| } |
| } |
| params := append([]interface{}{componentName, opName}, v...) |
| if langID != i18n.NoLangID { |
| id := idAction.ID |
| if id == "" { |
| id = ErrUnknown.ID |
| } |
| msg = i18n.Cat().Format(langID, i18n.MsgID(id), params...) |
| } |
| return E{idAction.ID, idAction.Action, msg, params, stack, chainedPCs} |
| } |
| |
| // ExplicitNew returns an error with the given ID, with an error string in the chosen |
| // language. The component and operation name are included the first and second |
| // parameters of the error. Other parameters are taken from v[]. The |
| // parameters are formatted into the message according to i18n.Cat().Format. |
| // The caller's PC is added to the error's stack. |
| // If the parameter list contains an instance of verror.E, then the stack of |
| // the first, and only the first, occurrence of such an instance, will be chained |
| // to the stack of this newly created error. |
| func ExplicitNew(idAction IDAction, langID i18n.LangID, componentName string, opName string, v ...interface{}) error { |
| stack := make([]uintptr, maxPCs) |
| stack = stack[:runtime.Callers(2, stack)] |
| return makeInternal(idAction, langID, componentName, opName, stack, v...) |
| } |
| |
| // A componentKey is used as a key for context.T's Value() map. |
| type componentKey struct{} |
| |
| // WithComponentName returns a context based on ctx that has the |
| // componentName that New() and Convert() can use. |
| func WithComponentName(ctx *context.T, componentName string) *context.T { |
| return context.WithValue(ctx, componentKey{}, componentName) |
| } |
| |
| // New is like ExplicitNew(), but obtains the language, component name, and operation |
| // name from the specified context.T. ctx may be nil. |
| func New(idAction IDAction, ctx *context.T, v ...interface{}) error { |
| langID, componentName, opName := dataFromContext(ctx) |
| stack := make([]uintptr, maxPCs) |
| stack = stack[:runtime.Callers(2, stack)] |
| return makeInternal(idAction, langID, componentName, opName, stack, v...) |
| } |
| |
| // isEmptyString() returns whether v is an empty string. |
| func isEmptyString(v interface{}) bool { |
| str, isAString := v.(string) |
| return isAString && str == "" |
| } |
| |
| // convertInternal is like ExplicitConvert(), but takes a slice of PC values as an argument, |
| // rather than constructing one from the caller's PC. |
| func convertInternal(idAction IDAction, langID i18n.LangID, componentName string, opName string, stack []uintptr, err error) E { |
| // If err is already a verror.E, we wish to: |
| // - retain all set parameters. |
| // - if not yet set, set parameters 0 and 1 from componentName and |
| // opName. |
| // - if langID is set, and we have the appropriate language's format |
| // in our catalogue, use it. Otherwise, retain a message (assuming |
| // additional parameters were not set) even if the language is not |
| // correct. |
| if e, ok := assertIsE(err); !ok { |
| return makeInternal(idAction, langID, componentName, opName, stack, err.Error()) |
| } else { |
| oldParams := e.ParamList |
| |
| // Convert all embedded E errors, recursively. |
| for i := range oldParams { |
| if subErr, isE := oldParams[i].(E); isE { |
| oldParams[i] = convertInternal(idAction, langID, componentName, opName, stack, subErr) |
| } else if subErrs, isSubErrs := oldParams[i].(SubErrs); isSubErrs { |
| for j := range subErrs { |
| if subErr, isE := subErrs[j].Err.(E); isE { |
| subErrs[j].Err = convertInternal(idAction, langID, componentName, opName, stack, subErr) |
| } |
| } |
| oldParams[i] = subErrs |
| } |
| } |
| |
| // Create a non-empty format string if we have the language in the catalogue. |
| var formatStr string |
| if langID != i18n.NoLangID { |
| id := e.ID |
| if id == "" { |
| id = ErrUnknown.ID |
| } |
| formatStr = i18n.Cat().Lookup(langID, i18n.MsgID(id)) |
| } |
| |
| // Ignore the caller-supplied component and operation if we already have them. |
| if componentName != "" && len(oldParams) >= 1 && !isEmptyString(oldParams[0]) { |
| componentName = "" |
| } |
| if opName != "" && len(oldParams) >= 2 && !isEmptyString(oldParams[1]) { |
| opName = "" |
| } |
| |
| var msg string |
| var newParams []interface{} |
| if componentName == "" && opName == "" { |
| if formatStr == "" { |
| return e // Nothing to change. |
| } else { // Parameter list does not change. |
| newParams = e.ParamList |
| msg = i18n.FormatParams(formatStr, newParams...) |
| } |
| } else { // Replace at least one of the first two parameters. |
| newLen := len(oldParams) |
| if newLen < 2 { |
| newLen = 2 |
| } |
| newParams = make([]interface{}, newLen) |
| copy(newParams, oldParams) |
| if componentName != "" { |
| newParams[0] = componentName |
| } |
| if opName != "" { |
| newParams[1] = opName |
| } |
| if formatStr != "" { |
| msg = i18n.FormatParams(formatStr, newParams...) |
| } |
| } |
| return E{e.ID, e.Action, msg, newParams, e.stackPCs, nil} |
| } |
| } |
| |
| // ExplicitConvert converts a regular err into an E error, setting its IDAction to idAction. If |
| // err is already an E, it returns err or an equivalent value without changing its type, but |
| // potentially changing the language, component or operation if langID!=i18n.NoLangID, |
| // componentName!="" or opName!="" respectively. The caller's PC is added to the |
| // error's stack. |
| func ExplicitConvert(idAction IDAction, langID i18n.LangID, componentName string, opName string, err error) error { |
| if err == nil { |
| return nil |
| } else { |
| var stack []uintptr |
| if _, isE := assertIsE(err); !isE { // Walk the stack only if convertInternal will allocate an E. |
| stack = make([]uintptr, maxPCs) |
| stack = stack[:runtime.Callers(2, stack)] |
| } |
| return convertInternal(idAction, langID, componentName, opName, stack, err) |
| } |
| } |
| |
| // defaultCtx is the context used when a nil context.T is passed to New() or Convert(). |
| var ( |
| defaultCtx *context.T |
| defaultCtxLock sync.RWMutex // Protects defaultCtx. |
| ) |
| |
| // SetDefaultContext sets the default context used when a nil context.T is |
| // passed to New() or Convert(). It is typically used in standalone |
| // programmes that have no RPC context, or in servers for the context of |
| // ancillary threads not associated with any particular RPC. |
| func SetDefaultContext(ctx *context.T) { |
| defaultCtxLock.Lock() |
| defaultCtx = ctx |
| defaultCtxLock.Unlock() |
| } |
| |
| // Convert is like ExplicitConvert(), but obtains the language, component and operation names |
| // from the specified context.T. ctx may be nil. |
| func Convert(idAction IDAction, ctx *context.T, err error) error { |
| if err == nil { |
| return nil |
| } |
| langID, componentName, opName := dataFromContext(ctx) |
| var stack []uintptr |
| if _, isE := assertIsE(err); !isE { // Walk the stack only if convertInternal will allocate an E. |
| stack = make([]uintptr, maxPCs) |
| stack = stack[:runtime.Callers(2, stack)] |
| } |
| return convertInternal(idAction, langID, componentName, opName, stack, err) |
| } |
| |
| // dataFromContext reads the languageID, component name, and operation name |
| // from the context, using defaults as appropriate. |
| func dataFromContext(ctx *context.T) (langID i18n.LangID, componentName string, opName string) { |
| // Use a default context if ctx is nil. defaultCtx may also be nil, so |
| // further nil checks are required below. |
| if ctx == nil { |
| defaultCtxLock.RLock() |
| ctx = defaultCtx |
| defaultCtxLock.RUnlock() |
| } |
| if ctx != nil { |
| langID = i18n.GetLangID(ctx) |
| value := ctx.Value(componentKey{}) |
| componentName, _ = value.(string) |
| opName = vtrace.GetSpan(ctx).Name() |
| } |
| if componentName == "" { |
| componentName = filepath.Base(os.Args[0]) |
| } |
| return defaultLangID(langID), componentName, opName |
| } |
| |
| // Error returns the error message; if it has not been formatted for a specific |
| // language, a default message containing the error ID and parameters is |
| // generated. This method is required to fulfil the error interface. |
| func (e E) Error() string { |
| msg := e.Msg |
| if isDefaultIDAction(e.ID, e.Action) && msg == "" { |
| msg = i18n.Cat().Format(i18n.NoLangID, i18n.MsgID(ErrUnknown.ID), e.ParamList...) |
| } else if msg == "" { |
| msg = i18n.Cat().Format(i18n.NoLangID, i18n.MsgID(e.ID), e.ParamList...) |
| } |
| return msg |
| } |
| |
| // String is the default printing function for SubErrs. |
| func (subErrs SubErrs) String() (result string) { |
| if len(subErrs) > 0 { |
| sep := "" |
| for _, s := range subErrs { |
| if (s.Options & Print) != 0 { |
| result += fmt.Sprintf("%s[%s: %s]", sep, s.Name, s.Err.Error()) |
| sep = ", " |
| } |
| } |
| } |
| return result |
| } |
| |
| // params returns the variadic arguments to ExplicitNew(). We do not export it |
| // to discourage its abuse as a general-purpose way to pass alternate return |
| // values. |
| func params(err error) []interface{} { |
| if e, ok := assertIsE(err); ok { |
| return e.ParamList |
| } |
| return nil |
| } |
| |
| // subErrorIndex returns index of the first SubErrs in e.ParamList |
| // or len(e.ParamList) if there is no such parameter. |
| func (e E) subErrorIndex() (i int) { |
| for i = range e.ParamList { |
| if _, isSubErrs := e.ParamList[i].(SubErrs); isSubErrs { |
| return i |
| } |
| } |
| return len(e.ParamList) |
| } |
| |
| // subErrors returns a copy of the subordinate errors accumulated into err, or |
| // nil if there are no such errors. We do not export it to discourage its |
| // abuse as a general-purpose way to pass alternate return values. |
| func subErrors(err error) (r SubErrs) { |
| if e, ok := assertIsE(err); ok { |
| size := 0 |
| for i := range e.ParamList { |
| if subErrsParam, isSubErrs := e.ParamList[i].(SubErrs); isSubErrs { |
| size += len(subErrsParam) |
| } |
| } |
| if size != 0 { |
| r = make(SubErrs, 0, size) |
| for i := range e.ParamList { |
| if subErrsParam, isSubErrs := e.ParamList[i].(SubErrs); isSubErrs { |
| r = append(r, subErrsParam...) |
| } |
| } |
| } |
| } |
| return r |
| } |
| |
| // addSubErrsInternal returns a copy of err with supplied errors appended as |
| // subordinate errors. Requires that errors[i].Err!=nil for 0<=i<len(errors). |
| func addSubErrsInternal(err error, langID i18n.LangID, componentName string, opName string, stack []uintptr, errors SubErrs) error { |
| var pe *E |
| var e E |
| var ok bool |
| if pe, ok = err.(*E); ok { |
| e = *pe |
| } else if e, ok = err.(E); !ok { |
| panic("non-verror.E passed to verror.AddSubErrs") |
| } |
| var subErrs SubErrs |
| index := e.subErrorIndex() |
| copy(e.ParamList, e.ParamList) |
| if index == len(e.ParamList) { |
| e.ParamList = append(e.ParamList, subErrs) |
| } else { |
| copy(subErrs, e.ParamList[index].(SubErrs)) |
| } |
| for _, subErr := range errors { |
| if _, ok := assertIsE(subErr.Err); ok { |
| subErrs = append(subErrs, subErr) |
| } else { |
| subErr.Err = convertInternal(IDAction{}, langID, componentName, opName, stack, subErr.Err) |
| subErrs = append(subErrs, subErr) |
| } |
| } |
| e.ParamList[index] = subErrs |
| if langID != i18n.NoLangID { |
| e.Msg = i18n.Cat().Format(langID, i18n.MsgID(e.ID), e.ParamList...) |
| } |
| return e |
| } |
| |
| // ExplicitAddSubErrs returns a copy of err with supplied errors appended as |
| // subordinate errors. Requires that errors[i].Err!=nil for 0<=i<len(errors). |
| func ExplicitAddSubErrs(err error, langID i18n.LangID, componentName string, opName string, errors ...SubErr) error { |
| stack := make([]uintptr, maxPCs) |
| stack = stack[:runtime.Callers(2, stack)] |
| return addSubErrsInternal(err, langID, componentName, opName, stack, errors) |
| } |
| |
| // AddSubErrs is like ExplicitAddSubErrs, but uses the provided context |
| // to obtain the langID, componentName, and opName values. |
| func AddSubErrs(err error, ctx *context.T, errors ...SubErr) error { |
| stack := make([]uintptr, maxPCs) |
| stack = stack[:runtime.Callers(2, stack)] |
| langID, componentName, opName := dataFromContext(ctx) |
| return addSubErrsInternal(err, langID, componentName, opName, stack, errors) |
| } |
| |
| // debugStringInternal returns a more verbose string representation of an |
| // error, perhaps more thorough than one might present to an end user, but |
| // useful for debugging by a developer. It prefixes all lines output with |
| // "prefix" and "name" (if non-empty) and adds intent to prefix wen recursing. |
| func debugStringInternal(err error, prefix string, name string) string { |
| str := prefix |
| if len(name) > 0 { |
| str += name + " " |
| } |
| str += err.Error() |
| // Append err's stack, indented a little. |
| prefix += " " |
| buf := bytes.NewBufferString("") |
| stackToTextIndent(buf, Stack(err), prefix) |
| str += "\n" + buf.String() |
| // Print all the subordinate errors, even the ones that were not |
| // printed by Error(), indented a bit further. |
| prefix += " " |
| if subErrs := subErrors(err); len(subErrs) > 0 { |
| for _, s := range subErrs { |
| str += debugStringInternal(s.Err, prefix, s.Name) |
| } |
| } |
| return str |
| } |
| |
| // DebugString returns a more verbose string representation of an error, |
| // perhaps more thorough than one might present to an end user, but useful for |
| // debugging by a developer. |
| func DebugString(err error) string { |
| return debugStringInternal(err, "", "") |
| } |