spin_trigger_http/
instrument.rs

1use anyhow::Result;
2use http::Response;
3use tracing::Level;
4
5use crate::Body;
6
7/// Create a span for an HTTP request.
8macro_rules! http_span {
9    ($request:tt, $addr:tt) => {
10        tracing::info_span!(
11            "spin_trigger_http.handle_http_request",
12            "otel.kind" = "server",
13            "http.request.method" = %$request.method(),
14            "network.peer.address" = %$addr.ip(),
15            "network.peer.port" = %$addr.port(),
16            "network.protocol.name" = "http",
17            "url.path" = $request.uri().path(),
18            "url.query" = $request.uri().query().unwrap_or(""),
19            "url.scheme" = $request.uri().scheme_str().unwrap_or(""),
20            "client.address" = $request.headers().get("x-forwarded-for").and_then(|val| val.to_str().ok()),
21            // Recorded later
22            "error.type" = ::tracing::field::Empty,
23            "http.response.status_code" = ::tracing::field::Empty,
24            "http.route" = ::tracing::field::Empty,
25            "otel.name" = ::tracing::field::Empty,
26        )
27    };
28}
29
30pub(crate) use http_span;
31
32/// Finish setting attributes on the HTTP span.
33pub(crate) fn finalize_http_span(
34    response: Result<Response<Body>>,
35    method: String,
36) -> Result<Response<Body>> {
37    let span = tracing::Span::current();
38    match response {
39        Ok(response) => {
40            tracing::info!(
41                "Request finished, sending response with status code {}",
42                response.status()
43            );
44
45            let matched_route = response.extensions().get::<MatchedRoute>();
46            // Set otel.name and http.route
47            if let Some(MatchedRoute { route }) = matched_route {
48                span.record("http.route", route);
49                span.record("otel.name", format!("{method} {route}"));
50            } else {
51                span.record("otel.name", method);
52            }
53
54            // Set status code
55            span.record("http.response.status_code", response.status().as_u16());
56
57            Ok(response)
58        }
59        Err(err) => {
60            instrument_error(&err);
61            span.record("http.response.status_code", 500);
62            span.record("otel.name", method);
63            Err(err)
64        }
65    }
66}
67
68/// Marks the current span as errored.
69pub(crate) fn instrument_error(err: &anyhow::Error) {
70    let span = tracing::Span::current();
71    tracing::event!(target:module_path!(), Level::INFO, error = %err);
72    span.record("error.type", format!("{:?}", err));
73}
74
75/// MatchedRoute is used as a response extension to track the route that was matched for OTel
76/// tracing purposes.
77#[derive(Clone)]
78pub struct MatchedRoute {
79    pub route: String,
80}
81
82impl MatchedRoute {
83    pub fn set_response_extension(resp: &mut Response<Body>, route: impl Into<String>) {
84        resp.extensions_mut().insert(MatchedRoute {
85            route: route.into(),
86        });
87    }
88
89    pub fn with_response_extension(
90        mut resp: Response<Body>,
91        route: impl Into<String>,
92    ) -> Response<Body> {
93        Self::set_response_extension(&mut resp, route);
94        resp
95    }
96}