Copper provides the cerrors package, which adds two things to Go’s plain errors: a message chain and structured tags. If you wrap errors on the way up, by the time one reaches your logs it will tell the whole story.
Wrapping Errors
You may wrap an error with cerrors.New(cause, message, tags):
Tags carry the context that a message alone cannot, such as IDs, states, and attempt counts. You should pass values directly rather than formatting them into the message.
If you would like to create a new error rather than wrap one, pass a nil cause:
When rendered, a wrapped error reads as a chain:
Sentinel Errors
You should keep sentinel errors as plain errors.New values so that callers can match them with errors.Is:
You may return them directly or wrapped. Since cerrors.Error implements Unwrap, errors.Is and errors.As see through the chain either way.
Handling Errors in Routers
At the HTTP boundary, you may switch on sentinels to map errors to status codes, treating everything else as a 500:
Expected failures receive a helpful response body, while unexpected ones are logged with the full chain and return a bare 500. For the complete handler pattern, including request validation, see JSON APIs.
Errors in Logs
You never need to log tags separately. When an error is passed to the logger, tags from the entire error chain are merged into the log’s structured output automatically. See Logging for more information.
cerrors.Error is a value type, not a pointer. If you ever need errors.As, match against cerrors.Error, not *cerrors.Error.