Skip to content

Commit 7637378

Browse files
authored
[APMSVLS-197] Add check to extract trace context present within event.request.headers (#1011)
# [APMSVLS-197] feat: Add check to extract trace context from event.request.headers ## Overview This PR adds automatic trace context extraction from `event.request.headers` for AppSync integration scenarios. This eliminates the need for customers to use customized extractors for RUM → AppSync → Lambda resolver flows where RUM-injected trace context is nested under `event["request"]["headers"]`. ## Problem Previously, trace context from RUM was not propagated through AppSync to Lambda resolvers because the extension only checked: 1. Trigger-specific headers (via span inferrer) 2. `event.headers` (top-level event headers) 3. Request headers (HTTP request headers) For AppSync, the trace context is nested under `event.request.headers`, which was not checked, causing traces to break at the AppSync boundary. ## Solution Added a step that checks `event.request.headers` after all other trace context sources have been attempted. ## Trace Examples ### Before Without this change, trace context is lost at the AppSync boundary: [Trace showing broken propagation through AppSync](https://ddserverless.datadoghq.com/apm/trace/5616290163800553628?graphType=waterfall&shouldShowLegend=true&spanID=5616290163800553628&timeHint=1769698078685.9998&traceQuery=) [Log showing the passed header value for x-datadog-trace-id does not align with the given trace_id](https://ddserverless.datadoghq.com/logs?query=%40lambda.request_id%3A%28cba225c5-7b76-45d8-9a43-86df9031687f%29%20OR%20trace_id%3A5616290163800553628&agg_m=count&agg_m_source=base&agg_t=count&cols=host%2Cservice&event=AwAAAZwKObH6tpUUmwAAABhBWndLT2JqNEFBQWpWdUZHSkF5YzFRQXgAAAAkZjE5YzBhNDQtYjE3Zi00YjljLTk2NzMtNWY2NzI2OTc2MDlmAA2mrg&fromUser=true&messageDisplay=inline&refresh_mode=paused&storage=flex_tier&stream_sort=desc&viz=stream&from_ts=1769697078981&to_ts=1769699079405&live=false) Flow: `RUM → AppSync → Lambda` (trace ID not propagated) ### After With this change, trace context is properly extracted and propagated: [Trace showing successful propagation through AppSync](https://ddserverless.datadoghq.com/apm/trace/12345?graphType=waterfall&shouldShowLegend=true&spanID=7582932037133874185&timeHint=1769698399447&traceQuery=) Flow: `RUM → AppSync → Lambda resolver` (trace continues using value within x-datadog-trace-id) ## Testing ### Unit Tests Added 10 unit tests covering: - Extraction from `event.request.headers` with Datadog headers - Extraction from `event.request.headers` with W3C TraceContext headers - Edge cases (missing headers, empty objects, invalid data) Run tests with: ```bash cargo test test_extract_span_context -- --nocapture ``` [APMSVLS-197]: https://datadoghq.atlassian.net/browse/APMSVLS-197?atlOrigin=eyJpIjoiNWRkNTljNzYxNjVmNDY3MDlhMDU5Y2ZhYzA5YTRkZjUiLCJwIjoiZ2l0aHViLWNvbS1KU1cifQ Co-authored-by: david.ogbureke <[email protected]>
1 parent d052e8d commit 7637378

File tree

1 file changed

+76
-0
lines changed

1 file changed

+76
-0
lines changed

bottlecap/src/lifecycle/invocation/processor.rs

Lines changed: 76 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1076,6 +1076,15 @@ impl Processor {
10761076
return Some(sc);
10771077
}
10781078

1079+
if let Some(sc) = payload_value
1080+
.get("request")
1081+
.and_then(|req| req.get("headers"))
1082+
.and_then(|headers| propagator.extract(headers))
1083+
{
1084+
debug!("Extracted trace context from event.request.headers");
1085+
return Some(sc);
1086+
}
1087+
10791088
if let Some(payload_headers) = payload_value.get("headers") {
10801089
if let Some(sc) = propagator.extract(payload_headers) {
10811090
debug!("Extracted trace context from event headers");
@@ -1851,4 +1860,71 @@ mod tests {
18511860
"Should be snapstart span (id=3), not cold start span (id=2)"
18521861
);
18531862
}
1863+
1864+
#[test]
1865+
fn test_extract_span_context_priority_order() {
1866+
let config = Arc::new(config::Config {
1867+
trace_propagation_style_extract: vec![
1868+
config::trace_propagation_style::TracePropagationStyle::Datadog,
1869+
config::trace_propagation_style::TracePropagationStyle::TraceContext,
1870+
],
1871+
..config::Config::default()
1872+
});
1873+
let propagator = Arc::new(DatadogCompositePropagator::new(Arc::clone(&config)));
1874+
1875+
let mut headers = HashMap::new();
1876+
headers.insert(DATADOG_TRACE_ID_KEY.to_string(), "111".to_string());
1877+
headers.insert(DATADOG_PARENT_ID_KEY.to_string(), "222".to_string());
1878+
1879+
let payload = json!({
1880+
"headers": {
1881+
"x-datadog-trace-id": "333",
1882+
"x-datadog-parent-id": "444"
1883+
},
1884+
"request": {
1885+
"headers": {
1886+
"x-datadog-trace-id": "555",
1887+
"x-datadog-parent-id": "666"
1888+
}
1889+
}
1890+
});
1891+
1892+
let result = Processor::extract_span_context(&headers, &payload, propagator);
1893+
1894+
assert!(result.is_some());
1895+
let context = result.unwrap();
1896+
assert_eq!(
1897+
context.trace_id, 555,
1898+
"Should prioritize event.request.headers as service-specific extraction"
1899+
);
1900+
}
1901+
1902+
#[test]
1903+
fn test_extract_span_context_no_request_headers() {
1904+
let config = Arc::new(config::Config {
1905+
trace_propagation_style_extract: vec![
1906+
config::trace_propagation_style::TracePropagationStyle::Datadog,
1907+
config::trace_propagation_style::TracePropagationStyle::TraceContext,
1908+
],
1909+
..config::Config::default()
1910+
});
1911+
let propagator = Arc::new(DatadogCompositePropagator::new(Arc::clone(&config)));
1912+
let headers = HashMap::new();
1913+
1914+
let payload = json!({
1915+
"argumentsMap": {
1916+
"id": "123"
1917+
},
1918+
"request": {
1919+
"body": "some body"
1920+
}
1921+
});
1922+
1923+
let result = Processor::extract_span_context(&headers, &payload, propagator);
1924+
1925+
assert!(
1926+
result.is_none(),
1927+
"Should return None when no trace context found"
1928+
);
1929+
}
18541930
}

0 commit comments

Comments
 (0)