Skip to content

Commit f559889

Browse files
committed
feat(sqs): extract SNS-to-SQS trace context
Read `_datadog` from both string and binary SQS message attributes so SNS-propagated context can be extracted.
1 parent 10c2daa commit f559889

9 files changed

Lines changed: 190 additions & 233 deletions

File tree

instrumentation/Cargo.lock

Lines changed: 1 addition & 91 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

instrumentation/datadog-aws-core-test-utils/src/integration_test_helpers.rs

Lines changed: 63 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
// SPDX-License-Identifier: Apache-2.0
33

44
use std::collections::HashMap;
5-
use std::sync::{Arc, Mutex};
5+
use std::sync::{Arc, Mutex, OnceLock};
66

77
use aws_credential_types::provider::SharedCredentialsProvider;
88
use aws_credential_types::Credentials;
@@ -17,6 +17,8 @@ use opentelemetry_sdk::trace::{
1717
use wiremock::matchers::any;
1818
use wiremock::{Mock, MockServer as WireMockServer, Request, Respond, ResponseTemplate};
1919

20+
const HTTP_URL: &str = "http.url";
21+
2022
#[derive(Clone)]
2123
struct CaptureBodyResponder {
2224
status: u16,
@@ -56,27 +58,28 @@ impl MockAwsServer {
5658
/// 2. Build the AWS client with `harness.sdk_config()`.
5759
/// 3. Execute SDK calls, then assert on `harness.finished_spans()` and `harness.server.bodies()`.
5860
///
59-
/// Tests should remain serialized (`#[serial]`) because harness creation sets global
60-
/// OpenTelemetry tracer/propagator state via `init_test_tracer`.
61+
/// The global OpenTelemetry tracer and propagator are initialized once per test
62+
/// binary. Each harness filters exported spans to requests sent to its mock
63+
/// server, so tests can run concurrently.
6164
pub struct TestHarness {
62-
pub exporter: InMemorySpanExporter,
65+
exporter: InMemorySpanExporter,
6366
pub server: MockAwsServer,
6467
}
6568

6669
impl TestHarness {
67-
/// Initializes tracing and starts a mock AWS endpoint returning `200 OK`
70+
/// Ensures tracing is initialized and starts a mock AWS endpoint returning `200 OK`
6871
/// for every request.
6972
pub async fn ok() -> Self {
7073
Self::from_status(200).await
7174
}
7275

73-
/// Initializes tracing and starts a mock AWS endpoint returning `200 OK`
76+
/// Ensures tracing is initialized and starts a mock AWS endpoint returning `200 OK`
7477
/// with the supplied response body.
7578
pub async fn ok_with_body(response_body: impl Into<String>) -> Self {
7679
Self::from_status_and_body(200, response_body).await
7780
}
7881

79-
/// Initializes tracing and starts a mock AWS endpoint returning
82+
/// Ensures tracing is initialized and starts a mock AWS endpoint returning
8083
/// `400 Bad Request` for every request.
8184
pub async fn bad_request() -> Self {
8285
Self::from_status(400).await
@@ -89,7 +92,12 @@ impl TestHarness {
8992

9093
/// Returns all finished spans currently exported by this harness.
9194
pub fn finished_spans(&self) -> Vec<SpanData> {
92-
self.exporter.get_finished_spans().unwrap()
95+
self.exporter
96+
.get_finished_spans()
97+
.unwrap()
98+
.into_iter()
99+
.filter(|span| span_targets_server(span, &self.server.url))
100+
.collect()
93101
}
94102

95103
async fn from_status(status: u16) -> Self {
@@ -135,15 +143,35 @@ pub async fn mock_aws_with_body(status: u16, response_body: impl Into<String>) -
135143
}
136144

137145
/// Registers an `InMemorySpanExporter` as the global tracer provider and
138-
/// a `TraceContextPropagator` as the global propagator.
146+
/// a `TraceContextPropagator` as the global propagator once per test binary.
139147
pub fn init_test_tracer() -> InMemorySpanExporter {
140-
let exporter = InMemorySpanExporter::default();
141-
let provider = SdkTracerProvider::builder()
142-
.with_span_processor(SimpleSpanProcessor::new(exporter.clone()))
143-
.build();
144-
global::set_tracer_provider(provider);
145-
global::set_text_map_propagator(TraceContextPropagator::new());
146-
exporter
148+
static EXPORTER: OnceLock<InMemorySpanExporter> = OnceLock::new();
149+
150+
EXPORTER
151+
.get_or_init(|| {
152+
let exporter = InMemorySpanExporter::default();
153+
let provider = SdkTracerProvider::builder()
154+
.with_span_processor(SimpleSpanProcessor::new(exporter.clone()))
155+
.build();
156+
global::set_tracer_provider(provider);
157+
global::set_text_map_propagator(TraceContextPropagator::new());
158+
exporter
159+
})
160+
.clone()
161+
}
162+
163+
fn span_targets_server(span: &SpanData, server_url: &str) -> bool {
164+
span.attributes.iter().any(|kv| {
165+
kv.key.as_str() == HTTP_URL && url_targets_server(&kv.value.to_string(), server_url)
166+
})
167+
}
168+
169+
fn url_targets_server(url: &str, server_url: &str) -> bool {
170+
let Some(rest) = url.strip_prefix(server_url) else {
171+
return false;
172+
};
173+
174+
rest.is_empty() || rest.starts_with('/') || rest.starts_with('?')
147175
}
148176

149177
/// Builds an `SdkConfig` pointing at `endpoint` with test credentials.
@@ -179,3 +207,22 @@ pub fn split_traceparent(tp: &str) -> (String, String) {
179207
let parts: Vec<&str> = tp.splitn(4, '-').collect();
180208
(parts[1].to_string(), parts[2].to_string())
181209
}
210+
211+
#[cfg(test)]
212+
mod tests {
213+
use super::*;
214+
215+
#[test]
216+
fn url_targets_server_requires_url_boundary() {
217+
let server_url = "http://127.0.0.1:1234";
218+
219+
assert!(url_targets_server(server_url, server_url));
220+
assert!(url_targets_server("http://127.0.0.1:1234/", server_url));
221+
assert!(url_targets_server(
222+
"http://127.0.0.1:1234/?Action=PutTargets",
223+
server_url
224+
));
225+
assert!(!url_targets_server("http://127.0.0.1:12345/", server_url));
226+
assert!(!url_targets_server("http://127.0.0.1:123", server_url));
227+
}
228+
}

instrumentation/datadog-aws-eventbridge/Cargo.toml

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,5 +27,4 @@ opentelemetry = { workspace = true }
2727
[dev-dependencies]
2828
datadog-aws-core-test-utils = { path = "../datadog-aws-core-test-utils" }
2929
aws-types = { workspace = true }
30-
serial_test = "3"
3130
tokio = { workspace = true, features = ["rt", "macros", "net", "io-util"] }

instrumentation/datadog-aws-eventbridge/tests/integration.rs

Lines changed: 0 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -2,17 +2,12 @@
22
// SPDX-License-Identifier: Apache-2.0
33

44
//! Integration tests for EventBridgeInterceptor.
5-
//!
6-
//! Tests are serialized with `#[serial]` because `init_test_tracer` sets the
7-
//! global OTel tracer provider and propagator. Concurrent tests would race on
8-
//! that global state, causing spans to land in the wrong exporter.
95
106
use aws_sdk_eventbridge::types::PutEventsRequestEntry;
117
use aws_types::SdkConfig;
128
use datadog_aws_core_test_utils::integration_test_helpers::{
139
span_attrs, split_traceparent, TestHarness,
1410
};
15-
use serial_test::serial;
1611

1712
use datadog_aws_eventbridge::ConfigExt as _;
1813

@@ -24,7 +19,6 @@ fn eventbridge_client(cfg: &SdkConfig) -> aws_sdk_eventbridge::Client {
2419
}
2520

2621
#[tokio::test]
27-
#[serial]
2822
async fn eventbridge_put_events_creates_span_and_injects_detail() {
2923
let harness = TestHarness::ok().await;
3024
let client = eventbridge_client(&harness.sdk_config());
@@ -68,7 +62,6 @@ async fn eventbridge_put_events_creates_span_and_injects_detail() {
6862
}
6963

7064
#[tokio::test]
71-
#[serial]
7265
async fn eventbridge_put_events_with_bus_name_creates_span() {
7366
let harness = TestHarness::ok().await;
7467
let client = eventbridge_client(&harness.sdk_config());
@@ -88,7 +81,6 @@ async fn eventbridge_put_events_with_bus_name_creates_span() {
8881
}
8982

9083
#[tokio::test]
91-
#[serial]
9284
async fn eventbridge_put_events_multi_entry_creates_single_span() {
9385
let harness = TestHarness::ok().await;
9486
let client = eventbridge_client(&harness.sdk_config());
@@ -118,7 +110,6 @@ async fn eventbridge_put_events_multi_entry_creates_single_span() {
118110
}
119111

120112
#[tokio::test]
121-
#[serial]
122113
async fn eventbridge_put_rule_creates_span_with_rulename() {
123114
let harness = TestHarness::ok().await;
124115
let client = eventbridge_client(&harness.sdk_config());
@@ -133,7 +124,6 @@ async fn eventbridge_put_rule_creates_span_with_rulename() {
133124
}
134125

135126
#[tokio::test]
136-
#[serial]
137127
async fn eventbridge_put_targets_creates_span_with_rulename() {
138128
let harness = TestHarness::ok().await;
139129
let client = eventbridge_client(&harness.sdk_config());

instrumentation/datadog-aws-sns/Cargo.toml

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,5 +26,4 @@ opentelemetry = { workspace = true }
2626
[dev-dependencies]
2727
datadog-aws-core-test-utils = { path = "../datadog-aws-core-test-utils" }
2828
aws-types = { workspace = true }
29-
serial_test = "3"
3029
tokio = { workspace = true, features = ["rt", "macros", "net", "io-util"] }

0 commit comments

Comments
 (0)