Skip to content

Commit 612838b

Browse files
committed
feat(aws-sdk-rust): add aws sdk request metadata groundwork
Add canonical aws.sdk span attribute keys for the current Rust-supported AWS services and introduce handler-owned request metadata extraction for SQS, SNS, and EventBridge. Keep the extraction layer explicit by using per-service operation enums and typed service metadata before converting into generic RequestMetadata. This does not create aws.sdk spans yet; it only prepares the metadata and tag vocabulary they will use.
1 parent 1c8b5a5 commit 612838b

5 files changed

Lines changed: 430 additions & 22 deletions

File tree

Lines changed: 25 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,30 @@
11
// Copyright 2025-Present Datadog, Inc. https://www.datadoghq.com/
22
// SPDX-License-Identifier: Apache-2.0
33

4+
// Trace propagation payload keys.
45
pub(crate) const DATADOG_ATTRIBUTE_KEY: &str = "_datadog";
56
pub(crate) const START_TIME_KEY: &str = "x-datadog-start-time";
6-
pub(crate) const RESOURCE_NAME_KEY: &str = "x-datadog-resource-name";
7+
pub(crate) const DATADOG_RESOURCE_NAME_KEY: &str = "x-datadog-resource-name";
8+
9+
// Generic aws.sdk span attributes.
10+
pub(crate) const AWS_SERVICE: &str = "aws.service";
11+
pub(crate) const AWS_OPERATION: &str = "aws.operation";
12+
pub(crate) const AWS_REGION: &str = "region";
13+
pub(crate) const AWS_PARTITION: &str = "aws.partition";
14+
pub(crate) const AWS_AGENT: &str = "aws.agent";
15+
pub(crate) const AWS_REQUEST_ID: &str = "aws.request_id";
16+
pub(crate) const HTTP_METHOD: &str = "http.method";
17+
pub(crate) const HTTP_URL: &str = "http.url";
18+
pub(crate) const HTTP_STATUS_CODE: &str = "http.status_code";
19+
pub(crate) const COMPONENT: &str = "component";
20+
pub(crate) const SPAN_KIND: &str = "span.kind";
21+
pub(crate) const SERVICE_NAME: &str = "service.name";
22+
pub(crate) const RESOURCE_NAME: &str = "resource.name";
23+
24+
// SQS aws.sdk span attributes.
25+
pub(crate) const CLOUD_RESOURCE_ID: &str = "cloud.resource_id";
26+
pub(crate) const QUEUE_NAME: &str = "queuename";
27+
28+
// SNS aws.sdk span attributes.
29+
pub(crate) const TOPIC_NAME: &str = "topicname";
30+
pub(crate) const TARGET_NAME: &str = "targetname";

integrations/aws/aws-sdk-rust/src/services/eventbridge.rs

Lines changed: 72 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -8,14 +8,38 @@ use aws_sdk_eventbridge::operation::put_events::PutEventsInput;
88
use aws_smithy_runtime_api::box_error::BoxError;
99
use aws_smithy_runtime_api::client::interceptors::context::Input;
1010

11-
use crate::attribute_keys::{DATADOG_ATTRIBUTE_KEY, RESOURCE_NAME_KEY, START_TIME_KEY};
11+
use crate::attribute_keys::{DATADOG_ATTRIBUTE_KEY, DATADOG_RESOURCE_NAME_KEY, START_TIME_KEY};
1212

13-
use super::{AwsServiceHandler, ONE_MB};
13+
use super::{base_request_metadata, AwsServiceHandler, RequestMetadata, ONE_MB};
1414

1515
#[derive(Debug, Clone, Copy, Default)]
1616
pub(crate) struct EventBridgeService;
1717

