tracing_actix_web/root_span_builder.rs
1use crate::root_span;
2use actix_web::body::MessageBody;
3use actix_web::dev::{ServiceRequest, ServiceResponse};
4use actix_web::http::StatusCode;
5use actix_web::{Error, ResponseError};
6use tracing::Span;
7
8/// `RootSpanBuilder` allows you to customise the root span attached by
9/// [`TracingLogger`] to incoming requests.
10///
11/// [`TracingLogger`]: crate::TracingLogger
12pub trait RootSpanBuilder {
13 fn on_request_start(request: &ServiceRequest) -> Span;
14 fn on_request_end<B: MessageBody>(span: Span, outcome: &Result<ServiceResponse<B>, Error>);
15}
16
17/// The default [`RootSpanBuilder`] for [`TracingLogger`].
18///
19/// It captures:
20/// - HTTP method (`http.method`);
21/// - HTTP route (`http.route`), with templated parameters;
22/// - HTTP version (`http.flavor`);
23/// - HTTP host (`http.host`);
24/// - Client IP (`http.client_ip`);
25/// - User agent (`http.user_agent`);
26/// - Request path (`http.target`);
27/// - Status code (`http.status_code`);
28/// - [Request id](crate::RequestId) (`request_id`);
29/// - `Display` (`exception.message`) and `Debug` (`exception.details`) representations of the error, if there was an error;
30/// - [Request id](crate::RequestId) (`request_id`);
31/// - [OpenTelemetry trace identifier](https://github.com/open-telemetry/opentelemetry-specification/blob/main/specification/overview.md#spancontext) (`trace_id`). Empty if the feature is not enabled;
32/// - OpenTelemetry span kind, set to `server` (`otel.kind`).
33///
34/// All field names follow [OpenTelemetry's semantic convention](https://github.com/open-telemetry/opentelemetry-specification/tree/main/specification/trace/semantic_conventions).
35///
36/// [`TracingLogger`]: crate::TracingLogger
37pub struct DefaultRootSpanBuilder;
38
39impl RootSpanBuilder for DefaultRootSpanBuilder {
40 fn on_request_start(request: &ServiceRequest) -> Span {
41 root_span!(level = crate::Level::INFO, request)
42 }
43
44 fn on_request_end<B: MessageBody>(span: Span, outcome: &Result<ServiceResponse<B>, Error>) {
45 match &outcome {
46 Ok(response) => {
47 if let Some(error) = response.response().error() {
48 // use the status code already constructed for the outgoing HTTP response
49 handle_error(span, response.status(), error.as_response_error());
50 } else {
51 let code: i32 = response.response().status().as_u16().into();
52 span.record("http.status_code", code);
53 span.record("otel.status_code", "OK");
54 }
55 }
56 Err(error) => {
57 let response_error = error.as_response_error();
58 handle_error(span, response_error.status_code(), response_error);
59 }
60 };
61 }
62}
63
64fn handle_error(span: Span, status_code: StatusCode, response_error: &dyn ResponseError) {
65 // pre-formatting errors is a workaround for https://github.com/tokio-rs/tracing/issues/1565
66 let display = format!("{response_error}");
67 let debug = format!("{response_error:?}");
68 span.record("exception.message", tracing::field::display(display));
69 span.record("exception.details", tracing::field::display(debug));
70 let code: i32 = status_code.as_u16().into();
71
72 span.record("http.status_code", code);
73
74 if status_code.is_client_error() {
75 span.record("otel.status_code", "OK");
76 } else {
77 span.record("otel.status_code", "ERROR");
78 }
79}