Skip to content

Commit a850e5c

Browse files
fix(tracing-actix-web): populate trace_id at span creation (#724)
* fix(tracing-actix-web): populate trace_id at span creation * docs: add changelog entry --------- Co-authored-by: Yuki Okushi <huyuumi.dev@gmail.com>
1 parent 2a22c6a commit a850e5c

3 files changed

Lines changed: 167 additions & 7 deletions

File tree

tracing-actix-web/CHANGELOG.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,9 @@
33
## Unreleased
44

55
- Minimum supported Rust version (MSRV) is now 1.88.
6+
- Ensure the OpenTelemetry `trace_id` is populated when request spans are created. [#724]
7+
8+
[#724]: https://github.com/actix/actix-extras/pull/724
69

710
## 0.7.21
811

tracing-actix-web/src/otel.rs

Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -97,6 +97,63 @@ impl Extractor for RequestHeaderCarrier<'_> {
9797
}
9898
}
9999

100+
/// Extract trace_id from the incoming request headers using the global propagator.
101+
/// Returns `Some(trace_id)` if a valid remote parent span context is found,
102+
/// `None` otherwise (trace_id will be generated later by OpenTelemetry).
103+
pub(crate) fn extract_trace_id(req: &ServiceRequest) -> Option<String> {
104+
use opentelemetry::trace::TraceContextExt as _;
105+
106+
let parent_context = opentelemetry::global::get_text_map_propagator(|propagator| {
107+
propagator.extract(&RequestHeaderCarrier::new(req.headers()))
108+
});
109+
let span_context = parent_context.span().span_context().clone();
110+
if !span_context.is_valid() {
111+
return None;
112+
}
113+
114+
// opentelemetry 0.13–0.16 expose `TraceId::to_hex()`;
115+
// 0.17+ removed it, so we format manually instead.
116+
#[cfg(not(any(
117+
feature = "opentelemetry_0_17",
118+
feature = "opentelemetry_0_18",
119+
feature = "opentelemetry_0_19",
120+
feature = "opentelemetry_0_20",
121+
feature = "opentelemetry_0_21",
122+
feature = "opentelemetry_0_22",
123+
feature = "opentelemetry_0_23",
124+
feature = "opentelemetry_0_24",
125+
feature = "opentelemetry_0_25",
126+
feature = "opentelemetry_0_26",
127+
feature = "opentelemetry_0_27",
128+
feature = "opentelemetry_0_28",
129+
feature = "opentelemetry_0_29",
130+
feature = "opentelemetry_0_30",
131+
feature = "opentelemetry_0_31",
132+
)))]
133+
let trace_id = span_context.trace_id().to_hex();
134+
135+
#[cfg(any(
136+
feature = "opentelemetry_0_17",
137+
feature = "opentelemetry_0_18",
138+
feature = "opentelemetry_0_19",
139+
feature = "opentelemetry_0_20",
140+
feature = "opentelemetry_0_21",
141+
feature = "opentelemetry_0_22",
142+
feature = "opentelemetry_0_23",
143+
feature = "opentelemetry_0_24",
144+
feature = "opentelemetry_0_25",
145+
feature = "opentelemetry_0_26",
146+
feature = "opentelemetry_0_27",
147+
feature = "opentelemetry_0_28",
148+
feature = "opentelemetry_0_29",
149+
feature = "opentelemetry_0_30",
150+
feature = "opentelemetry_0_31",
151+
))]
152+
let trace_id = format!("{:032x}", span_context.trace_id());
153+
154+
Some(trace_id)
155+
}
156+
100157
pub(crate) fn set_otel_parent(req: &ServiceRequest, span: &tracing::Span) {
101158
use opentelemetry::trace::TraceContextExt as _;
102159
use tracing_opentelemetry::OpenTelemetrySpanExt as _;

tracing-actix-web/src/root_span_macro.rs

Lines changed: 107 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -89,7 +89,37 @@ macro_rules! root_span {
8989
let connection_info = $request.connection_info();
9090
let request_id = $crate::root_span_macro::private::get_request_id($request);
9191

92+
// Extract trace_id from incoming request headers (e.g. traceparent)
93+
// BEFORE creating the span, so it is available in on_new_span.
94+
let __otel_trace_id = $crate::root_span_macro::private::extract_otel_trace_id(&$request);
95+
96+
// Two inner_span arms: one pre-populates trace_id when a remote parent
97+
// is found in the request headers; the other leaves it Empty for later.
9298
macro_rules! inner_span {
99+
($level:expr, trace_id = $tid:expr) => {
100+
$crate::root_span_macro::private::tracing::span!(
101+
$level,
102+
"HTTP request",
103+
http.method = %http_method,
104+
http.route = %http_route,
105+
http.flavor = %$crate::root_span_macro::private::http_flavor($request.version()),
106+
http.scheme = %$crate::root_span_macro::private::http_scheme(connection_info.scheme()),
107+
http.host = %connection_info.host(),
108+
http.client_ip = %$request.connection_info().realip_remote_addr().unwrap_or(""),
109+
http.user_agent = %user_agent,
110+
http.target = %$request.uri().path_and_query().map(|p| p.as_str()).unwrap_or(""),
111+
http.status_code = $crate::root_span_macro::private::tracing::field::Empty,
112+
otel.name = %format!("{} {}", http_method, http_route),
113+
otel.kind = "server",
114+
otel.status_code = $crate::root_span_macro::private::tracing::field::Empty,
115+
trace_id = %$tid,
116+
request_id = %request_id,
117+
exception.message = $crate::root_span_macro::private::tracing::field::Empty,
118+
// Not proper OpenTelemetry, but their terminology is fairly exception-centric
119+
exception.details = $crate::root_span_macro::private::tracing::field::Empty,
120+
$($field)*
121+
)
122+
};
93123
($level:expr) => {
94124
$crate::root_span_macro::private::tracing::span!(
95125
$level,
@@ -115,13 +145,30 @@ macro_rules! root_span {
115145
)
116146
};
117147
}
118-
let span = match $lvl {
119-
$crate::Level::TRACE => inner_span!($crate::Level::TRACE),
120-
$crate::Level::DEBUG => inner_span!($crate::Level::DEBUG),
121-
$crate::Level::INFO => inner_span!($crate::Level::INFO),
122-
$crate::Level::WARN => inner_span!($crate::Level::WARN),
123-
$crate::Level::ERROR => inner_span!($crate::Level::ERROR),
124-
};
148+
149+
macro_rules! make_span {
150+
($lvl_value:expr, $trace_id:expr) => {
151+
if let Some(ref __tid) = $trace_id {
152+
match $lvl_value {
153+
$crate::Level::TRACE => inner_span!($crate::Level::TRACE, trace_id = __tid),
154+
$crate::Level::DEBUG => inner_span!($crate::Level::DEBUG, trace_id = __tid),
155+
$crate::Level::INFO => inner_span!($crate::Level::INFO, trace_id = __tid),
156+
$crate::Level::WARN => inner_span!($crate::Level::WARN, trace_id = __tid),
157+
$crate::Level::ERROR => inner_span!($crate::Level::ERROR, trace_id = __tid),
158+
}
159+
} else {
160+
match $lvl_value {
161+
$crate::Level::TRACE => inner_span!($crate::Level::TRACE),
162+
$crate::Level::DEBUG => inner_span!($crate::Level::DEBUG),
163+
$crate::Level::INFO => inner_span!($crate::Level::INFO),
164+
$crate::Level::WARN => inner_span!($crate::Level::WARN),
165+
$crate::Level::ERROR => inner_span!($crate::Level::ERROR),
166+
}
167+
}
168+
};
169+
}
170+
171+
let span = make_span!($lvl, __otel_trace_id);
125172
std::mem::drop(connection_info);
126173

127174
// Previously, this line was instrumented with an opentelemetry-specific feature
@@ -152,6 +199,59 @@ pub mod private {
152199

153200
use crate::RequestId;
154201

202+
#[doc(hidden)]
203+
#[allow(unused_variables)]
204+
pub fn extract_otel_trace_id(req: &ServiceRequest) -> Option<String> {
205+
#[cfg(any(
206+
feature = "opentelemetry_0_13",
207+
feature = "opentelemetry_0_14",
208+
feature = "opentelemetry_0_15",
209+
feature = "opentelemetry_0_16",
210+
feature = "opentelemetry_0_17",
211+
feature = "opentelemetry_0_18",
212+
feature = "opentelemetry_0_19",
213+
feature = "opentelemetry_0_20",
214+
feature = "opentelemetry_0_21",
215+
feature = "opentelemetry_0_22",
216+
feature = "opentelemetry_0_23",
217+
feature = "opentelemetry_0_24",
218+
feature = "opentelemetry_0_25",
219+
feature = "opentelemetry_0_26",
220+
feature = "opentelemetry_0_27",
221+
feature = "opentelemetry_0_28",
222+
feature = "opentelemetry_0_29",
223+
feature = "opentelemetry_0_30",
224+
feature = "opentelemetry_0_31",
225+
))]
226+
{
227+
crate::otel::extract_trace_id(req)
228+
}
229+
#[cfg(not(any(
230+
feature = "opentelemetry_0_13",
231+
feature = "opentelemetry_0_14",
232+
feature = "opentelemetry_0_15",
233+
feature = "opentelemetry_0_16",
234+
feature = "opentelemetry_0_17",
235+
feature = "opentelemetry_0_18",
236+
feature = "opentelemetry_0_19",
237+
feature = "opentelemetry_0_20",
238+
feature = "opentelemetry_0_21",
239+
feature = "opentelemetry_0_22",
240+
feature = "opentelemetry_0_23",
241+
feature = "opentelemetry_0_24",
242+
feature = "opentelemetry_0_25",
243+
feature = "opentelemetry_0_26",
244+
feature = "opentelemetry_0_27",
245+
feature = "opentelemetry_0_28",
246+
feature = "opentelemetry_0_29",
247+
feature = "opentelemetry_0_30",
248+
feature = "opentelemetry_0_31",
249+
)))]
250+
{
251+
None
252+
}
253+
}
254+
155255
#[doc(hidden)]
156256
// We need to allow unused variables because the function
157257
// body is empty if the user of the library chose not to activate

0 commit comments

Comments
 (0)