cockroachdb/errors

repository·master·Indexed 25 days ago

https://github.com/cockroachdb/errors

A Go library providing enhanced error handling as a drop-in replacement for the standard errors package and github.com/pkg/errors. It is designed for distributed systems with features including network portability via protobuf encoding, PII-free detail support, and seamless Sentry.io integration. The library supports rich metadata such as stack traces, hints, assertion failures, and issue tracker links, while maintaining error identity across network boundaries using errors.Is().

Tokens
11.2K
Snippets
9
Records
75
Agent score
81%

What's inside cockroachdb/errors

  1. Overview of cockroachdb/errors

    master

    The cockroachdb/errors library is a drop-in replacement for Go's standard errors package and github.com/pkg/errors. It is designed for distributed systems, providing network portability for error objects. This allows error identity (via errors.Is()) to be maintained even after errors have been encoded and sent across a network, supporting forward compatibility between different software versions.

    Key capabilities include:

    • Network Portability: Transparent protobuf encoding/decoding with forward compatibility.
    • PII Protection: Native support for PII-free (Personally Identifiable Information) details and safe reporting.
    • Sentry Integration: An opt-in mechanism to automatically format and strip PII from error reports sent to Sentry.io.
    • Rich Metadata: Support for stack traces, hints, details, assertion failures, issue tracker links, and secondary causes.
  2. How to use cockroachdb/errors

    master

    To integrate cockroachdb/errors into your project, follow these patterns:

    Error Construction and Wrapping

    • Use standard constructors like errors.New() and errors.Errorf().
    • Use errors.Wrap() to add context to errors.
    • For specific error types, use specialized leaf constructors or wrappers (see documentation for specific types).

    Error Inspection

    • Identity: Use errors.Is() to check error identity. Unlike the standard library, this works across the network.
    • Multiple Identities: Use errors.IsAny() to check if an error matches any of several reference errors.
    • OS Errors: Replace os.IsPermission(), os.IsTimeout(), os.IsExist(), and os.IsNotExist() with their equivalents in the oserror sub-package to ensure they can peek through layers of wrapping.
    • Unwrapping: Use errors.UnwrapOnce() or errors.UnwrapAll() to access causes. (Note: errors.Cause() and errors.Unwrap() are also provided for compatibility).

    Network and Reporting

    • Protobuf: Use errors.EncodeError() and errors.DecodeError() to transmit errors over the network.
    • PII-Free Details: Use errors.GetSafeDetails() to extract details safe for logging or external display.
    • User Hints: Use errors.GetAllHints(), errors.GetAllDetails(), errors.FlattenHints(), or errors.FlattenDetails() to extract human-facing information.
    • Sentry: Use errors.BuildSentryReport() or errors.ReportError() to generate detailed, PII-stripped reports for Sentry.io.

    Custom Error Types

    To implement your own error types:

    1. Implement the error and errors.Wrapper interfaces.
    2. Register encode/decode functions using errors.Register{Leaf,Wrapper}{Encoder,Decoder}() inside an init() function.
    3. Implement a Format() method that redirects to errors.FormatError().
  3. Build custom leaf error types

    master

    To create a custom leaf error type, implement the standard Go error interface. If your type already implements proto.Message (from gogoproto), the library will use it for encoding/decoding automatically.

    If it does not implement proto.Message, you must provide a decoder function and register it using errors.RegisterLeafEncoder.

    If your error type has fields that are not reflected in the Error() string and are not PII-free (meaning they cannot be exposed via errors.SafeDetailer), you must implement a custom encoder. Otherwise, the library can automatically encode the error message and any strings returned by SafeDetails().

    // note: we use the gogoproto `proto` sub-package.
    func yourDecode(_ string, _ []string, _ proto.Message) error {
       return &yourType{}
    }
    
    func init() {
       errors.RegisterLeafEncoder((*yourType)(nil), yourDecodeFunc)
    }
  4. Make %+v work with custom error types

    master

    To ensure that the %+v verb correctly formats your custom error types and recursively prints their causes, you should implement the fmt.Formatter interface and redirect to errors.FormatError.

    Additionally, if your error type has payload data not visible in Error() that should be emitted during %+v formatting, implement the errors.SafeFormatter interface using SafeFormatError(p errors.Printer) (next error).

    // Required for recursive %+v support
    func (e *yourType) Format(s *fmt.State, verb rune) {
        errors.FormatError(e, s, verb)
    }
    
    // Optional: to include extra details in %+v output
    func (w *withHTTPCode) SafeFormatError(p errors.Printer) (next error) {
        if p.Detail() {
            p.Printf("http code: %d", errors.Safe(w.code))
        }
        return w.cause
    }
  5. Build custom wrapper error types

    master

    To create a custom wrapper error type, implement the error interface and the errors.Wrapper interface (by providing an Unwrap() method).

    If your type does not implement proto.Message, you must provide a decoder function that handles the cause and register it using errors.RegisterWrapperDecoder. The library automatically handles the encoding/decoding of the cause error passed to the decoder.

    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)
    }
  6. Provide PII-free details for Sentry reporting

    master

    The library automatically redacts PII-unsafe strings when building Sentry reports. To ensure specific data is included in reports (making it 'safe'), use one of the following methods:

    1. Use errors.Safe(): Wrap arguments in the ...f() constructors.

      err := errors.Newf("my code: %d", errors.Safe(123))

      The value 123 will be included in Sentry reports and is available via errors.GetSafeDetails()/GetAllSafeDetails().

    2. Use errors.WithSafeDetails(): Attach arbitrary strings that are safe for reporting but not part of the main Error() message.

      err = errors.WithSafeDetails(err, "additional data: %s", errors.Safe("hello"))
    3. Implement errors.SafeDetailer: For custom error types, implement the SafeDetails() []string method to provide a list of safe strings.

  7. Manage error domains with domains_api.go

    master

    The errors package provides tools to annotate errors with a Domain. Domains allow you to categorize errors (e.g., by package or specific functional area), which helps in filtering, reporting (like Sentry), and formatting errors using %+v or errors.GetSafeDetails().

    Key concepts:

    • NoDomain: A constant representing errors that do not originate from a barrier or have no domain annotation.
    • NamedDomain(name string): Creates a domain identified by a specific string.
    • PackageDomain(): Automatically identifies the domain of the package that calls this function.
    • WithDomain(err, domain): Wraps an existing error to associate it with a specific domain without hiding the original error's cause.
    • HandledInDomain(err, domain): Creates a new error in the specified domain. Unlike WithDomain, this hides the original error as a cause, but preserves the original error's message for debugging.
  8. Configure stack trace depth with WithDepth functions

    master

    Most constructor and wrapper functions have a corresponding WithDepth variant (e.g., NewWithDepth, WrapWithDepth, JoinWithDepth, NewWithDepthf, WrapWithDepthf, AssertionFailedWithDepthf, HandleAsAssertionFailureDepth).

    Use these when you need to manually control the depth at which the stack trace is captured, typically when wrapping errors in helper functions to avoid capturing the helper's own stack frame.

  9. Extract safe (PII-free) details from errors

    master

    The library supports extracting 'Safe Details'—information attached to an error that is guaranteed to be free of Personally Identifiable Information (PII). This is useful for logging or displaying error information to users without compromising security.

    • GetSafeDetails(err error) (payload SafeDetailPayload): Returns a single safe detail payload.
    • GetAllSafeDetails(err error) []SafeDetailPayload: Returns all available safe detail payloads.
  10. Handle error type renames with RegisterTypeMigration

    master

    If you rename a Go package or an error type, existing errors transported over the network may no longer be recognized by errors.Is in newer versions of your software.

    To maintain network portability, call errors.RegisterTypeMigration in an init() function to map the old path and type name to a new instance of the error type.

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

    master

    Domains allow you to categorize errors into logical groups (e.g., database errors vs. network errors). This helps in filtering and handling errors based on their origin.

    • GetDomain(err error) Domain: Retrieves the domain of the error.
    • NamedDomain(domainName string) Domain: Creates a new domain with the specified name.
    • PackageDomain() Domain: Returns the domain associated with the current package.
    • PackageDomainAtDepth(depth int) Domain: Returns the domain at a specific depth in the error chain.
    • NotInDomain(err error, doms ...Domain) bool: Checks if the error is NOT in any of the specified domains.
  12. Register custom error encoders and decoders

    master

    If you define custom error types, you must register their encoding and decoding logic so they can be correctly serialized and deserialized during network transport. You can register encoders/decoders for both 'Leaf' errors (the root cause) and 'Wrapper' errors (errors that wrap other errors).

    Available registration functions:

    • RegisterLeafEncoder(typeName TypeKey, encoder LeafEncoder)
    • RegisterLeafDecoder(typeName TypeKey, decoder LeafDecoder)
    • RegisterWrapperEncoder(typeName TypeKey, encoder WrapperEncoder)
    • RegisterWrapperDecoder(typeName TypeKey, decoder WrapperDecoder)
    • RegisterMultiCauseEncoder(theType TypeKey, encoder MultiCauseEncoder)
    • RegisterMultiCauseDecoder(theType TypeKey, decoder MultiCauseDecoder)