18+
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
19+
enum EventBridgeOperation {
20+
PutEvents,
21+
}
22+
23+
impl EventBridgeOperation {
24+
fn from_name(operation: &str) -> Option<Self> {
25+
match operation {
26+
"PutEvents" => Some(Self::PutEvents),
27+
_ => None,
28+
}
29+
}
30+
31+
fn name(self) -> &'static str {
32+
match self {
33+
Self::PutEvents => "PutEvents",
34+
}
35+
}
36+
}
37+
1838
impl AwsServiceHandler for EventBridgeService {
39+
fn service_id(&self) -> &'static str {
40+
"EventBridge"
41+
}
42+
1943
fn inject(
2044
&self,
2145
operation: &str,
@@ -24,14 +48,34 @@ impl AwsServiceHandler for EventBridgeService {
2448
) -> Result<(), BoxError> {
2549
inject(operation, trace_headers, input)
2650
}
51+
52+
fn extract_request_metadata(
53+
&self,
54+
operation: &str,
55+
input: &Input,
56+
region: &str,
57+
partition: &str,
58+
) -> Option<RequestMetadata> {
59+
let operation = EventBridgeOperation::from_name(operation)?;
60+
if input.downcast_ref::<PutEventsInput>().is_none() {
61+
return None;
62+
}
63+
64+
Some(base_request_metadata(
65+
self.service_id(),
66+
operation.name(),
67+
region,
68+
partition,
69+
))
70+
}
2771
}
2872

