Go error library with error portability over the network

cockroachdb/errors: Go errors with network portability

This library aims to be used as a drop-in replacement to github.com/pkg/errors and Go's standard errors package. It also provides network portability of error objects, in ways suitable for distributed systems with mixed-version software compatibility.

It also provides native and comprehensive support for PII-free details and an opt-in Sentry.io reporting mechanism that automatically formats error details and strips them of PII.

See also the design RFC.

Build Status

Table of contents:

Features

Feature Go's <1.13 errors github.com/pkg/errors Go 1.13 errors/xerrors cockroachdb/errors
error constructors (New, Errorf etc)
error causes (Cause / Unwrap)
cause barriers (Opaque / Handled)
errors.As(), errors.Is()
automatic error wrap when format ends with : %w
standard wrappers with efficient stack trace capture
transparent protobuf encode/decode with forward compatibility
errors.Is() recognizes errors across the network
comprehensive support for PII-free reportable strings
support for both Cause() and Unwrap() go#31778
standard error reports to Sentry.io
wrappers to denote assertion failures
wrappers with issue tracker references
wrappers for user-facing hints and details
wrappers to attach secondary causes
wrappers to attach logtags details from context.Context
errors.FormatError(), Formatter, Printer (under construction)
errors.SafeFormatError(), SafeFormatter
wrapper-aware IsPermission(), IsTimeout(), IsExist(), IsNotExist()

"Forward compatibility" above refers to the ability of this library to recognize and properly handle network communication of error types it does not know about, for example when a more recent version of a software package sends a new error object to another system running an older version of the package.

How to use

  • construct errors with errors.New(), etc as usual, but also see the other error leaf constructors below.
  • wrap errors with errors.Wrap() as usual, but also see the other wrappers below.
  • test error identity with errors.Is() as usual. Unique in this library: this works even if the error has traversed the network! Also, errors.IsAny() to recognize two or more reference errors.
  • replace uses of os.IsPermission(), os.IsTimeout(), os.IsExist() and os.IsNotExist() by their analog in sub-package oserror so that they can peek through layers of wrapping.
  • access error causes with errors.UnwrapOnce() / errors.UnwrapAll() (note: errors.Cause() and errors.Unwrap() also provided for compatibility with other error packages).
  • encode/decode errors to protobuf with errors.EncodeError() / errors.DecodeError().
  • extract PII-free safe details with errors.GetSafeDetails().
  • extract human-facing hints and details with errors.GetAllHints()/errors.GetAllDetails() or errors.FlattenHints()/errors.FlattenDetails().
  • produce detailed Sentry.io reports with errors.BuildSentryReport() / errors.ReportError().
  • implement your own error leaf types and wrapper types:
    • implement the error and errors.Wrapper interfaces as usual.
    • register encode/decode functions: call errors.Register{Leaf,Wrapper}{Encoder,Decoder}() in a init() function in your package.
    • implement Format() that redirects to errors.FormatError().
    • see the section Building your own error types below.

What comes out of an error?

Error detail Error() and format %s/%q/%v format %+v GetSafeDetails() Sentry report via ReportError()
main message, eg New() visible visible yes (CHANGED IN v1.6) full (CHANGED IN v1.6)
wrap prefix, eg WithMessage() visible (as prefix) visible yes (CHANGED IN v1.6) full (CHANGED IN v1.6)
stack trace, eg WithStack() not visible simplified yes full
hint , eg WithHint() not visible visible no type only
detail, eg WithDetail() not visible visible no type only
assertion failure annotation, eg WithAssertionFailure() not visible visible no type only
issue links, eg WithIssueLink(), UnimplementedError() not visible visible yes full
safe details, eg WithSafeDetails() not visible not visible yes full
telemetry keys, eg. WithTelemetryKey() not visible visible yes full
secondary errors, eg. WithSecondaryError(), CombineErrors() not visible visible redacted, recursively redacted, recursively
barrier origins, eg. Handled() not visible visible redacted, recursively redacted, recursively
error domain, eg. WithDomain() not visible visible yes full
context tags, eg. WithContextTags() not visible visible keys visible, values redacted keys visible, values redacted

Available error leaves

An error leaf is an object that implements the error interface, but does not refer to another error via a Unwrap() or Cause() method.

  • New(string) error, Newf(string, ...interface{}) error, Errorf(string, ...interface{}) error: leaf errors with message

    • when to use: common error cases.
    • what it does: also captures the stack trace at point of call and redacts the provided message for safe reporting.
    • how to access the detail: Error(), regular Go formatting. Details in Sentry report.
    • see also: Section Error composition below. errors.NewWithDepth() variants to customize at which call depth the stack trace is captured.
  • AssertionFailedf(string, ...interface{}) error, NewAssertionFailureWithWrappedErrf(error, string, ...interface{}) error: signals an assertion failure / programming error.

    • when to use: when an invariant is violated; when an unreachable code path is reached.
    • what it does: also captures the stack trace at point of call, redacts the provided strings for safe reporting, prepares a hint to inform a human user.
    • how to access the detail: IsAssertionFailure()/HasAssertionFailure(), format with %+v, Safe details included in Sentry reports.
    • see also: Section Error composition below. errors.AssertionFailedWithDepthf() variant to customize at which call depth the stack trace is captured.
  • Handled(error) error, Opaque(error) error, HandledWithMessage(error, string) error: captures an error cause but make it invisible to Unwrap() / Is().

    • when to use: when a new error occurs while handling an error, and the original error must be "hidden".
    • what it does: captures the cause in a hidden field. The error message is preserved unless the ...WithMessage() variant is used.
    • how to access the detail: format with %+v, redacted details reported in Sentry reports.
  • UnimplementedError(IssueLink, string) error: captures a message string and a URL reference to an external resource to denote a feature that was not yet implemented.

    • when to use: to inform (human) users that some feature is not implemented yet and refer them to some external resource.
    • what it does: captures the message, URL and detail in a wrapper. The URL and detail are considered safe for reporting.
    • how to access the detail: errors.GetAllHints(), errors.FlattenHints(), format with %+v, URL and detail included in Sentry report (not the message).
    • see also: errors.WithIssueLink() below for errors that are not specifically about unimplemented features.

