Autumn can emit a standards-conformant Server-Timing response header on every served request when the [observability] config section opts in. The header surfaces the same wall time that the access log records as duration_ms, plus a per-request database roll-up so N+1 queries show up directly in the browser's DevTools Network → Timing pane.

Sample header

Code
Server-Timing: total;dur=42.180, db;dur=21.500;desc="14 queries"
  • total;dur=… — whole-request wall time, in milliseconds, measured with the same Instant-based formula as the access log's duration_ms.
  • db;dur=…;desc="N queries" — cumulative time spent inside Db-tracked Postgres queries, plus the query count in desc. Only present when at least one instrumented query ran during the request.

Both dur values are f64 milliseconds rounded to three decimals.

Viewing it in the browser

In Chromium DevTools open the Network tab, click any request, and go to the Timing sub-tab. The Server-Timing metrics appear at the bottom under Server Timing, one row per metric, with the desc string shown as the row label. Firefox surfaces the same metrics in its Network → Timings panel.

Enabling it

Add to autumn.toml:

TOML
[observability]
server_timing = true

or via environment variable:

Code
AUTUMN_OBSERVABILITY__SERVER_TIMING=true

Profile defaults

The default follows the active profile:

ProfileDefault server_timing
dev / developmenton
everything elseoff

This means production apps never leak internal timings to anonymous clients by default — you have to opt in explicitly, or set the env override in whichever profile you want it on. Explicit server_timing = true or server_timing = false always wins over the profile default.

Units and clock

The total metric uses start.elapsed().as_secs_f64() * 1000.0 — the identical formula the access log uses for its duration_ms field. This middleware is applied outer to the access log, so its total brackets the access-log duration_ms. For any one request the two values agree to within a few microseconds — the access log does path-matching work between the two Instant captures, so expect a sub-microsecond-to-microsecond difference rather than exact equality — and both are subject to the same monotonic-clock guarantees on the host.

The db metric aggregates elapsed times from run_instrumented and the Db drop hook. Queries that run outside a request (background jobs, startup work) don't count — they can't, because the middleware only scopes the accumulator for the request future.

Streaming responses (SSE)

Responses whose Content-Type starts with text/event-stream receive a total-only header — the db metric is omitted even if queries ran during header assembly. The total value reflects time to first byte (when the response headers flush) rather than the full stream lifetime, which is unbounded for a long-lived SSE feed. Handlers that care about the duration of a specific streamed body operation should emit a purpose-built metric via their own tracing span rather than rely on the header.

Non-SSE streaming bodies (chunked JSON, files, WebSocket upgrade responses) get the full metric set as if they were unary responses — the middleware measures until the response head is ready, not until the body drains.

Failure modes

The header is a best-effort observability surface. If the assembled string somehow fails HeaderValue::from_str, the middleware silently drops the header rather than aborting the response. Absent timing data (e.g. no queries ran) never turns into an error path.

Interaction with app-provided Diesel instrumentation

To measure per-query database time, autumn installs its own Diesel connection instrumentation on each connection it checks out while a request is being measured. Diesel's set_instrumentation wholesale replaces a connection's instrumentation, so this has an important consequence:

  • When server_timing is disabled (the production default), autumn installs nothing. Any global instrumentation you registered with diesel::connection::set_default_instrumentation — query logging, tracing, custom metrics — runs untouched.
  • When server_timing is enabled, autumn's timer replaces your instrumentation for the duration of each measured checkout. Autumn does not currently compose with (wrap or chain) an app-provided default instrumentation, so your hook will not fire for queries that run while the feature is active.

This is a documented limitation, not a bug: server_timing is a development-oriented, off-by-default feature. If you rely on your own Diesel instrumentation in a given environment, keep server_timing disabled there (it already defaults off outside the dev/development profiles).