2973
pub(super) fn inject(
3074
operation: &str,
3175
trace_headers: HashMap<String, String>,
3276
input: &mut Input,
3377
) -> Result<(), BoxError> {
34-
if operation == "PutEvents" {
78+
if let Some(EventBridgeOperation::PutEvents) = EventBridgeOperation::from_name(operation) {
3579
if let Some(put_input) = input.downcast_mut::<PutEventsInput>() {
3680
inject_into_put_events(put_input, trace_headers)?;
3781
}
@@ -62,13 +106,13 @@ fn inject_into_put_events(
62106
for entry in entries.iter_mut() {
63107
if let Some(name) = entry.event_bus_name.as_deref() {
64108
ctx.insert(
65-
RESOURCE_NAME_KEY.into(),
109+
DATADOG_RESOURCE_NAME_KEY.into(),
66110
serde_json::Value::String(name.into()),
67111
);
68112
}
69113

70114
let trace_ctx = serde_json::Value::Object(ctx.clone());
71-
ctx.remove(RESOURCE_NAME_KEY);
115+
ctx.remove(DATADOG_RESOURCE_NAME_KEY);
72116

73117
let detail = entry.detail.as_deref().unwrap_or("{}");
74118
let mut detail_map: serde_json::Map<String, serde_json::Value> =
@@ -101,6 +145,7 @@ mod tests {
101145
DATADOG_TRACE_ID_KEY,
102146
};
103147
use aws_sdk_eventbridge::types::PutEventsRequestEntry;
148+
use aws_smithy_runtime_api::client::interceptors::context::Input;
104149

105150
fn parse_detail_datadog(detail: &str) -> HashMap<String, String> {
106151
let parsed: serde_json::Value = serde_json::from_str(detail).unwrap();
@@ -153,7 +198,7 @@ mod tests {
153198
let detail = entries[0].detail.as_ref().unwrap();
154199
let dd = parse_detail_datadog(detail);
155200
assert_eq!(dd[DATADOG_TRACE_ID_KEY], "123456789");
156-
assert!(!dd.contains_key(RESOURCE_NAME_KEY));
201+
assert!(!dd.contains_key(DATADOG_RESOURCE_NAME_KEY));
157202
}
158203

159204
#[test]
@@ -172,7 +217,7 @@ mod tests {
172217
let entries = input.entries.as_ref().unwrap();
173218
let detail = entries[0].detail.as_ref().unwrap();
174219
let dd = parse_detail_datadog(detail);
175-
assert_eq!(dd[RESOURCE_NAME_KEY], "my-bus");
220+
assert_eq!(dd[DATADOG_RESOURCE_NAME_KEY], "my-bus");
176221
}
177222

178223
#[test]
@@ -205,9 +250,9 @@ mod tests {
205250
assert!(dd.contains_key(START_TIME_KEY));
206251
}
207252
let dd0 = parse_detail_datadog(entries[0].detail.as_ref().unwrap());
208-
assert_eq!(dd0[RESOURCE_NAME_KEY], "bus-1");
253+
assert_eq!(dd0[DATADOG_RESOURCE_NAME_KEY], "bus-1");
209254
let dd1 = parse_detail_datadog(entries[1].detail.as_ref().unwrap());
210-
assert!(!dd1.contains_key(RESOURCE_NAME_KEY));
255+
assert!(!dd1.contains_key(DATADOG_RESOURCE_NAME_KEY));
211256
}
212257

213258
#[test]
@@ -285,4 +330,22 @@ mod tests {
285330
.unwrap();
286331
assert!(!detail.contains(DATADOG_ATTRIBUTE_KEY));
287332
}
333+
334+
#[test]
335+
fn extracts_put_events_request_metadata() {
336+
let input = Input::erase(PutEventsInput::builder().build().unwrap());
337+
338+
let metadata = EventBridgeService
339+
.extract_request_metadata("PutEvents", &input, "eu-west-1", "aws")
340+
.unwrap();
341+
342+
assert_eq!(metadata.service_name, "aws.EventBridge");
343+
assert_eq!(metadata.resource_name, "EventBridge.PutEvents");
344+
assert_eq!(metadata.tags["aws.service"], "EventBridge");
345+
assert_eq!(metadata.tags["aws.operation"], "PutEvents");
346+
assert_eq!(metadata.tags["region"], "eu-west-1");
347+
assert_eq!(metadata.tags["aws.partition"], "aws");
348+
assert_eq!(metadata.tags["service.name"], "aws.EventBridge");
349+
assert_eq!(metadata.tags["resource.name"], "EventBridge.PutEvents");
350+
}
288351
}

integrations/aws/aws-sdk-rust/src/services/mod.rs

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,10 @@ use std::collections::HashMap;
1010
use aws_smithy_runtime_api::box_error::BoxError;
1111
use aws_smithy_runtime_api::client::interceptors::context::Input;
1212

13+
use crate::attribute_keys::{
14+
AWS_OPERATION, AWS_PARTITION, AWS_REGION, AWS_SERVICE, RESOURCE_NAME, SERVICE_NAME,
15+
};
16+
1317
pub(crate) use eventbridge::EventBridgeService;
1418
pub(crate) use sns::SnsService;
1519
pub(crate) use sqs::SqsService;
@@ -18,12 +22,53 @@ pub(crate) const MAX_MESSAGE_ATTRIBUTES: usize = 10;
1822
pub(crate) const ONE_MB: usize = 1024 * 1024;
1923

2024
pub(crate) trait AwsServiceHandler {
25+
fn service_id(&self) -> &'static str;
26+
2127
fn inject(
2228
&self,
2329
operation: &str,
2430
trace_headers: HashMap<String, String>,
2531
input: &mut Input,
2632
) -> Result<(), BoxError>;
33+
34+
fn extract_request_metadata(
35+
&self,
36+
operation: &str,
37+
input: &Input,
38+
region: &str,
39+
partition: &str,
40+
) -> Option<RequestMetadata>;
41+
}
42+
43+
#[derive(Debug, Clone, PartialEq, Eq)]
44+
pub(crate) struct RequestMetadata {
45+
pub(crate) service_name: String,
46+
pub(crate) resource_name: String,
47+
pub(crate) tags: HashMap<&'static str, String>,
48+
}
49+
50+
pub(crate) fn base_request_metadata(
51+
service_id: &'static str,
52+
operation: &str,
53+
region: &str,
54+
partition: &str,
55+
) -> RequestMetadata {
56+
let service_name = format!("aws.{service_id}");
57+
let resource_name = format!("{service_id}.{operation}");
58+
let tags = HashMap::from([
59+
(AWS_SERVICE, service_id.to_string()),
60+
(AWS_OPERATION, operation.to_string()),
61+
(AWS_REGION, region.to_string()),
62+
(AWS_PARTITION, partition.to_string()),
63+
(SERVICE_NAME, service_name.clone()),
64+
(RESOURCE_NAME, resource_name.clone()),
65+
]);
66+
67+
RequestMetadata {
68+
service_name,
69+
resource_name,
70+
tags,
71+
}
2772
}
2873

2974
#[cfg(test)]

0 commit comments

Comments
 (0)