Available wrapper constructors

An error wrapper is an object that implements the error interface, and also refers to another error via an Unwrap() (preferred) and/or Cause() method.

All wrapper constructors can be applied safely to a nil error: they behave as no-ops in this case:

// The following:
// if err := foo(); err != nil {
//    return errors.Wrap(err, "foo")
// }
// return nil
//
// is not needed. Instead, you can use this:
return errors.Wrap(foo(), "foo")
  • Wrap(error, string) error, Wrapf(error, string, ...interface{}) error:

    • when to use: on error return paths.
    • what it does: combines WithMessage(), WithStack(), WithSafeDetails().
    • how to access the details: Error(), regular Go formatting. Details in Sentry report.
    • see also: Section Error composition below. WrapWithDepth() variants to customize at which depth the stack trace is captured.
  • WithSecondaryError(error, error) error: annotate an error with a secondary error.

    • when to use: when an additional error occurs in the code that is handling a primary error. Consider using errors.CombineErrors() instead (see below).
    • what it does: it captures the secondary error but hides it from errors.Is().
    • how to access the detail: format with %+v, redacted recursively in Sentry reports.
    • see also: errors.CombineErrors()
  • CombineErrors(error, error) error: combines two errors into one.

    • when to use: when two operations occur concurrently and either can return an error, and only one final error must be returned.
    • what it does: returns either of its arguments if the other is nil, otherwise calls WithSecondaryError().
    • how to access the detail: see WithSecondaryError() above.
  • Mark(error, error) error: gives the identity of one error to another error.

    • when to use: when a caller expects to recognize a sentinel error with errors.Is() but the callee provides a diversity of error messages.
    • what it does: it overrides the "error mark" used internally by errors.Is().
    • how to access the detail: format with %+v, Sentry reports.
  • WithStack(error) error: annotate with stack trace

    • when to use: usually not needed, use errors.Wrap()/errors.Wrapf() instead.

      Special cases:

      • when returning a sentinel, for example:

        var myErr = errors.New("foo")
        
        func myFunc() error {
          if ... {
             return errors.WithStack(myErr)
          }
        }
      • on error return paths, when not trivial but also not warranting a wrap. For example:

        err := foo()
        if err != nil {
          doSomething()
          if !somecond {
             return errors.WithStack(err)
          }
        }
    • what it does: captures (efficiently) a stack trace.

    • how to access the details: format with %+v, errors.GetSafeDetails(), Sentry reports. The stack trace is considered safe for reporting.

    • see also: WithStackDepth() to customize the call depth at which the stack trace is captured.

  • WithSafeDetails(error, string, ...interface{}) error: safe details for reporting.

    • when to use: probably never. Use errors.Wrap()/errors.Wrapf() instead.
    • what it does: saves some strings for safe reporting.
    • how to access the detail: format with %+v, errors.GetSafeDetails(), Sentry report.
  • WithMessage(error, string) error, WithMessagef(error, string, ...interface{}) error: message prefix.

    • when to use: probably never. Use errors.Wrap()/errors.Wrapf() instead.
    • what it does: adds a message prefix.
    • how to access the detail: Error(), regular Go formatting, Sentry Report.
  • WithDetail(error, string) error, WithDetailf(error, string, ...interface{}) error, user-facing detail with contextual information.

    • when to use: need to embark a message string to output when the error is presented to a human.
    • what it does: captures detail strings.
    • how to access the detail: errors.GetAllDetails(), errors.FlattenDetails() (all details are preserved), format with %+v. Not included in Sentry reports.
  • WithHint(error, string) error, WithHintf(error, string, ...interface{}) error: user-facing detail with suggestion for action to take.

    • when to use: need to embark a message string to output when the error is presented to a human.
    • what it does: captures hint strings.
    • how to access the detail: errors.GetAllHints(), errors.FlattenHints() (hints are de-duplicated), format with %+v. Not included in Sentry reports.
  • WithIssueLink(error, IssueLink) error: annotate an error with an URL and arbitrary string.

    • when to use: to refer (human) users to some external resources.
    • what it does: captures the URL and detail in a wrapper. Both are considered safe for reporting.
    • how to access the detail: errors.GetAllHints(), errors.FlattenHints(), errors.GetSafeDetails(), format with %+v, Sentry report.
    • see also: errors.UnimplementedError() to construct leaves (see previous section).
  • WithTelemetry(error, string) error: annotate an error with a key suitable for telemetry.

    • when to use: to gather strings during error handling, for capture in the telemetry sub-system of a server package.
    • what it does: captures the string. The telemetry key is considered safe for reporting.
    • how to access the detail: errors.GetTelemetryKeys(), errors.GetSafeDetails(), format with %+v, Sentry report.
  • WithDomain(error, Domain) error, HandledInDomain(error, Domain) error, HandledInDomainWithMessage(error, Domain, string) error (experimental): annotate an error with an origin package.

    • when to use: at package boundaries.
    • what it does: captures the identity of the error domain. Can be asserted with errors.EnsureNotInDomain(), errors.NotInDomain().
    • how to access the detail: format with %+v, Sentry report.
  • WithAssertionFailure(error) error: annotate an error as being an assertion failure.

    • when to use: probably never. Use errors.AssertionFailedf() and variants.
    • what it does: wraps the error with a special type. Triggers an auto-generated hint.
    • how to access the detail: IsAssertionFailure()/HasAssertionFailure(), errors.GetAllHints(), errors.FlattenHints(), format with %+v, Sentry report.
  • WithContextTags(error, context.Context) error: annotate an error with the k/v pairs attached to a context.Context instance with the logtags package.

    • when to use: when capturing/producing an error and a context.Context is available.
    • what it does: it captures the logtags.Buffer object in the wrapper.
    • how to access the detail: errors.GetContextTags(), format with %+v, Sentry reports.

