Add TraceLayer to quire-server matching quire-ci
Adds tower-http as a dependency and wires up TraceLayer with the same
make_span_with (MatchedPath + method) and on_response (RequestError
extension logging) pattern used in quire-ci/src/server.rs.
change
commit a197042fe08cc367cb5c18e4134b97b01ff911e4
author Claude <noreply@anthropic.com>
date
parent ead4b589
diff --git a/Cargo.lock b/Cargo.lock
index 340a2a2..87263bb 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -2718,6 +2718,7 @@ dependencies = [
  "thiserror",
  "tokio",
  "tower",
+ "tower-http",
  "tracing",
  "tracing-opentelemetry",
  "uuid",
diff --git a/quire-server/Cargo.toml b/quire-server/Cargo.toml
index 953e502..7ccfcb3 100644
--- a/quire-server/Cargo.toml
+++ b/quire-server/Cargo.toml
@@ -35,6 +35,7 @@ tracing-opentelemetry = { workspace = true }
 askama = "*"
 axum = "*"
 axum-extra = { version = "*", features = ["typed-header"] }
+tower-http = { workspace = true }
 clap = { workspace = true }
 clap_complete = "*"
 rand = "*"
diff --git a/quire-server/src/bin/quire/server.rs b/quire-server/src/bin/quire/server.rs
index 3b9edae..4c41bb6 100644
--- a/quire-server/src/bin/quire/server.rs
+++ b/quire-server/src/bin/quire/server.rs
@@ -1,12 +1,22 @@
 use std::net::SocketAddr;
 use std::os::unix::net::UnixListener as StdUnixListener;
+use std::time::Duration;
 
 use axum::Router;
+use axum::extract::MatchedPath;
+use axum::http::Request;
+use axum::response::Response;
 use axum::routing::get;
 use miette::{Context, IntoDiagnostic, Result};
 use quire::Quire;
 use quire::ci;
 use quire::event::PushEvent;
+use tower_http::trace::TraceLayer;
+use tracing::info_span;
+
+/// Carries an error message through response extensions so TraceLayer can log it.
+#[derive(Clone)]
+pub struct RequestError(pub String);
 
 async fn health() -> &'static str {
     "ok"
@@ -56,7 +66,26 @@ pub async fn run(quire: &Quire, web_routes: axum::Router, api_routes: axum::Rout
         .route("/health", get(health))
         .route("/", get(index))
         .merge(web_routes)
-        .nest("/api", api_routes);
+        .nest("/api", api_routes)
+        .layer(
+            TraceLayer::new_for_http()
+                .make_span_with(|request: &Request<_>| {
+                    let matched_path = request
+                        .extensions()
+                        .get::<MatchedPath>()
+                        .map(MatchedPath::as_str);
+                    info_span!("http_request", method = ?request.method(), matched_path)
+                })
+                .on_response(|response: &Response, _: Duration, _: &tracing::Span| {
+                    if let Some(RequestError(error)) = response.extensions().get::<RequestError>() {
+                        if response.status().is_server_error() {
+                            tracing::error!(%error);
+                        } else {
+                            tracing::warn!(%error);
+                        }
+                    }
+                }),
+        );
 
     tracing::info!(%addr, "starting HTTP server");