When an Autumn handler panics or returns a 5xx, the failure should go
somewhere you'll actually see it — Sentry, Honeycomb, Slack, a custom sink —
not just scroll past in the logs. Autumn's error-reporting pipeline is the
"configure once, errors go somewhere" seam, modeled on Rails'
Rails.error.report.
Two things happen automatically, out of the box:
- Handler panics are caught at the HTTP layer. A panic inside a handler
becomes a clean
500Problem Details response instead of aborting the worker task. The panic payload never leaks to the client. - Panics and 5xx responses are reported. Each produces exactly one
structured
ErrorEventdelivered to every registered reporter. The defaultLogReporterwrites it throughtracing, so the feature is useful with zero configuration.
This slice covers panics + server errors only. Client (
4xx) errors and validation-error aggregation are out of scope.
Quick start
A reporter is one trait with one method. Wiring it up is a single builder call:
use autumn_web::reporting::{ErrorEvent, ErrorReporter, ReportFuture};
struct SlackReporter {
webhook_url: String,
}
impl ErrorReporter for SlackReporter {
fn report<'a>(&'a self, event: &'a ErrorEvent) -> ReportFuture<'a> {
Box::pin(async move {
// POST `event` to Slack; swallow transport errors.
let _ = (&self.webhook_url, event.status, &event.message);
})
}
}
#[autumn_web::main]
async fn main() {
autumn_web::app()
.with_error_reporter(SlackReporter { webhook_url: std::env::var("SLACK_URL").unwrap() })
.routes(routes![/* ... */])
.run()
.await;
}
A runnable version lives in examples/error-reporting.
The ErrorEvent
Every report carries enough context to locate the failure:
| Field | Description |
|---|---|
status | HTTP status of the failing response (always 5xx). |
message | The error message (panic payload, or the underlying error). |
problem_type | The Problem Details type URI, when the error carried one. |
request_id | The X-Request-Id of the failing request. |
route | The matched route template, e.g. /users/{id}. |
method | The HTTP method, e.g. GET. |
panic | Some(PanicInfo) for caught panics: the payload + a backtrace. |
For panics, panic.backtrace is populated only when RUST_BACKTRACE is set
(RUST_BACKTRACE=1), matching the standard library's backtrace gate.
Multiple reporters
Call .with_error_reporter(..) more than once to fan out — every reporter
receives every event:
autumn_web::app()
.with_error_reporter(SentryReporter::from_env())
.with_error_reporter(SlackReporter { webhook_url })
.routes(routes![/* ... */])
.run()
.await;
When you register no reporter, the built-in LogReporter is used so
panics and server errors still surface in your logs.
Reporting never breaks a request
Reporting runs on a detached task, so a slow or failing reporter can't delay or break the client response:
- The client always gets its clean
500(or other5xx) response immediately. - If a reporter panics or errors, the failure is swallowed and logged; other reporters still run.
Configuration
The [reporting] section of autumn.toml controls delivery:
[reporting]
enabled = true # deliver events to reporters (default: true)
sample_rate = 0.25 # report ~25% of events (default: 1.0 = all)
enabled = falsesuppresses delivery only. Handler panics are still caught and turned into clean 500s regardless of this setting.sample_rateis a fraction in[0.0, 1.0]. Use it to cap reporter volume (and cost) on high-traffic services.
How it fits the middleware stack
The reporting + panic-catch layer sits inside
RequestIdLayer (so the request id is
available when a handler panics) and outside the route handler (so handler
panics are caught). The resulting 500 still flows out through the
exception-filter chain, so HTML error-page negotiation and
Problem Details rendering work exactly as they do for any other error.
This is the panic-aware promotion of the older
ExceptionFilter concept: ExceptionFilter transforms the
response; ErrorReporter ships the event. They compose — keep your
filters, add reporters.
Scope: which 5xx are observed
Because the layer sits inside RequestIdLayer (so every event — panics
included — carries the request id), 5xx responses produced by middleware
outside it are not observed. In practice the only such case is a 503 from
the session layer when a session store (e.g. Redis) is unavailable. That is
a deliberate trade-off: a session-store outage is an infrastructure failure
already surfaced by readiness/health probes, and moving reporting outside the
session layer would also move it outside RequestIdLayer, dropping the request
id from every event. Handler panics and handler/inner-middleware server errors
— the failures you're expected to act on — are reported with full context.
Shipping to a concrete backend
A concrete Sentry/Honeycomb/Datadog backend lives in its own crate (mirroring
how autumn-storage-s3 lives outside core). To build one, implement
ErrorReporter over the backend's client and publish it as a small adapter
crate; users add it as a dependency and wire it with one
.with_error_reporter(..) call.
See also
- Errors tutorial —
AutumnError, Problem Details, HTML error pages. - Middleware guide — the layer ordering and exception filters.