Providing PII-free details

The library support PII-free strings essentially as follows:

  • by default, many strings included in an error object are considered to be PII-unsafe, and are stripped out when building a Sentry report.
  • some fields in the library are assumed to be PII-safe by default.
  • you can opt additional strings in to Sentry reports.

The following strings from this library are considered to be PII-free, and thus included in Sentry reports automatically:

  • the type of error objects,
  • stack traces (containing only file paths, line numbers, function names - arguments are not included),
  • issue tracker links (including URL and detail field),
  • telemetry keys,
  • error domains,
  • context tag keys,
  • the format string argument of Newf, AssertionFailedf, etc (the constructors ending with ...f()),
  • the type of the additional arguments passed to the ...f() constructors,
  • the value of specific argument types passed to the ...f() constructors, when known to be PII-safe. For details of which arguments are considered PII-free, see the redact package.

It is possible to opt additional in to Sentry reporting, using either of the following methods:

  • implement the errors.SafeDetailer interface, providing the SafeDetails() []string method on your error type.

  • enclose additional arguments passed to the ...f() constructors with errors.Safe(). For example: err := errors.Newf("my code: %d", errors.Safe(123)) — in this example, the value 123 will be included when a Sentry report is constructed.

    • it also makes it available via errors.GetSafeDetails()/GetAllSafeDetails().
    • the value 123 is also part of the main error message returned by Error().
  • attach additional arbitrary strings with errors.WithSafeDetails(error, string, ...interface{}) error and also use errors.Safe(). For example: err = errors.WithSafeDetails(err, "additional data: %s", errors.Safe("hello")).

    • in this example, the string "hello" will be included in Sentry reports.
    • however, it is not part of the main error message returned by Error().

For more details on how Sentry reports are built, see the report sub-package.

Building your own error types

You can create an error type as usual in Go: implement the error interface, and, if your type is also a wrapper, the errors.Wrapper interface (an Unwrap() method). You may also want to implement the Cause() method for backward compatibility with github.com/pkg/errors, if your project also uses that.

If your error type is a wrapper, you should implement a Format() method that redirects to errors.FormatError(), otherwise %+v will not work. Additionally, if your type has a payload not otherwise visible via Error(), you may want to implement errors.SafeFormatter. See making %+v work with your type below for details.

Finally, you may want your new error type to be portable across the network.

If your error type is a leaf, and already implements proto.Message (from gogoproto), you are all set and the errors library will use that automatically. If you do not or cannot implement proto.Message, or your error type is a wrapper, read on.

At a minimum, you will need a decoder function: while cockroachdb/errors already does a bunch of encoding/decoding work on new types automatically, the one thing it really cannot do on its own is instantiate a Go object using your new type.

Here is the simplest decode function for a new leaf error type and a new wrapper type:

// note: we use the gogoproto `proto` sub-package.
func yourDecode(_ string, _ []string, _ proto.Message) error {
   return &yourType{}
}

func init() {
   errors.RegisterLeafEncoder((*yourType)(nil), yourDecodeFunc)
}

func yourDecodeWrapper(cause error, _ string, _ []string, _ proto.Message) error {
   // Note: the library already takes care of encoding/decoding the cause.
   return &yourWrapperType{cause: cause}
}

func init() {
   errors.RegisterWrapperDecoder((*yourWrapperType)(nil), yourDecodeWrapper)
}

In the case where your type does not have any other field (empty struct for leafs, just a cause for wrappers), this is all you have to do.

(See the type withAssertionFailure in assert/assert.go for an example of this simple case.)

If your type does have additional fields, you may still not need a custom encoder. This is because the library automatically encodes/decodes the main error message and any safe strings that your error types makes available via the errors.SafeDetailer interface (the SafeDetails() method).

Say, for example, you have the following leaf type:

type myLeaf struct {
   code int
}

