Skip to main content

noiseless_tracing_actix_web/
lib.rs

1use std::borrow::Cow;
2use actix_web::body::MessageBody;
3use actix_web::dev::{ServiceRequest, ServiceResponse};
4use actix_web::{Error, ResponseError};
5use actix_web::http::{Method, StatusCode, Version};
6use tracing::Span;
7use tracing_actix_web::RootSpanBuilder;
8
9/// Adaptation of [tracing_actix_web::DefaultRootSpanBuilder] which provides less noisy output
10#[derive(Clone, Copy)]
11pub struct NoiselessRootSpanBuilder;
12
13impl RootSpanBuilder for NoiselessRootSpanBuilder {
14    #[allow(unused)] // They're not unused, CLion just doesnt know that
15    fn on_request_start(request: &ServiceRequest) -> Span {
16        let http_route: Cow<'static, str> = request
17            .match_pattern()
18            .map(Into::into)
19            .unwrap_or_else(|| "default".into());
20        let http_method = http_method_str(&request.method());
21        let http_flavor = http_flavor(request.version());
22
23        let span = ::tracing::span!(
24                    ::tracing::Level::INFO,
25                    "HTTP request",
26                    http.method = %http_method,
27                    http.route = %http_route,
28                    http.flavor = %http_flavor,
29                    http.status_code = ::tracing::field::Empty,
30                    exception.message = ::tracing::field::Empty,
31                    exception.details = ::tracing::field::Empty,
32                );
33        span
34    }
35
36    fn on_request_end<B: MessageBody>(span: Span, outcome: &Result<ServiceResponse<B>, Error>) {
37        match &outcome {
38            Ok(response) => {
39                if let Some(error) = response.response().error() {
40                    // use the status code already constructed for the outgoing HTTP response
41                    handle_error(span, response.status(), error.as_response_error());
42                } else {
43                    let code: i32 = response.response().status().as_u16().into();
44                    span.record("http.status_code", code);
45                    span.record("otel.status_code", "OK");
46                }
47            }
48            Err(error) => {
49                let response_error = error.as_response_error();
50                handle_error(span, response_error.status_code(), response_error);
51            }
52        };
53    }
54}
55
56fn handle_error(span: Span, status_code: StatusCode, response_error: &dyn ResponseError) {
57    // pre-formatting errors is a workaround for https://github.com/tokio-rs/tracing/issues/1565
58    let display = format!("{response_error}");
59    let debug = format!("{response_error:?}");
60    span.record("exception.message", &tracing::field::display(display));
61    span.record("exception.details", &tracing::field::display(debug));
62    let code: i32 = status_code.as_u16().into();
63
64    span.record("http.status_code", code);
65}
66
67fn http_flavor(version: Version) -> Cow<'static, str> {
68    match version {
69        Version::HTTP_09 => "0.9".into(),
70        Version::HTTP_10 => "1.0".into(),
71        Version::HTTP_11 => "1.1".into(),
72        Version::HTTP_2 => "2.0".into(),
73        Version::HTTP_3 => "3.0".into(),
74        other => format!("{other:?}").into(),
75    }
76}
77
78fn http_method_str(method: &Method) -> Cow<'static, str> {
79    match method {
80        &Method::OPTIONS => "OPTIONS".into(),
81        &Method::GET => "GET".into(),
82        &Method::POST => "POST".into(),
83        &Method::PUT => "PUT".into(),
84        &Method::DELETE => "DELETE".into(),
85        &Method::HEAD => "HEAD".into(),
86        &Method::TRACE => "TRACE".into(),
87        &Method::CONNECT => "CONNECT".into(),
88        &Method::PATCH => "PATCH".into(),
89        other => other.to_string().into(),
90    }
91}