errdef splits error handling in Go into Definitions and Error instances, so you can keep errors typed, structured, and uniform. It integrates cleanly with the standard ecosystem — errors.Is / errors.As, fmt.Formatter, json.Marshaler, and slog.LogValuer — while adding fields, stack traces, and flexible error composition.
Status: The core API is stable, but minor breaking changes may occur before v1.0.0.
Note: Both errors.Is and field extractors compare identities, not string names. Each Define or DefineField call creates a unique identity, even with identical strings. Use unique names throughout your application to avoid confusion.
Choose how to construct an error depending on whether you create a new one or keep a cause.
- New(msg): Create a new error.
- Errorf(fmt, ...): Create with a formatted message.
- Wrap(cause): Wrap and keep the cause (errors.Is(err, cause) stays true).
- Wrapf(cause, fmt, ...): Wrap with a cause and a formatted message.
- Join(causes...): Join multiple causes (errors.Is(err, cause) stays true).
- With(ctx, ...opts): Requires ctx. Use when options need request-scoped data.
- WithOptions(...opts): No ctx. Use for context-independent options.
Using the %+v format specifier will print the error message, kind, fields, stack trace, and any wrapped errors.
Example Output:
errdef.Error implements json.Marshaler to produce structured JSON output.
Example Output:
Note: If multiple fields have the same name, the last one in insertion order will be used in the JSON output.
errdef.Error implements slog.LogValuer out-of-the-box to provide structured logging with zero configuration.
Example Output:
Note: If multiple fields have the same name, the last one in insertion order will be used in the log output.
For more advanced control, you can:
-
Log the full stack trace: The Stack type also implements slog.LogValuer.
stack := err.(errdef.Error).Stack() slog.Error("...", "stack", stack) -
Log the full causes tree: The ErrorNode type also implements slog.LogValuer.
nodes, _ := err.(errdef.Error).UnwrapTree() slog.Error("...", "causes", nodes)
The field constructor can be chained with methods like WithValue or WithValueFunc to create new, simplified constructors. This is useful for creating options with predefined or dynamically generated values.
The field extractor provides several helper methods for retrieving values from an error instance, especially for handling cases where a field might not exist.
Note: Extractors follow the same rules as errors.As. They search the error chain and extract the value from the first matching errdef.Error, then stop searching. If you need inner fields at the outer layer, prefer explicitly copying the needed fields when wrapping.
You can attach free-form diagnostic details to an error under the "details" field.
Note: Details is derived from a map[string]any type and implements Option, allowing you to attach arbitrary key-value pairs.
You can use context.Context to automatically attach request-scoped information to your errors.
Wrap secrets (tokens, emails, IDs, etc.) with Redacted[T] to ensure they always render as "[REDACTED]" in logs and serialized output (fmt, json, slog, encoding). The original value remains accessible via .Value() for internal use.
You can join multiple errors into one using the Join method on a Definition.
errdef provides a convenient way to convert panics into structured errors, ensuring that even unexpected failures are handled consistently.
For advanced use cases like mapping error codes from external APIs, use a Resolver.
Note: If multiple definitions have the same Kind or field value, the first one in the resolver's definition order will be used.
The errdef/unmarshaler package allows you to deserialize errdef.Error instances from JSON or other formats. Use a Resolver to map kind strings to error definitions, and the unmarshaler will restore typed errors with their fields and stack traces.
Note: The unmarshaler package is designed to work with any serialization format, not just JSON. You can implement custom Decoder functions for formats like Protocol Buffers, XML, MessagePack, or any proprietary format.
For a complete example with Protocol Buffers including marshal functions and full round-trip demonstration, see examples/protobuf.
errdef is designed to work seamlessly with the broader Go ecosystem.
- Structured Logging: Implements slog.LogValuer for rich, structured logs out-of-the-box.
- Error Reporting Services:
- Sentry: Compatible with the Sentry Go SDK by implementing the stackTracer interface.
- Google Cloud Error Reporting: Integrates directly with the service by implementing the DebugStacker interface.
- Legacy Error Handling: Supports interoperability with pkg/errors by implementing the causer interface.
| HTTPStatus(int) | Attaches an HTTP status code. | HTTPStatusFrom |
| LogLevel(slog.Level) | Attaches a log level of type slog.Level. | LogLevelFrom |
| TraceID(string) | Attaches a trace or request ID. | TraceIDFrom |
| Domain(string) | Labels the error with a service or subsystem name. | DomainFrom |
| UserHint(string) | Provides a safe, user-facing hint message. | UserHintFrom |
| Public() | Marks the error as safe to expose externally. | IsPublic |
| Retryable() | Marks the operation as retryable. | IsRetryable |
| RetryAfter(time.Duration) | Recommends a delay to wait before retrying. | RetryAfterFrom |
| Unreportable() | Prevents the error from being sent to error tracking. | IsUnreportable |
| ExitCode(int) | Sets the exit code for a CLI application. | ExitCodeFrom |
| HelpURL(string) | Provides a URL for documentation or help guides. | HelpURLFrom |
| Details{} | Attaches free-form diagnostic details to an error. | DetailsFrom |
| NoTrace() | Disables stack trace collection for the error. | - |
| StackSkip(int) | Skips a specified number of frames during stack capture. | - |
| StackDepth(int) | Sets the depth of the stack capture (default: 32). | - |
| Formatter(f) | Overrides the default fmt.Formatter behavior. | - |
| JSONMarshaler(f) | Overrides the default json.Marshaler behavior. | - |
| LogValuer(f) | Overrides the default slog.LogValuer behavior. | - |
- HTTP API Server - Error handling in REST APIs
- Protocol Buffers - Custom serialization with protobuf
Last updated: 2025-10-11
errdef adds structured error handling on top of Go's standard library. Here are the key performance metrics:
| New | ~16 ns | ~27 ns | ~287 ns | ~477 ns |
| Wrap | ~104 ns | ~28 ns | ~289 ns | ~479 ns |
| Memory | 16-56 B | ~80 B | ~336 B | ~896 B |
Note: Benchmarked on Apple M1 Pro, Go 1.25 (stack depth: 32)
In practice, error handling is rarely the bottleneck. Focus on correctness first, then optimize if profiling shows that error creation is a significant cost.
-
Default is fine for most cases: The ~300 ns overhead is negligible unless you're creating thousands of errors per second.
-
Disable stack traces in hot paths: Stack trace capture takes ~260 ns per error. Use NoTrace() in tight loops or high-frequency code paths where errors are common:
// ✅ Hot paths (loops, frequently called functions) var ErrValidation = errdef.Define("validation", errdef.NoTrace()) for _, item := range items { if err := validate(item); err != nil { return ErrValidation.Wrap(err) // Fast: ~28 ns, ~80 B } } // ❌ API boundaries, critical errors (keep stack traces!) var ErrDatabaseFailure = errdef.Define("db_failure") // ~287 ns, ~336 B -
Minimize dynamic fields: Using With() or WithOptions() adds ~190 ns base overhead plus ~50-90 ns per field. For hot paths where fields are constant, prefer defining errors with fields upfront:
// ✅ Attach fields to definition upfront var ErrInvalidInput = errdef.Define("invalid_input", errdef.HTTPStatus(400), errdef.NoWrap()) for _, input := range inputs { if !input.IsValid() { return ErrInvalidInput.New("invalid") // Fast: ~27 ns, ~80 B } } // ❌ Attach fields dynamically in hot path var ErrInvalidInput = errdef.Define("invalid_input", errdef.NoWrap()) for _, input := range inputs { if !input.IsValid() { return ErrInvalidInput.WithOptions(errdef.HTTPStatus(400)).New("invalid") // ~220 ns, ~640 B } } -
Limit stack depth: For hot paths where you still want to capture the error origin, use StackDepth(1) to keep just the first frame:
// ✅ Capture only the immediate origin var ErrDBQuery = errdef.Define("db_query", errdef.StackDepth(1)) if err := db.Query(...); err != nil { return ErrDBQuery.Wrap(err) // Fast: ~177 ns, ~88 B } // ❌ Keep full stack trace (default depth: 32) var ErrDBQuery = errdef.Define("db_query") if err := db.Query(...); err != nil { return ErrDBQuery.Wrap(err)) // ~287 ns, ~336 B }
Other operations also have measurable overhead:
| Detailed Error Formatting | ~1370 ns | 1240 B |
| Structured Logging (slog) | ~331 ns | 544 B |
| Field Extraction | ~220 ns | 32 B |
| Resolver (kind lookup) | ~6-8 ns | 0 B |
| Resolver (field lookup) | ~213-243 ns | 544 B |
| JSON Marshaling (NoTrace) | ~878 ns | 752 B |
| JSON Marshaling (simple) | ~2.1 µs | 1.7 KB |
| JSON Marshaling (deep chain) | ~130 µs | 42 KB |
| JSON unmarshaling (simple) | ~5.8 µs | 2.7 KB |
| JSON unmarshaling (deep chain) | ~65 µs | 47 KB |
Last updated: 2025-10-07
If you spot inaccuracies or want another library included, please open an issue or PR.
| errors.Is/As Compatibility | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ |
| Automatic Stack Traces | ❌ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ |
| Stack Control (Disable/Depth) | ❌ | ❌ | ⚠️ | ❌ | ❌ | ✅ | ✅ |
| Structured Data | ❌ | ❌ | ⚠️ | ❌ | ⚠️ | ⚠️ | ✅ (Type-Safe) |
| Redaction | ❌ | ❌ | ✅ | ❌ | ❌ | ❌ | ✅ |
| Structured JSON | ❌ | ❌ | ⚠️ (Proto) | ⚠️ (Logging) | ❌ | ⚠️ (Formatted) | ✅ |
| slog Integration | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ✅ |
| Panic Recovery | ❌ | ❌ | ✅ | ❌ | ❌ | ❌ | ✅ |
| Multiple Causes (errors.Join) | ✅ | ❌ | ✅ | ✅ | ✅ | ✅ | ✅ |
| JSON Deserialization | ❌ | ❌ | ⚠️ | ❌ | ❌ | ❌ | ✅ |
| Protobuf Deserialization | ❌ | ❌ | ✅ | ❌ | ❌ | ❌ | ⚠️ (Extensible) |
- pkg/errors: A historical library that added stack trace functionality, but is now archived.
- cockroachdb/errors: Specializes in distributed systems and has a very powerful Protobuf serialization/deserialization feature.
- eris: Provides good stack trace formatting but lacks structured field attachment feature.
- errorx / merry v2: Although not type-safe, they provide a feature to attach information to errors in a simple key-value format.
- errdef: Features a design that separates Definitions from Instances, enabling type-safe fields, native slog integration, and full JSON round-trip capabilities.
Contributions are welcome! Feel free to send issues or pull requests.
This project is licensed under the MIT License.
.png)