func (m *myLeaf) Error() string { return fmt.Sprintf("my error: %d" + m.code }

In that case, the library will automatically encode the result of calling Error(). This string will then be passed back to your decoder function as the first argument. This makes it possible to decode the code field exactly:

func myLeafDecoder(msg string, _ []string, _ proto.Message) error {
	codeS := strings.TrimPrefix(msg, "my error: ")
	code, _ := strconv.Atoi(codeS)
	// Note: error handling for strconv is omitted here to simplify
	// the explanation. If your decoder function should fail, simply
	// return a `nil` error object (not another unrelated error!).
	return &myLeaf{code: code}
}

Likewise, if your fields are PII-free, they are safe to expose via the errors.SafeDetailer interface. Those strings also get encoded automatically, and get passed to the decoder function as the second argument.

For example, say you have the following leaf type:

type myLeaf struct {
   // both fields are PII-free.
   code int
   tag string
}

func (m *myLeaf) Error() string { ... }

Then you can expose the fields as safe details as follows:

func (m *myLeaf) SafeDetails() []string {
  return []string{fmt.Sprintf("%d", m.code), m.tag}
}

(If the data is PII-free, then it is good to do this in any case: it enables any network system that receives an error of your type, but does not know about it, to still produce useful Sentry reports.)

Once you have this, the decode function receives the strings and you can use them to re-construct the error:

func myLeafDecoder(_ string, details []string, _ proto.Message) error {
    // Note: you may want to test the length of the details slice
	// is correct.
    code, _ := strconv.Atoi(details[0])
    tag := details[1]
	return &myLeaf{code: code, tag: tag}
}

(For an example, see the withTelemetry type in telemetry/with_telemetry.go.)

The only case where you need a custom encoder is when your error type contains some fields that are not reflected in the error message (so you can't extract them back from there), and are not PII-free and thus cannot be reported as "safe details".

To take inspiration from examples, see the following types in the library that need a custom encoder:

Making %+v work with your type

In short:

  • When in doubt, you should always implement the fmt.Formatter interface (Format(fmt.State, rune)) on your custom error types, exactly as follows:

    func (e *yourType) Format(s *fmt.State, verb rune) { errors.FormatError(e, s, verb) }

    (If you do not provide this redirection for your own custom wrapper type, this will disable the recursive application of the %+v flag to the causes chained from your wrapper.)

  • You may optionally implement the errors.SafeFormatter interface: SafeFormatError(p errors.Printer) (next error). This is optional, but should be done when some details are not included by Error() and should be emitted upon %+v.

The example withHTTPCode wrapper included in the source tree achieves this as follows:

// Format() implements fmt.Formatter, is required until Go knows about FormatError.
func (w *withHTTPCode) Format(s fmt.State, verb rune) { errors.FormatError(w, s, verb) }

// FormatError() formats the error.
func (w *withHTTPCode) SafeFormatError(p errors.Printer) (next error) {
	// Note: no need to print out the cause here!
	// FormatError() knows how to do this automatically.
	if p.Detail() {
		p.Printf("http code: %d", errors.Safe(w.code))
	}
	return w.cause
}

Technical details follow:

  • The errors library follows the Go 2 proposal.

  • At some point in the future, Go's standard fmt library will learn how to recognize error wrappers, and how to use the errors.Formatter interface automatically. Until then, you must ensure that you also implement a Format() method (from fmt.Formatter) that redirects to errors.FormatError.

    Note: you may implement fmt.Formatter (Format() method) in this way without implementing errors.Formatter (a FormatError() method). In that case, errors.FormatError will use a separate code path that does "the right thing", even for wrappers.

  • The library provides an implementation of errors.FormatError(), modeled after the same function in Go 2. This is responsible for printing out error details, and knows how to present a chain of causes in a semi-structured format upon formatting with %+v.

Ensuring errors.Is works when errors/packages are renamed

If a Go package containing a custom error type is renamed, or the error type itself is renamed, and errors of this type are transported over the network, then another system with a different code layout (e.g. running a different version of the software) may not be able to recognize the error any more via errors.Is.

To ensure that network portability continues to work across multiple software versions, in the case error types get renamed or Go packages get moved / renamed / etc, the server code must call errors.RegisterTypeMigration() from e.g. an init() function.

Example use:

 previousPath := "github.com/old/path/to/error/package"
 previousTypeName := "oldpackage.oldErrorName"
 newErrorInstance := &newTypeName{...}
 errors.RegisterTypeMigration(previousPath, previousTypeName, newErrorInstance)

Error composition (summary)

Constructor Composes
New NewWithDepth (see below)
Errorf = Newf
Newf NewWithDepthf (see below)
WithMessage custom wrapper with message prefix and knowledge of safe strings
Wrap WrapWithDepth (see below)
Wrapf WrapWithDepthf (see below)
AssertionFailed AssertionFailedWithDepthf (see below)
NewWithDepth custom leaf with knowledge of safe strings + WithStackDepth (see below)
NewWithDepthf custom leaf with knowledge of safe strings + WithSafeDetails + WithStackDepth
WithMessagef custom wrapper with message prefix and knowledge of safe strings
WrapWithDepth WithMessage + WithStackDepth
WrapWithDepthf WithMessagef + WithStackDepth
AssertionFailedWithDepthf NewWithDepthf + WithAssertionFailure
NewAssertionErrorWithWrappedErrf HandledWithMessagef (barrier) + WrapWithDepthf + WithAssertionFailure

API (not constructing error objects)

// Access causes.
func UnwrapAll(err error) error
func UnwrapOnce(err error) error
func Cause(err error) error // compatibility
func Unwrap(err error) error // compatibility
type Wrapper interface { ... } // compatibility

// Error formatting.
type Formatter interface { ... } // compatibility, not recommended
type SafeFormatter interface { ... }
type Printer interface { ... }
func FormatError(err error, s fmt.State, verb rune)
func Formattable(err error) fmt.Formatter

// Identify errors.
func Is(err, reference error) bool
func IsAny(err error, references ...error) bool
func If(err error, pred func(err error) (interface{}, bool)) (interface{}, bool)
func As(err error, target interface{}) bool

// Encode/decode errors.
type EncodedError // this is protobuf-encodable
func EncodeError(ctx context.Context, err error) EncodedError
func DecodeError(ctx context.Context, enc EncodedError) error

// Register encode/decode functions for custom/new error types.
func RegisterLeafDecoder(typeName TypeKey, decoder LeafDecoder)
func RegisterLeafEncoder(typeName TypeKey, encoder LeafEncoder)
func RegisterWrapperDecoder(typeName TypeKey, decoder WrapperDecoder)
func RegisterWrapperEncoder(typeName TypeKey, encoder WrapperEncoder)
type LeafEncoder = func(ctx context.Context, err error) (msg string, safeDetails []string, payload proto.Message)
type LeafDecoder = func(ctx context.Context, msg string, safeDetails []string, payload proto.Message) error
type WrapperEncoder = func(ctx context.Context, err error) (msgPrefix string, safeDetails []string, payload proto.Message)
type WrapperDecoder = func(ctx context.Context, cause error, msgPrefix string, safeDetails []string, payload proto.Message) error

// Registering package renames for custom error types.
func RegisterTypeMigration(previousPkgPath, previousTypeName string, newType error)

// Sentry reports.
func BuildSentryReport(err error) (*sentry.Event, map[string]interface{})
func ReportError(err error) (string)

// Stack trace captures.
func GetOneLineSource(err error) (file string, line int, fn string, ok bool)
type ReportableStackTrace = sentry.StackTrace
func GetReportableStackTrace(err error) *ReportableStackTrace

// Safe (PII-free) details.
type SafeDetailPayload struct { ... }
func GetAllSafeDetails(err error) []SafeDetailPayload
func GetSafeDetails(err error) (payload SafeDetailPayload)

// Obsolete APIs.
type SafeMessager interface { ... }
func Redact(r interface{}) string

// Aliases redact.Safe.
func Safe(v interface{}) SafeMessager

// Assertion failures.
func HasAssertionFailure(err error) bool
func IsAssertionFailure(err error) bool

// User-facing details and hints.
func GetAllDetails(err error) []string
func FlattenDetails(err error) string
func GetAllHints(err error) []string
func FlattenHints(err error) string

// Issue links / URL wrappers.
func HasIssueLink(err error) bool
func IsIssueLink(err error) bool
func GetAllIssueLinks(err error) (issues []IssueLink)

// Unimplemented errors.
func HasUnimplementedError(err error) bool
func IsUnimplementedError(err error) bool

// Telemetry keys.
func GetTelemetryKeys(err error) []string

// Domain errors.
type Domain
const NoDomain Domain
func GetDomain(err error) Domain
func NamedDomain(domainName string) Domain
func PackageDomain() Domain
func PackageDomainAtDepth(depth int) Domain
func EnsureNotInDomain(err error, constructor DomainOverrideFn, forbiddenDomains ...Domain) error
func NotInDomain(err error, doms ...Domain) bool

// Context tags.
func GetContextTags(err error) []*logtags.Buffer
Owner
CockroachDB
the scalable, survivable, SQL database
CockroachDB
Comments
  • Go Modules Support

    Go Modules Support

    I saw https://github.com/cockroachdb/errors/pull/4 but it seems stalled.

    Tests

    Output of go test ./...

    ?   	github.com/cockroachdb/errors	[no test files]
    ok  	github.com/cockroachdb/errors/assert	(cached)
    ok  	github.com/cockroachdb/errors/barriers	(cached)
    ok  	github.com/cockroachdb/errors/contexttags	(cached)
    ok  	github.com/cockroachdb/errors/domains	(cached)
    ?   	github.com/cockroachdb/errors/domains/internal	[no test files]
    ok  	github.com/cockroachdb/errors/errbase	(cached)
    ?   	github.com/cockroachdb/errors/errbase/internal	[no test files]
    ?   	github.com/cockroachdb/errors/errorspb	[no test files]
    ok  	github.com/cockroachdb/errors/errutil	(cached)
    ok  	github.com/cockroachdb/errors/exthttp	(cached)
    ok  	github.com/cockroachdb/errors/hintdetail	(cached)
    ok  	github.com/cockroachdb/errors/issuelink	(cached)
    ok  	github.com/cockroachdb/errors/markers	(cached)
    ?   	github.com/cockroachdb/errors/markers/internal	[no test files]
    ok  	github.com/cockroachdb/errors/report	(cached)
    ok  	github.com/cockroachdb/errors/safedetails	(cached)
    ok  	github.com/cockroachdb/errors/secondary	(cached)
    ?   	github.com/cockroachdb/errors/stdstrings	[no test files]
    ok  	github.com/cockroachdb/errors/telemetrykeys	(cached)
    ok  	github.com/cockroachdb/errors/testutils	(cached)
    ok  	github.com/cockroachdb/errors/withstack	(cached)
    ?   	github.com/cockroachdb/errors/withstack/internal	[no test files]
    

    When I ran go test -short all there were two packages that exploded:

    • https://github.com/pkg/errors, unfortunately Dave says it's not feasible to get those tests passing.
    • https://github.com/getsentry/raven-go, several module-related problems. Unfortunately this package is deprecated, so I forked it and got the tests working again. This one's a bit tricky because sentry-go does have perfectly good module support, but switching over cockroachdb/errors to using this newer package is not a small amount of work to do correctly. (Plus, I don't have a need for Sentry integration.)

    Closes #4 Fixes #2


    This change is Reviewable

  • Best way to add slice or map of key-value pairs to an error?

    Best way to add slice or map of key-value pairs to an error?

    Hi. We use structured logging, and we often have multiple contextual fields that we wish to output, and when we create or wrap an error, we would like to be able to add those fields.

    For instance, if I am making a GraphQL query, and the arguments to the query are an "id" and a "userName", and that GraphQL query fails, I would like to make an error with those two fields and their values, so that I can later format a structured log message.

    How would I do best do that? errors.WithField("id", id).WithField("userName", userName) does not appear to be a thing?

  • GetContextTags question/improvement

    GetContextTags question/improvement

    Hi. I have a few questions about the usage of GetContextTags function.

    For example, I have a package that has an important set of tags during operation processing and in case of an error, I want to attach those tags to the returned error so the caller could reveal them. It's simple, I do it like that:

    return errors.WithContextTags(err, ctx)
    

    And later up to the stack the caller decides to log the error in a structured way, it needs to extract the list of all tags from the error:

    tags := errors.GetContextTags(err)
    

    But it returns a list of Buffers (Buffer for each error with tags in the chain) and each buffer contains a list of tags. So it's [][]Tag, instead of []Tag. Wouldn't it be better to add a function that will return merged buffers with a single list of unique tags, isn't it what the caller interested the most?

    Another thing that I wanted to ask about is how context tags work with the CombineError function. As far as I can see if I combine two errors err1 and err2 (both of them have context tags):

    return errors.CombineError(err1, err2)
    

    The resulting error will contain tags only from the err1 when I call GetContextTags on it. But for example, if the error gets printed, all stack traces will be displayed for the second error two. Shouldn't GetContextTags visit all child errors (including secondaries) and follow the stack trace printing logic?

  • Switch to sentry-go when building the reports

    Switch to sentry-go when building the reports

    Previously, we were using getsentry/raven-go to populate the reports for Sentry. However, that tool has become obsolete. This commit switches to using getsentry/sentry-go.

    There are some differences in the API, and I made the following changes:

    • now there can be multiple "exceptions" specified in the sentry event (previously, only one was allowed).
    • "message" field will now be populated with the "head message" (a short description of the first error)
    • "long message" will be included in Extra.

    This change is Reviewable

  • Drop fork and update to latest sentry-go

    Drop fork and update to latest sentry-go

    Fixes #36 Fixes #41

    This PR drops the fork and updates to the latest stable sentry-go release (v0.12.0).

    Because that's quite a jump, even though there are no breaking changes, I did some manual tests, to make sure everything still looks correct (see the folds below).

    Tests are green locally.

    Quick script I used to QA

    cmd/qa/main.go

    package main
    
    import (
    	"fmt"
    	"log"
    	"os"
    	"time"
    
    	"github.com/cockroachdb/errors"
    	"github.com/getsentry/sentry-go"
    )
    
    func Foo(i int) {
    	Bar(i + 1)
    }
    
    func Bar(i int) {
    	Baz(i + 1)
    }
    
    func Baz(i int) {
    	_, err := os.Open("non-existing.ext")
    	e := errors.Wrap(err, "Doing some QA")
    	errors.ReportError(e)
    }
    
    func Event() {
    	_, err := os.Open("different-non-existing.ext")
    	e := errors.Wrap(err, "Doing some QA")
    
    	event, extraDetails := errors.BuildSentryReport(e)
    	fmt.Printf("%#v\n", extraDetails)
    
    	sentry.WithScope(func(scope *sentry.Scope) {
    		scope.SetExtras(extraDetails)
    		sentry.CaptureEvent(event)
    	})
    }
    
    func main() {
    	err := sentry.Init(sentry.ClientOptions{})
    	if err != nil {
    		log.Fatalf("sentry.Init: %s", err)
    	}
    
    	Foo(2)
    	Event()
    	defer sentry.Flush(5 * time.Second)
    	fmt.Println("bye")
    }
    
    Screenshots image image

    This change is Reviewable

  • Improve GoDoc of top level package

    Improve GoDoc of top level package

    I wanted to see an overview of the available functions and their descriptions in a format I'm used to - via GoDocs. And thought I could even send a small PR that adds the GoDoc badge to the README, like this: GoDoc.

    But the GoDoc of the top level package, which is the one most people are going to use, doesn't contain much useful info. Almost all of the functions just say "... forwards a definition". See https://godoc.org/github.com/cockroachdb/errors.

    I think it would be useful if the functions' GoDoc contain the proper information. For example the GoDoc of errors.WithStack(error) (https://github.com/cockroachdb/errors/blob/9b2e535fbedbf187adf0de1d98509ae8a70ae44e/withstack_api.go#L19-L20) only says "WithStack forwards a definition.", but the README contains much more useful info here, like:

    usually not needed, use errors.Wrap()/errors.Wrapf() instead.

    On the other hand, in the subpackages, the functions are much better documented. Sticking with the previous example of WithStack: https://github.com/cockroachdb/errors/blob/78b6caa8a0455b5fa93f17d4b4c3132597342c0e/withstack/withstack.go#L23-L36


    TL;DR: I'd love to see the top level package documented in the same way as the subpackages.

  • extgrpc: add support for automatic en/decoding of gRPC Status

    extgrpc: add support for automatic en/decoding of gRPC Status

    This patch adds support for automatic en/decoding of gRPC Status errors, enabled by importing the extgrpc package. It supports statuses generated both by the standard google.golang.org/grpc/status and the gogoproto-based github.com/gogo/status packages.

    Encoded statuses are always represented as gogo/status-based Status Protobufs, since the EncodedError type is a gogoproto type using an Any field for structured errors, and only gogoproto types can be placed here since standard Protobufs are not registered in the gogoproto type registry. We lock the gogo/googleapis package to 1.2.0 to use gogoproto 1.2-compatible Protobufs required by CRDB, these have been verified to not be vulnerable to the "skippy pb" bug.

    Note that to preserve error details, gogo-based Status objects with gogo-based details must be used, otherwise details may not be preserved. This is for similar reasons as outlined above, and is a limitation in the gRPC package -- see code comments for further details. A CRDB linter rule forbidding use of Status.WithDetails() was submitted in cockroachdb/cockroach#61617.

    Resolves #63, related to #64 and cockroachdb/cockroach#56208.


    This change is Reviewable

  • protobuf: recompile using gogoproto 1.2 from CRDB master

    protobuf: recompile using gogoproto 1.2 from CRDB master

    Generated Protobufs have been a mix of gogoproto 1.2 and 1.3 types, since different packages have been compiled with different Protobuf compilers. This was in part because Makefile.update-protos only covered errorspb/*.proto, with other Protobufs compiled ad hoc. This was problematic since CockroachDB currently uses gogoproto 1.2 and thus could not make use of the 1.3-generated types.

    This patch changes Makefile.update-protos to compile all Protobufs in the repo, and recompiles all Protobufs using the current Protobuf compiler used in CockroachDB.

    Note in particular that this downgrades generated Protobufs for extgrpc, exthttp, and grpc from gogoproto 1.3 to 1.2, which might be considered a breaking change.

    This also addresses CVE-2021-3121.

    Related to #63 and https://github.com/cockroachdb/cockroach/issues/56208.


    This change is Reviewable

  • Tweak import paths of .proto files in errorspb

    Tweak import paths of .proto files in errorspb

    This makes imports consistent both within this repository as well as in the main CockroachDB repo. This simplifies the Bazel migration as seen in https://github.com/cockroachdb/cockroach/pull/57974.


    This change is Reviewable

  • v1.8.5 go.mod checksum changes?

    v1.8.5 go.mod checksum changes?

    I apologize if this is something unique to me, but I am wondering if there was anything odd about the 1.8.5 release. I am repeatedly getting these in CI:

    Step #0: # get https://proxy.golang.org/github.com/cockroachdb/errors/@v/v1.8.5.mod: 200 OK (0.022s)
    Step #0: verifying github.com/cockroachdb/[email protected]/go.mod: checksum mismatch
    Step #0: 	downloaded: h1:GV8u+kb+Pb23w4FneavC3BBo/8XbnWqcCGhHKi3foME=
    Step #0: 	go.sum:     h1:hOm5fabihW+xEyY1kuypGwqT+Vt7rafg04ytBtIpeIQ=
    Step #0: 
    Step #0: SECURITY ERROR
    Step #0: This download does NOT match an earlier download recorded in go.sum.
    Step #0: The bits may have been replaced on the origin server, or an attacker may
    Step #0: have intercepted the download attempt.
    

    I tried: go clean -cache; go clean -modcache ; go mod tidy and yet on my ci go download -x; go mod verify fails to verify the checksum. Just double checking that you didn't have some sort of incident.

  • Add gRPC Tools

    Add gRPC Tools

    gRPC Tools

    1. Created grpc/middleware package which provides server-side and client-side unary gRPC middleware which can be attached to connections to allow transparent encoding & decoding of errors.
    2. Created extgrpc package, copied from exthttp, which provides a wrapper to annotate an error with a gRPC status.
    3. Created grpc/status package which reproduces a subset of the github.com/gogo/status functionality, namely:
      • Error(c codes.Code, msg string) error for returning a gRPC status inside a server implementation method (no error wrapping occurs).
      • WrapErr(c codes.Code, msg string, err error) error for returning a gRPC status with an included error.
      • Code(err error) codes.Code for evaluating the gRPC status code returned by a server.

    With the middleware in place, errors from this package will transparently encode & decode when returned by the server and received by the client.

    The other packages work together to allow a developer to return & read gRPC status codes in addition to the cockroachdb/errors functionality, if desired.

    Caveats

    • This work was done using go modules, which I added support for in another PR. To keep the PRs separate, I removed the go.mod and go.sum files. I haven't tested it in a GOPATH configuration.
    • I generated ext_grpc.pb.go using gogoproto to ensure compatibility. However, it has generated with const _ = proto.GoGoProtoPackageIsVersion3 whereas the other .pb.go files in the repo are v2. I tried to roll back my libproto to 2.x, but apparently that const is the progeny of your go plugin for protoc. Shortly afterwards I gave up; I don't mind fixing it if someone has pointers for me.

    Note: all tests passing, including the new ones.


    This change is Reviewable

  • Possible

    Possible "index out of range" in `equalMarks`

    Hello!

    I'm interested in carefree iteration over lists whose lengths can vary:

    // equalMarks compares two error markers.
    func equalMarks(m1, m2 errorMark) bool {
    	if m1.msg != m2.msg {
    		return false
    	}
    	for i, t := range m1.types {
    		if !t.Equals(m2.types[i]) {
    			return false
    		}
    	}
    	return true
    }
    

    And I made an example that breaks this code:

    package main
    
    import (
    	"fmt"
    	"github.com/cockroachdb/errors"
    )
    
    type SimpleWrapper struct {
    	err error
    }
    
    func (w SimpleWrapper) Error() string {
    	return "boom!"
    }
    
    func (w SimpleWrapper) Unwrap() error {
    	return w.err
    }
    
    func main() {
    	stack := errors.WithStack
    
    	ref := stack(stack(SimpleWrapper{}))
    	err := stack(stack(SimpleWrapper{err: stack(errors.New("boom!"))}))
    
    	if errors.IsAny(err, ref) {
    		fmt.Println("gotcha!")
    	}
    
    	/* panic: runtime error: index out of range [3] with length 3
    
    	goroutine 1 [running]:
    	github.com/cockroachdb/errors/markers.equalMarks(...)
    	        github.com/cockroachdb/[email protected]/markers/markers.go:205
    	github.com/cockroachdb/errors/markers.IsAny({0x102802528, 0x1400000e438}, {0x14000167f48, 0x1, 0x14000167f28?})
    	        github.com/cockroachdb/[email protected]/markers/markers.go:186 +0x364
    	github.com/cockroachdb/errors.IsAny(...)
    	        github.com/cockroachdb/[email protected]/markers_api.go:64
    	main.main()
    	        examples/04-non-standard-modules/cockroach-is-any-bug/main.go:26 +0x318
    	*/
    }
    

    Where am I wrong?

  • secondary: add SummarizeErrors variadic method

    secondary: add SummarizeErrors variadic method

    I was recently debugging a workflow with lots of async workers able to cancel each other, so that by the time an error bubbled up it was ambiguous where the longest stack would be and whether there were errors in two places because one wrapped the other or because of two separate root errors. I felt like what would be most convenient would be to smoosh everything together rather than try to be too smart about it.

    This PR implements SummarizeErrors, which is mainly the generalization of CombineErrors over n errors. If one argument is detectably already wrapping another, the result will ignore the latter. If two errors are distinct but both wrapping the same error, the result will preserve that information. Two errors are distinct if err1 != err2; there didn't look to be much benefit in bringing in things like .Is as we're really interested in identity, not equivalence.

    This is an unsolicited spike; feel free to bikeshed details or reject out of hand.


    This change is Reviewable

  • pkg/errors dependency

    pkg/errors dependency

    pkg/errors is no longer maintained, cockroachdb/errors still depends on it, will that be an issue down the road.

    looking for a pkg/errors replacement and just found this project, will start using it.

  • Decode context.Canceled, context.DeadlineExceeded etc as a reference to the stdlib instance

    Decode context.Canceled, context.DeadlineExceeded etc as a reference to the stdlib instance

    Currently, instances of the stdlib errors trasnferred over the network are decoded into an object of the same type/value, but not the same instance. So reference comparisons with == with the stdlib instance fail.

    This matters for e.g. context.Context.Err() which describes in its API doc that it returns the standrd errors unwrapped so it's legitimate for a caller to use ==.

    We could address that by adding a special case in the leaf decoder, so that if a leaf is one of the known stdlib errors, we return the stdlib instance instead.

  • Add ability to wrap an error with an Unimplemented error

    Add ability to wrap an error with an Unimplemented error

    I have a bit of code that does something like this:

    errors.UnimplementedErrorf(
      link,
      "unsupported expression %q: %v",
      exprString,
      err,
    )
    

    This flattens the err argument. What I'd really like is something like

    errors.UnimplementedWrapf(
      err,
      link,
      "unsupported expression %q",
      exprString,
    )
    

    (analogous to errors.Errorf versus errors.Wrapf)

    The exact piece of code I'm referring to is: https://github.com/cockroachdb/cockroach/blob/7bf398fc9c715e67ae4ab342569b26f50809fb59/pkg/ccl/importccl/read_import_mysql.go#L764

    cc @knz

A comprehensive error handling library for Go

Highlights The errorx library provides error implementation and error-related utilities. Library features include (but are not limited to): Stack trac

Jan 6, 2023
A flexible error support library for Go

errors Please see http://godoc.org/github.com/spacemonkeygo/errors for info License Copyright (C) 2014 Space Monkey, Inc. Licensed under the Apache Li

Nov 3, 2022
Reduce debugging time while programming Go. Use static and stack-trace analysis to determine which func call causes the error.
Reduce debugging time while programming Go. Use static and stack-trace analysis to determine which func call causes the error.

Errlog: reduce debugging time while programming Introduction Use errlog to improve error logging and speed up debugging while you create amazing code

Nov 18, 2022
Simple error handling primitives

errors Package errors provides simple error handling primitives. go get github.com/pkg/errors The traditional error handling idiom in Go is roughly ak

Dec 26, 2022
A drop-in replacement for Go errors, with some added sugar! Unwrap user-friendly messages, HTTP status code, easy wrapping with multiple error types.
A drop-in replacement for Go errors, with some added sugar! Unwrap user-friendly messages, HTTP status code, easy wrapping with multiple error types.

Errors Errors package is a drop-in replacement of the built-in Go errors package with no external dependencies. It lets you create errors of 11 differ

Dec 6, 2022
A Go (golang) package for representing a list of errors as a single error.

go-multierror go-multierror is a package for Go that provides a mechanism for representing a list of error values as a single error. This allows a fun

Jan 1, 2023
Error tracing and annotation.

errors -- import "github.com/juju/errgo" The errors package provides a way to create and diagnose errors. It is compatible with the usual Go error idi

Nov 3, 2022
A powerful, custom error package for Go

custom-error-go A powerful, custom error package for Go Detailed explanation: https://medium.com/codealchemist/error-handling-in-go-made-more-powerful

Apr 19, 2022
Declarative error handling for Go.

ErrorFlow Declarative error handling for Go. Motivation Reading list: Don't defer Close() on writable files Error Handling — Problem Overview Proposal

Mar 3, 2022
Generic error handling with panic, recover, and defer.

Generic error handling with panic, recover, and defer.

Aug 25, 2022
Go extract error codes

go-extract-error-codes Overview This library helps to extract possible error codes from configured go-restful applications quality level: PoC High Lev

Nov 24, 2021
brief: a piece of error handling codelet

brief a piece of error handling codelet. this code only demonstrates how to hide sql.ErrNoRows to the caller. the Get() method defined in the pkg/proj

Oct 30, 2021
Error interface wrappers for Google's errdetails protobuf types, because they're handy as heck and I want to use them more

Error interface wrappers for Google's errdetails protobuf types, because they're handy as heck and I want to use them more

Nov 18, 2021
Just another error handling primitives for golang

errors Just another error handling primitives for golang Install go install github.com/WAY29/errors@latest Usage New error and print error context Th

Feb 19, 2022
ApolloError compliant error function for gqlgen

gqlgen-apollo-error ApolloError compliant error function for gqlgen Installation $ go get -u github.com/s-ichikawa/gqlgen-apollo-error Quickstart Retu

Sep 6, 2022
Golang advanced error usage with stack tracing

UhOh Golang advanced error usage with stack tracing uhoh consists of 3 parts: Original error Description of error Stack trace File Function Line Usage

Mar 30, 2022
🥷 CError (Custom Error Handling)

?? CError (Custom Error Handling) Installation Via go packages: go get github.com/rozturac/cerror Usage Here is a sample CError uses: import ( "gi

Sep 21, 2022
Errors - A lib for handling error gracefully in Go

?? Errors Errors 是一个用于优雅地处理 Go 中错误的库。 Read me in English ??‍ 功能特性 优雅地处理 error,嗯,

Jan 17, 2022
Simple, intuitive and effective error handling for Go

Error Handling with eluv-io/errors-go The package eluv-io/errors-go makes Go error handling simple, intuitive and effective. err := someFunctionThatCa

Jan 19, 2022