Operational Features¶
Three small, orthogonal features for production use:
- HTTP status — attach and walk a status code along the cause chain.
- Retryable / Temporary — classify whether retrying makes sense.
- Safe message — emit a redacted variant for logs that may leave the trust boundary.
Each is set with an option at construction (or inherited via Wrap) and read either via a method on *Error or a top-level walker function.
HTTP status¶
err := ewrap.New("upstream rejected request",
ewrap.WithHTTPStatus(http.StatusBadGateway))
ewrap.HTTPStatus(err) // 502
ewrap.HTTPStatus(io.EOF) // 0 — no ewrap layer set one
ewrap.HTTPStatus(nil) // 0
HTTPStatus(err) walks the chain via errors.As and returns the first non-zero status it finds. Wrapping a tagged error keeps the status:
inner := ewrap.New("rejected", ewrap.WithHTTPStatus(http.StatusBadGateway))
outer := ewrap.Wrap(inner, "fetching invoice")
ewrap.HTTPStatus(outer) // 502, inherited from inner
A standard fmt.Errorf("...: %w", inner) wrapper also works — HTTPStatus walks past it via errors.Unwrap.
Typical use in an HTTP handler¶
func handle(w http.ResponseWriter, r *http.Request) {
if err := process(r); err != nil {
status := ewrap.HTTPStatus(err)
if status == 0 {
status = http.StatusInternalServerError
}
http.Error(w, err.Error(), status)
return
}
}
Retryable / Temporary¶
The classification is explicit and three-state:
err.Retryable() // (value, set bool)
// set == false → not classified
// set == true → value is the explicit classification
ewrap.IsRetryable(err) walks the chain. If no ewrap layer set the flag, it falls through to the stdlib interface{ Temporary() bool }, which net.OpError, *net.DNSError, and friends already implement:
Typical use in a retry loop¶
for attempt := 1; attempt <= max; attempt++ {
err := callUpstream(req)
if err == nil {
return nil
}
if !ewrap.IsRetryable(err) {
return err
}
time.Sleep(backoff(attempt))
}
Combining with WithRetry¶
WithRetry carries a per-error retry policy (max attempts, delay, predicate). WithRetryable is a simple classification flag. Use both together when you want a self-describing retryable error:
Safe (PII-redacted) messages¶
SafeError() returns a redacted variant of the error chain suitable for external sinks (third-party logs, customer-visible responses, public metrics):
err := ewrap.New("user 'alice@example.com' rejected",
ewrap.WithSafeMessage("user [redacted] rejected"))
err.Error() // "user 'alice@example.com' rejected"
err.SafeError() // "user [redacted] rejected"
SafeError walks the chain. Each layer contributes either its WithSafeMessage value (if set) or its raw msg. Standard wrapped errors without a SafeError method are included verbatim — wrap them in an ewrap.Error with WithSafeMessage if they may contain PII:
root := ewrap.New("token=secret123", ewrap.WithSafeMessage("token=[redacted]"))
outer := ewrap.Wrap(root, "auth failed for user@example.com",
ewrap.WithSafeMessage("auth failed for [redacted]"))
outer.Error() // "auth failed for user@example.com: token=secret123"
outer.SafeError() // "auth failed for [redacted]: token=[redacted]"
Typical use in dual-sink logging¶
logger.Error("internal", "err", err.Error()) // full detail to private sink
external.Error("public", "err", err.SafeError()) // redacted to public sink
Inheritance through Wrap¶
All three classifications are inherited when wrapping an ewrap.Error:
inner := ewrap.New("boom",
ewrap.WithHTTPStatus(http.StatusBadGateway),
ewrap.WithRetryable(true))
outer := ewrap.Wrap(inner, "in handler")
ewrap.HTTPStatus(outer) // 502
ewrap.IsRetryable(outer) // true
Pass an option to Wrap to override the inherited value at the new layer.
What's intentionally not here¶
- gRPC status codes — would require pulling in
google.golang.org/grpc. UseWithHTTPStatusand translate at the boundary, or implement a tiny gRPC subpackage in your own repo. - Message templates / i18n — out of scope. Build your own helper that calls
WithSafeMessagewith the localized string. - Automatic PII detection — too domain-specific.
WithSafeMessageis the explicit hook; reach for it where the original message can leak.