Skip to main content

cja/server/
trace.rs

1use tower_http::trace::{MakeSpan, OnResponse};
2use tracing::Level;
3
4/// `MakeSpan`/`OnResponse` implementation behind cja's `server.request` spans.
5///
6/// [`run_server`](super::run_server) installs this automatically. Apps that
7/// manage their own `axum::serve` loop (custom bind address, graceful
8/// shutdown) can attach it themselves to keep the same span vocabulary:
9///
10/// ```rust,ignore
11/// let tracer = cja::server::trace::Tracer;
12/// let trace_layer = tower_http::trace::TraceLayer::new_for_http()
13///     .make_span_with(tracer)
14///     .on_response(tracer);
15/// ```
16#[derive(Debug, Clone, Copy)]
17pub struct Tracer;
18
19impl<Body> MakeSpan<Body> for Tracer {
20    fn make_span(&mut self, request: &http::Request<Body>) -> tracing::Span {
21        let route = http_route(request);
22        let span_name = format!("{} {}", request.method(), route);
23
24        tracing::span!(
25            Level::INFO,
26            "server.request",
27            otel.name = span_name,
28            kind = "server",
29            uri = %request.uri(),
30            url.path = %request.uri().path(),
31            url.query = request.uri().query(),
32            url.scheme = request.uri().scheme_str(),
33            server.address = request.uri().host(),
34            server.port = request.uri().port_u16(),
35            http_version = ?request.version(),
36            user_agent.original = request.headers().get("user-agent").and_then(|h| h.to_str().ok()),
37            http.route = route,
38            http.request.method = %request.method(),
39            http.request.header.host = request.headers().get("host").and_then(|h| h.to_str().ok()),
40            http.request.header.forwarded_for = request.headers().get("x-forwarded-for").and_then(|h| h.to_str().ok()),
41            http.request.header.forwarded_proto = request.headers().get("x-forwarded-proto").and_then(|h| h.to_str().ok()),
42            http.request.header.forwarded_ssl = request.headers().get("x-forwarded-ssl").and_then(|h| h.to_str().ok()),
43            http.request.header.referer = request.headers().get("referer").and_then(|h| h.to_str().ok()),
44            http.request.header.fly_forwarded_port = request.headers().get("fly-forwarded-port").and_then(|h| h.to_str().ok()),
45            http.request.header.fly_region = request.headers().get("fly-region").and_then(|h| h.to_str().ok()),
46            http.request.header.via = request.headers().get("via").and_then(|h| h.to_str().ok()),
47
48            http.response.status_code = tracing::field::Empty,
49            http.response.header.content_type = tracing::field::Empty,
50        )
51    }
52}
53
54impl<Body> OnResponse<Body> for Tracer {
55    fn on_response(
56        self,
57        response: &http::Response<Body>,
58        latency: std::time::Duration,
59        span: &tracing::Span,
60    ) {
61        let status_code = response.status().as_u16();
62        tracing::event!(
63            Level::INFO,
64            status = status_code,
65            latency = format_args!("{} ms", latency.as_millis()),
66            "finished processing request"
67        );
68
69        span.record("http.response.status_code", status_code);
70        span.record(
71            "http.response.header.content_type",
72            response
73                .headers()
74                .get("content-type")
75                .and_then(|h| h.to_str().ok()),
76        );
77    }
78}
79
80#[inline]
81fn http_route<B>(req: &http::Request<B>) -> &str {
82    req.extensions()
83        .get::<axum::extract::MatchedPath>()
84        .map_or_else(|| "", |mp| mp.as_str())
85}