A comprehensive Go error handling library that extends the standard library with rich context, stack traces, error chaining, and integration with error reporting services.
This package provides a drop-in replacement for errors (stdlib) and github.com/pkg/errors with additional features like stacktrace embedding, tagging, domains, multi-error handling, and more.
- Error Creation: Create new errors with automatic domain detection and stack traces
- Error Wrapping: Add context to errors while preserving the original error chain
- Stack Traces: Automatic stack frame capture for debugging
- Domains: Categorize errors by domain (package-based or custom)
- Tags: Attach structured key-value metadata to errors
- Status: Associate status information with errors
- Multi-Error Support: Combine multiple errors into a single error
- Opaque Errors: Hide internal error details from external callers
- Secondary Errors: Attach additional contextual errors
- Error Reporting: Built-in support for Sentry and custom reporters
- Standard Library Compatible: Works with
errors.Is,errors.As, anderrors.Unwrap
go get github.com/upfluence/errorsimport"github.com/upfluence/errors"// Create a new errorerr:=errors.New("configuration file not found")
// Create a formatted errorerr:=errors.Newf("invalid user ID: %d", userID)
// Wrap an existing errorfile, err:=os.Open("config.yaml")
iferr!=nil {
returnerrors.Wrap(err, "failed to open configuration file")
}
// Wrap with formattingdata, err:=readFile(path)
iferr!=nil {
returnerrors.Wrapf(err, "failed to read file %s", path)
}New(msg string) error
Creates a new error with automatic domain detection, stack trace, and opaque wrapping.
funcloadConfig() error {
if!fileExists("config.yaml") {
returnerrors.New("configuration file not found")
}
returnnil
}Newf(msg string, args ...interface{}) error
Creates a new formatted error with automatic domain detection, stack trace, and opaque wrapping.
funcgetUser(idint) error {
ifid<0 {
returnerrors.Newf("invalid user ID: %d", id)
}
returnnil
}Wrap(err error, msg string) error
Wraps an error with additional context and a stack frame.
file, err:=os.Open("config.yaml")
iferr!=nil {
returnerrors.Wrap(err, "failed to open configuration file")
}Wrapf(err error, msg string, args ...interface{}) error
Wraps an error with formatted context and a stack frame.
data, err:=readFile(path)
iferr!=nil {
returnerrors.Wrapf(err, "failed to read file %s", path)
}Cause(err error) error
Returns the root cause of an error by recursively unwrapping it.
err:=errors.New("root cause")
wrapped:=errors.Wrap(err, "additional context")
root:=errors.Cause(wrapped) // returns "root cause"Unwrap(err error) error
Returns the next error in the chain (one level).
err:=errors.New("root")
wrapped:=errors.Wrap(err, "wrapper")
unwrapped:=errors.Unwrap(wrapped) // returns errIs(err, target error) bool
Reports whether any error in the chain matches the target.
iferrors.Is(err, io.EOF) {
fmt.Println("End of file reached")
}As(err error, target interface{}) bool
Finds the first error in the chain matching the target type.
varpathErr*os.PathErroriferrors.As(err, &pathErr) {
fmt.Println("Failed at path:", pathErr.Path)
}IsTimeout(err error) bool
Checks if any error in the chain implements Timeout() bool and returns true.
iferrors.IsTimeout(err) {
fmt.Println("Operation timed out, retrying...")
}IsOfType[T error](err error) bool (Go 1.18+)
Reports whether any error in the chain matches the generic type T. This is a type-safe alternative to using As when you only need to check for the presence of a type without extracting it.
typeMyErrorstruct{}
func (eMyError) Error() string { return"my error" }
err:=errors.Wrap(MyError{}, "wrapped error")
if errors.IsOfType[MyError](err) {
// MyError was found in the error chain
}AsType[T error](err error) (T, bool) (Go 1.18+)
Attempts to convert an error to the generic type T by traversing the error chain. Returns the converted error and true if successful, or a zero value and false otherwise. This is a type-safe wrapper around errors.As.
typeMyErrorstruct {
Codeint
}
func (eMyError) Error() string { returnfmt.Sprintf("error %d", e.Code) }
err:=errors.Wrap(MyError{Code: 404}, "not found")
ifmyErr, ok:= errors.AsType[MyError](err); ok {
fmt.Println(myErr.Code) // prints: 404
}WithStack(err error) error
Adds a stack frame at the current location.
err:=someExternalLibrary()
iferr!=nil {
returnerrors.WithStack(err)
}WithStack2[T any](v T, err error) (T, error) (Go 1.18+)
Adds a stack frame at the current location, while passing through a return value. This is useful for adding stack traces to errors from functions that return multiple values (value, error) in a single line.
If err is nil, returns (v, nil) unchanged. If err is not nil, returns (v, err_with_stack).
// Instead of this:result, err:=externalLib.DoSomething()
iferr!=nil {
returnresult, errors.WithStack(err)
}
returnresult, nil// You can write this:returnerrors.WithStack2(externalLib.DoSomething())WithFrame(err error, depth int) error
Adds a stack frame at a specific depth in the call stack.
funcwrapError(errerror) error {
returnerrors.WithFrame(err, 1) // Skip current frame
}WithDomain(err error, domain string) error
Attaches a domain for error categorization.
err:=doSomething()
iferr!=nil {
returnerrors.WithDomain(err, "database")
}WithTags(err error, tags map[string]interface{}) error
Attaches structured metadata to an error.
err:=fetchUser(userID)
iferr!=nil {
returnerrors.WithTags(err, map[string]interface{}{
"user_id": userID,
"operation": "fetch",
})
}WithStatus(err error, status string) error
Attaches a status string to an error.
err:=processRequest()
iferr!=nil {
returnerrors.WithStatus(err, "failed")
}WithSecondaryError(err error, additionalErr error) error
Attaches an additional error for context.
err:=saveToDatabase(data)
iferr!=nil {
cacheErr:=invalidateCache()
returnerrors.WithSecondaryError(err, cacheErr)
}Join(errs ...error) error
Combines multiple errors (compatible with Go 1.20+ errors.Join).
err:=errors.Join(
errors.New("first error"),
errors.New("second error"),
)Combine(errs ...error) error
Alternative to Join with the same behavior.
err:=errors.Combine(
validateName(),
validateEmail(),
validateAge(),
)WrapErrors(errs []error) error
Combines a slice of errors.
varerrs []erroriferr:=validateName(); err!=nil {
errs=append(errs, err)
}
iferr:=validateEmail(); err!=nil {
errs=append(errs, err)
}
iflen(errs) >0 {
returnerrors.WrapErrors(errs)
}Opaque(err error) error
Prevents unwrapping to hide internal implementation details.
err:=doInternalOperation()
iferr!=nil {
returnerrors.Opaque(err) // Hide internal errors from callers
}The package includes integration with error reporting services like Sentry.
typeReporterinterface {
io.CloserReport(error, ReportOptions)
}import"github.com/upfluence/errors/reporter/sentry"reporter, err:=sentry.NewReporter(sentry.Options{
DSN: "your-sentry-dsn",
})
iferr!=nil {
log.Fatal(err)
}
deferreporter.Close()
reporter.Report(err, reporter.ReportOptions{
Tags: map[string]interface{}{
"environment": "production",
"user_id": userID,
},
})The package provides standard tag keys for common error metadata:
reporter.TransactionKey// "transaction"reporter.DomainKey// "domain"reporter.UserEmailKey// "user.email"reporter.UserIDKey// "user.id"reporter.RemoteIP// "remote.ip"reporter.HTTPRequestPathKey// "http.request.path"reporter.HTTPRequestMethodKey// "http.request.method"// ... and moreThe package includes testing utilities in the errtest subpackage for writing error-related tests.