Break Server-Timing into sess, db and tpl metrics

`app` alone said how long the origin took; it could not say where. The
header now also carries `sess` (the session/auth layers around the
routed stack), `db` (SQLite statement time, statement count in `desc`)
and `tpl` (askama rendering), each only where it was measured.

`db` needs a tracing layer: sqlx-sqlite runs statements on a worker
thread and only reports elapsed time there, inside the span the caller
handed it. The new `web::timing` module hangs the request's metrics on a
per-request span and reads them back off sqlx's `sqlx::query` event.
The layer's filter admits every span up to DEBUG, which is what lets it
see the request span past the middleware spans below it; the module docs
spell out the process-wide cost of that.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Va5eMEmWEnjMXBsBob5FDW
This commit is contained in:
2026-09-05 03:12:02 +00:00
co-authored by Claude Fable 5.1
parent 9c4964ae27
commit 17a88043ff
5 changed files with 471 additions and 23 deletions
+3
View File
@@ -177,6 +177,9 @@ pub fn router(state: AppState) -> Router {
.layer(PropagateRequestIdLayer::x_request_id())
.layer(TraceLayer::new_for_http())
.layer(SetRequestIdLayer::x_request_id(MakeRequestUuid))
// Immediately inside the auth layer, so what is left outside it is the
// session load and save that `server_timing` reports as `sess`.
.layer(from_fn(crate::web::route_boundary))
.layer(auth_layer)
.layer(from_fn_with_state(
state.clone(),