From 6618e054cb96931ee66e58e8214d58a19670f165 Mon Sep 17 00:00:00 2001 From: AJ Stuyvenberg Date: Wed, 23 Oct 2024 13:27:18 -0400 Subject: [PATCH 01/31] wip: sqs --- .../src/lifecycle/invocation/span_inferrer.rs | 26 +- .../src/lifecycle/invocation/triggers/mod.rs | 1 + .../invocation/triggers/sqs_event.rs | 332 ++++++++++++++++++ 3 files changed, 357 insertions(+), 2 deletions(-) create mode 100644 bottlecap/src/lifecycle/invocation/triggers/sqs_event.rs diff --git a/bottlecap/src/lifecycle/invocation/span_inferrer.rs b/bottlecap/src/lifecycle/invocation/span_inferrer.rs index 7b2a0eefc..73fd9a60d 100644 --- a/bottlecap/src/lifecycle/invocation/span_inferrer.rs +++ b/bottlecap/src/lifecycle/invocation/span_inferrer.rs @@ -6,7 +6,7 @@ use tracing::debug; use crate::config::AwsConfig; use crate::lifecycle::invocation::triggers::{ - api_gateway_http_event::APIGatewayHttpEvent, api_gateway_rest_event::APIGatewayRestEvent, + api_gateway_http_event::APIGatewayHttpEvent, api_gateway_rest_event::APIGatewayRestEvent, sqs_event::SqsRecord, Trigger, }; @@ -81,11 +81,33 @@ impl SpanInferrer { ), ]); + self.is_async_span = t.is_async(); + self.inferred_span = Some(span); + } + } else if SqsRecord::is_match(&payload_value) { + if let Some(t) = SqsRecord::new(payload_value) { + let mut span = Span { + span_id: Self::generate_span_id(), + ..Default::default() + }; + + t.enrich_span(&mut span); + span.meta.extend([ + ( + FUNCTION_TRIGGER_EVENT_SOURCE_TAG.to_string(), + "sqs".to_string(), + ), + ( + FUNCTION_TRIGGER_EVENT_SOURCE_ARN_TAG.to_string(), + t.get_arn(&aws_config.region), + ), + ]); + debug!("SQS span is {:?}", span); self.is_async_span = t.is_async(); self.inferred_span = Some(span); } } else { - debug!("Unable to infer span from payload"); + debug!("Unable to infer span from payload: no matching trigger found"); } } else { debug!("Unable to serialize payload"); diff --git a/bottlecap/src/lifecycle/invocation/triggers/mod.rs b/bottlecap/src/lifecycle/invocation/triggers/mod.rs index 5eb32ec6b..b1cd63083 100644 --- a/bottlecap/src/lifecycle/invocation/triggers/mod.rs +++ b/bottlecap/src/lifecycle/invocation/triggers/mod.rs @@ -6,6 +6,7 @@ use serde_json::Value; pub mod api_gateway_http_event; pub mod api_gateway_rest_event; +pub mod sqs_event; pub trait Trigger: Sized { fn new(payload: Value) -> Option; diff --git a/bottlecap/src/lifecycle/invocation/triggers/sqs_event.rs b/bottlecap/src/lifecycle/invocation/triggers/sqs_event.rs new file mode 100644 index 000000000..243715ba6 --- /dev/null +++ b/bottlecap/src/lifecycle/invocation/triggers/sqs_event.rs @@ -0,0 +1,332 @@ +use datadog_trace_protobuf::pb::Span; +use std::time::{SystemTime, UNIX_EPOCH}; +use serde::{Deserialize, Serialize}; +use serde_json::Value; +use std::collections::HashMap; +use tracing::debug; + +use crate::lifecycle::invocation::{ + processor::MS_TO_NS, + triggers::{get_aws_partition_by_region, Trigger}, +}; + +#[derive(Debug, Serialize, Deserialize, Clone, PartialEq)] +pub struct SqsEvent { + #[serde(rename = "Records")] + pub records: Vec, +} + +#[derive(Debug, Deserialize, Serialize, Clone, PartialEq)] +pub struct SqsRecord{ + #[serde(rename = "messageId")] + pub message_id: String, + #[serde(rename = "receiptHandle")] + pub receipt_handle: String, + pub body: String, + pub attributes: Attributes, + #[serde(rename = "messageAttributes")] + pub message_attributes: HashMap, + #[serde(rename = "md5OfBody")] + pub md5_of_body: String, + #[serde(rename = "eventSource")] + pub event_source: String, + #[serde(rename = "eventSourceARN")] + pub event_source_arn: String, + #[serde(rename = "awsRegion")] + pub aws_region: String, +} + + +#[derive(Debug, Serialize, Deserialize, Clone, PartialEq)] +pub struct MessageAttribute { + #[serde(rename = "stringValue")] + pub string_value: Option, + #[serde(rename = "binaryValue")] + pub binary_value: Option, + #[serde(rename = "stringListValues")] + pub string_list_values: Option>, + #[serde(rename = "binaryListValues")] + pub binary_list_values: Option>, + #[serde(rename = "dataType")] + pub data_type: String, +} + +#[derive(Debug, Serialize, Deserialize, Clone, PartialEq)] +pub struct Attributes { + #[serde(rename = "ApproximateFirstReceiveTimestamp")] + pub approximate_first_receive_timestamp: String, + #[serde(rename = "ApproximateReceiveCount")] + pub approximate_receive_count: String, + #[serde(rename = "SentTimestamp")] + pub sent_timestamp: String, +} + +impl Trigger for SqsRecord { + fn new(payload: Value) -> Option { + let records = payload.get("Records").and_then(Value::as_array); + match records { + Some(records) => match serde_json::from_value::(records[0].clone()) { + Ok(event) => Some(event), + Err(e) => { + debug!("Failed to deserialize SQSEvent: {}", e); + None + } + }, + None => None, + } + } + + fn is_match(payload: &Value) -> bool { + if let Some(first_record) = payload.get("Records").and_then(Value::as_array).and_then(|r| r.first()).take() { + first_record.get("eventSource").and_then(Value::as_str).map_or(false, |s| s == "aws:sqs") + } else { + false + } + } + + #[allow(clippy::cast_possible_truncation)] + fn enrich_span(&self, span: &mut Span) { + debug!("Enriching an Inferred Span for an SQS Event"); + let resource = self.event_source_arn.clone().split(':').last().unwrap_or_default().to_string(); + let start_time = (self.attributes.sent_timestamp.parse::().unwrap_or_default() as f64 * MS_TO_NS) as i64; + // todo: service mapping + let service_name = "sqs"; + + span.name = "aws.sqs".to_string(); + span.service = service_name.to_string(); + span.resource.clone_from(&resource); + span.r#type = "web".to_string(); + span.start = start_time; + // duration is current_time_epoch - start_time + span.duration = SystemTime::now() + .duration_since(UNIX_EPOCH) + .unwrap() + .as_nanos() as i64 + - start_time; + span.meta.extend(HashMap::from([ + ("operation_name".to_string(), "aws.sqs".to_string()), + ( + "receipt_handle".to_string(), + self.receipt_handle.clone(), + ), + ( "retry_count".to_string(), self.attributes.approximate_receive_count.clone()), + ("sender_id".to_string(), self.event_source.clone()), + ("source_arn".to_string(), self.event_source_arn.clone()), + ("aws_region".to_string(), self.aws_region.clone()), + ("resource_names".to_string(), resource.clone()), + ])); + } + + fn get_tags(&self) -> HashMap { + let tags = HashMap::from([ + ( "retry_count".to_string(), self.attributes.approximate_receive_count.clone()), + ("sender_id".to_string(), self.event_source.clone()), + ("source_arn".to_string(), self.event_source_arn.clone()), + ("aws_region".to_string(), self.aws_region.clone()), + ]); + + tags + } + + fn get_arn(&self, region: &str) -> String { + if let [_, _, _, _, account, queue_name] = self.event_source_arn.split(':').collect::>().as_slice() { + format!( + "arn:aws:sqs:{}:{}:{}", + get_aws_partition_by_region(region), + account, + queue_name + ) + } else { + "".to_string() + } + } + + fn is_async(&self) -> bool { + true + } +} + +#[cfg(test)] +mod tests { + use super::*; + use crate::lifecycle::invocation::triggers::test_utils::read_json_file; + + #[test] + fn test_new() { + let json = read_json_file("api_gateway_rest_event.json"); + let payload = serde_json::from_str(&json).expect("Failed to deserialize into Value"); + let result = APIGatewayRestEvent::new(payload) + .expect("Failed to deserialize into APIGatewayRestEvent"); + + let expected = APIGatewayRestEvent { + headers: HashMap::from([ + ("Header1".to_string(), "value1".to_string()), + ("Header2".to_string(), "value2".to_string()), + ]), + request_context: RequestContext { + stage: "$default".to_string(), + request_id: "id=".to_string(), + api_id: "id".to_string(), + domain_name: "id.execute-api.us-east-1.amazonaws.com".to_string(), + time_epoch: 1_583_349_317_135, + method: "GET".to_string(), + path: "/my/path".to_string(), + protocol: "HTTP/1.1".to_string(), + resource_path: "/path".to_string(), + identity: Identity { + source_ip: "IP".to_string(), + user_agent: "user-agent".to_string(), + }, + }, + }; + + assert_eq!(result, expected); + } + + #[test] + fn test_is_match() { + let json = read_json_file("api_gateway_rest_event.json"); + let payload = + serde_json::from_str(&json).expect("Failed to deserialize APIGatewayRestEvent"); + + assert!(APIGatewayRestEvent::is_match(&payload)); + } + + #[test] + fn test_is_not_match() { + let json = read_json_file("api_gateway_http_event.json"); + let payload = + serde_json::from_str(&json).expect("Failed to deserialize APIGatewayRestEvent"); + assert!(!APIGatewayRestEvent::is_match(&payload)); + } + + #[test] + fn test_enrich_span() { + let json = read_json_file("api_gateway_rest_event.json"); + let payload = serde_json::from_str(&json).expect("Failed to deserialize into Value"); + let event = + APIGatewayRestEvent::new(payload).expect("Failed to deserialize APIGatewayRestEvent"); + let mut span = Span::default(); + event.enrich_span(&mut span); + assert_eq!(span.name, "aws.apigateway"); + assert_eq!(span.service, "id.execute-api.us-east-1.amazonaws.com"); + assert_eq!(span.resource, "GET /path"); + assert_eq!(span.r#type, "http"); + + assert_eq!( + span.meta, + HashMap::from([ + ("endpoint".to_string(), "/my/path".to_string()), + ( + "http.url".to_string(), + "https://id.execute-api.us-east-1.amazonaws.com/my/path".to_string() + ), + ("http.method".to_string(), "GET".to_string()), + ("http.protocol".to_string(), "HTTP/1.1".to_string()), + ("http.source_ip".to_string(), "IP".to_string()), + ("http.user_agent".to_string(), "user-agent".to_string()), + ("http.route".to_string(), "/path".to_string()), + ("operation_name".to_string(), "aws.apigateway".to_string()), + ("request_id".to_string(), "id=".to_string()), + ("resource_names".to_string(), "GET /path".to_string()), + ]) + ); + } + + #[test] + fn test_get_tags() { + let json = read_json_file("api_gateway_rest_event.json"); + let payload = serde_json::from_str(&json).expect("Failed to deserialize into Value"); + let event = + APIGatewayRestEvent::new(payload).expect("Failed to deserialize APIGatewayRestEvent"); + let tags = event.get_tags(); + + let expected = HashMap::from([ + ( + "http.url".to_string(), + "https://id.execute-api.us-east-1.amazonaws.com/my/path".to_string(), + ), + ("http.url_details.path".to_string(), "/my/path".to_string()), + ("http.method".to_string(), "GET".to_string()), + ("http.route".to_string(), "/path".to_string()), + ("http.user_agent".to_string(), "user-agent".to_string()), + ]); + + assert_eq!(tags, expected); + } + + #[test] + fn test_enrich_parameterized_span() { + let json = read_json_file("api_gateway_rest_event_parameterized.json"); + let payload = serde_json::from_str(&json).expect("Failed to deserialize into Value"); + let event = + APIGatewayRestEvent::new(payload).expect("Failed to deserialize APIGatewayRestEvent"); + let mut span = Span::default(); + event.enrich_span(&mut span); + assert_eq!(span.name, "aws.apigateway"); + assert_eq!( + span.service, + "mcwkra0ya4.execute-api.sa-east-1.amazonaws.com" + ); + assert_eq!(span.resource, "GET /user/{id}"); + assert_eq!(span.r#type, "http"); + let expected = HashMap::from([ + ("endpoint".to_string(), "/dev/user/42".to_string()), + ( + "http.url".to_string(), + "https://mcwkra0ya4.execute-api.sa-east-1.amazonaws.com/dev/user/42".to_string(), + ), + ("http.method".to_string(), "GET".to_string()), + ("http.protocol".to_string(), "HTTP/1.1".to_string()), + ("http.source_ip".to_string(), "76.115.124.192".to_string()), + ("http.user_agent".to_string(), "curl/8.1.2".to_string()), + ("http.route".to_string(), "/user/{id}".to_string()), + ("operation_name".to_string(), "aws.apigateway".to_string()), + ( + "request_id".to_string(), + "e16399f7-e984-463a-9931-745ba021a27f".to_string(), + ), + ("resource_names".to_string(), "GET /user/{id}".to_string()), + ]); + assert_eq!(span.meta, expected); + } + + #[test] + fn test_get_tags_parameterized() { + let json = read_json_file("api_gateway_rest_event_parameterized.json"); + let payload = serde_json::from_str(&json).expect("Failed to deserialize into Value"); + let event = + APIGatewayRestEvent::new(payload).expect("Failed to deserialize APIGatewayRestEvent"); + let tags = event.get_tags(); + + assert_eq!( + tags, + HashMap::from([ + ( + "http.url".to_string(), + "https://mcwkra0ya4.execute-api.sa-east-1.amazonaws.com/dev/user/42" + .to_string(), + ), + ( + "http.url_details.path".to_string(), + "/dev/user/42".to_string(), + ), + ("http.method".to_string(), "GET".to_string()), + ("http.route".to_string(), "/user/{id}".to_string()), + ("http.user_agent".to_string(), "curl/8.1.2".to_string()), + ]) + ); + } + + #[test] + fn test_get_arn() { + let json = read_json_file("api_gateway_rest_event.json"); + let payload = serde_json::from_str(&json).expect("Failed to deserialize into Value"); + let event = + APIGatewayRestEvent::new(payload).expect("Failed to deserialize APIGatewayRestEvent"); + assert_eq!( + event.get_arn("us-east-1"), + "arn:aws:apigateway:us-east-1::/restapis/id/stages/$default" + ); + } +} From 058059e955e0a9856f9a1fa0d14ee24e05c92863 Mon Sep 17 00:00:00 2001 From: AJ Stuyvenberg Date: Wed, 23 Oct 2024 16:02:03 -0400 Subject: [PATCH 02/31] feat: sqs tests --- .../invocation/triggers/sqs_event.rs | 173 ++++++------------ bottlecap/tests/payloads/sqs_event.json | 20 ++ 2 files changed, 71 insertions(+), 122 deletions(-) create mode 100644 bottlecap/tests/payloads/sqs_event.json diff --git a/bottlecap/src/lifecycle/invocation/triggers/sqs_event.rs b/bottlecap/src/lifecycle/invocation/triggers/sqs_event.rs index 243715ba6..499426a24 100644 --- a/bottlecap/src/lifecycle/invocation/triggers/sqs_event.rs +++ b/bottlecap/src/lifecycle/invocation/triggers/sqs_event.rs @@ -59,6 +59,8 @@ pub struct Attributes { pub approximate_receive_count: String, #[serde(rename = "SentTimestamp")] pub sent_timestamp: String, + #[serde(rename = "SenderId")] + pub sender_id: String, } impl Trigger for SqsRecord { @@ -110,7 +112,7 @@ impl Trigger for SqsRecord { self.receipt_handle.clone(), ), ( "retry_count".to_string(), self.attributes.approximate_receive_count.clone()), - ("sender_id".to_string(), self.event_source.clone()), + ("sender_id".to_string(), self.attributes.sender_id.clone()), ("source_arn".to_string(), self.event_source_arn.clone()), ("aws_region".to_string(), self.aws_region.clone()), ("resource_names".to_string(), resource.clone()), @@ -120,7 +122,7 @@ impl Trigger for SqsRecord { fn get_tags(&self) -> HashMap { let tags = HashMap::from([ ( "retry_count".to_string(), self.attributes.approximate_receive_count.clone()), - ("sender_id".to_string(), self.event_source.clone()), + ("sender_id".to_string(), self.attributes.sender_id.clone()), ("source_arn".to_string(), self.event_source_arn.clone()), ("aws_region".to_string(), self.aws_region.clone()), ]); @@ -153,31 +155,27 @@ mod tests { #[test] fn test_new() { - let json = read_json_file("api_gateway_rest_event.json"); + let json = read_json_file("sqs_event.json"); let payload = serde_json::from_str(&json).expect("Failed to deserialize into Value"); - let result = APIGatewayRestEvent::new(payload) - .expect("Failed to deserialize into APIGatewayRestEvent"); + let result = SqsRecord::new(payload) + .expect("Failed to deserialize into Record"); - let expected = APIGatewayRestEvent { - headers: HashMap::from([ - ("Header1".to_string(), "value1".to_string()), - ("Header2".to_string(), "value2".to_string()), - ]), - request_context: RequestContext { - stage: "$default".to_string(), - request_id: "id=".to_string(), - api_id: "id".to_string(), - domain_name: "id.execute-api.us-east-1.amazonaws.com".to_string(), - time_epoch: 1_583_349_317_135, - method: "GET".to_string(), - path: "/my/path".to_string(), - protocol: "HTTP/1.1".to_string(), - resource_path: "/path".to_string(), - identity: Identity { - source_ip: "IP".to_string(), - user_agent: "user-agent".to_string(), - }, + let expected = SqsRecord { + message_id: "19dd0b57-b21e-4ac1-bd88-01bbb068cb78".to_string(), + receipt_handle: "MessageReceiptHandle".to_string(), + body: "Hello from SQS!".to_string(), + attributes: Attributes { + approximate_first_receive_timestamp: "1523232000001".to_string(), + approximate_receive_count: "1".to_string(), + sent_timestamp: "1523232000000".to_string(), + sender_id: "123456789012".to_string(), }, + message_attributes: HashMap::from([]), + md5_of_body: "{{{md5_of_body}}}".to_string(), + event_source: "aws:sqs".to_string(), + event_source_arn: "arn:aws:sqs:us-east-1:123456789012:MyQueue".to_string(), + aws_region: "us-east-1".to_string(), + }; assert_eq!(result, expected); @@ -185,148 +183,79 @@ mod tests { #[test] fn test_is_match() { - let json = read_json_file("api_gateway_rest_event.json"); + let json = read_json_file("sqs_event.json"); let payload = - serde_json::from_str(&json).expect("Failed to deserialize APIGatewayRestEvent"); + serde_json::from_str(&json).expect("Failed to deserialize SqsRecord"); - assert!(APIGatewayRestEvent::is_match(&payload)); + assert!(SqsRecord::is_match(&payload)); } #[test] fn test_is_not_match() { let json = read_json_file("api_gateway_http_event.json"); let payload = - serde_json::from_str(&json).expect("Failed to deserialize APIGatewayRestEvent"); - assert!(!APIGatewayRestEvent::is_match(&payload)); + serde_json::from_str(&json).expect("Failed to deserialize SqsRecord"); + assert!(!SqsRecord::is_match(&payload)); } #[test] fn test_enrich_span() { - let json = read_json_file("api_gateway_rest_event.json"); + let json = read_json_file("sqs_event.json"); let payload = serde_json::from_str(&json).expect("Failed to deserialize into Value"); let event = - APIGatewayRestEvent::new(payload).expect("Failed to deserialize APIGatewayRestEvent"); + SqsRecord::new(payload).expect("Failed to deserialize SqsRecord"); let mut span = Span::default(); event.enrich_span(&mut span); - assert_eq!(span.name, "aws.apigateway"); - assert_eq!(span.service, "id.execute-api.us-east-1.amazonaws.com"); - assert_eq!(span.resource, "GET /path"); - assert_eq!(span.r#type, "http"); + assert_eq!(span.name, "aws.sqs"); + assert_eq!(span.service, "sqs"); + assert_eq!(span.resource, "MyQueue"); + assert_eq!(span.r#type, "web"); assert_eq!( span.meta, HashMap::from([ - ("endpoint".to_string(), "/my/path".to_string()), + ("operation_name".to_string(), "aws.sqs".to_string()), ( - "http.url".to_string(), - "https://id.execute-api.us-east-1.amazonaws.com/my/path".to_string() + "receipt_handle".to_string(), + "MessageReceiptHandle".to_string(), ), - ("http.method".to_string(), "GET".to_string()), - ("http.protocol".to_string(), "HTTP/1.1".to_string()), - ("http.source_ip".to_string(), "IP".to_string()), - ("http.user_agent".to_string(), "user-agent".to_string()), - ("http.route".to_string(), "/path".to_string()), - ("operation_name".to_string(), "aws.apigateway".to_string()), - ("request_id".to_string(), "id=".to_string()), - ("resource_names".to_string(), "GET /path".to_string()), + ("retry_count".to_string(), 1.to_string()), + ("sender_id".to_string(), "123456789012".to_string()), + ("source_arn".to_string(), "arn:aws:sqs:us-east-1:123456789012:MyQueue".to_string()), + ("aws_region".to_string(), "us-east-1".to_string()), + ("resource_names".to_string(), "MyQueue".to_string()), ]) ); } #[test] fn test_get_tags() { - let json = read_json_file("api_gateway_rest_event.json"); + let json = read_json_file("sqs_event.json"); let payload = serde_json::from_str(&json).expect("Failed to deserialize into Value"); let event = - APIGatewayRestEvent::new(payload).expect("Failed to deserialize APIGatewayRestEvent"); + SqsRecord::new(payload).expect("Failed to deserialize SqsRecord"); let tags = event.get_tags(); let expected = HashMap::from([ - ( - "http.url".to_string(), - "https://id.execute-api.us-east-1.amazonaws.com/my/path".to_string(), - ), - ("http.url_details.path".to_string(), "/my/path".to_string()), - ("http.method".to_string(), "GET".to_string()), - ("http.route".to_string(), "/path".to_string()), - ("http.user_agent".to_string(), "user-agent".to_string()), + ("retry_count".to_string(), 1.to_string()), + ("sender_id".to_string(), "123456789012".to_string()), + ("source_arn".to_string(), "arn:aws:sqs:us-east-1:123456789012:MyQueue".to_string()), + ("aws_region".to_string(), "us-east-1".to_string()), ]); assert_eq!(tags, expected); } - - #[test] - fn test_enrich_parameterized_span() { - let json = read_json_file("api_gateway_rest_event_parameterized.json"); - let payload = serde_json::from_str(&json).expect("Failed to deserialize into Value"); - let event = - APIGatewayRestEvent::new(payload).expect("Failed to deserialize APIGatewayRestEvent"); - let mut span = Span::default(); - event.enrich_span(&mut span); - assert_eq!(span.name, "aws.apigateway"); - assert_eq!( - span.service, - "mcwkra0ya4.execute-api.sa-east-1.amazonaws.com" - ); - assert_eq!(span.resource, "GET /user/{id}"); - assert_eq!(span.r#type, "http"); - let expected = HashMap::from([ - ("endpoint".to_string(), "/dev/user/42".to_string()), - ( - "http.url".to_string(), - "https://mcwkra0ya4.execute-api.sa-east-1.amazonaws.com/dev/user/42".to_string(), - ), - ("http.method".to_string(), "GET".to_string()), - ("http.protocol".to_string(), "HTTP/1.1".to_string()), - ("http.source_ip".to_string(), "76.115.124.192".to_string()), - ("http.user_agent".to_string(), "curl/8.1.2".to_string()), - ("http.route".to_string(), "/user/{id}".to_string()), - ("operation_name".to_string(), "aws.apigateway".to_string()), - ( - "request_id".to_string(), - "e16399f7-e984-463a-9931-745ba021a27f".to_string(), - ), - ("resource_names".to_string(), "GET /user/{id}".to_string()), - ]); - assert_eq!(span.meta, expected); - } - - #[test] - fn test_get_tags_parameterized() { - let json = read_json_file("api_gateway_rest_event_parameterized.json"); - let payload = serde_json::from_str(&json).expect("Failed to deserialize into Value"); - let event = - APIGatewayRestEvent::new(payload).expect("Failed to deserialize APIGatewayRestEvent"); - let tags = event.get_tags(); - - assert_eq!( - tags, - HashMap::from([ - ( - "http.url".to_string(), - "https://mcwkra0ya4.execute-api.sa-east-1.amazonaws.com/dev/user/42" - .to_string(), - ), - ( - "http.url_details.path".to_string(), - "/dev/user/42".to_string(), - ), - ("http.method".to_string(), "GET".to_string()), - ("http.route".to_string(), "/user/{id}".to_string()), - ("http.user_agent".to_string(), "curl/8.1.2".to_string()), - ]) - ); - } + #[test] fn test_get_arn() { - let json = read_json_file("api_gateway_rest_event.json"); + let json = read_json_file("sqs_event.json"); let payload = serde_json::from_str(&json).expect("Failed to deserialize into Value"); let event = - APIGatewayRestEvent::new(payload).expect("Failed to deserialize APIGatewayRestEvent"); + SqsRecord::new(payload).expect("Failed to deserialize SqsRecord"); assert_eq!( event.get_arn("us-east-1"), - "arn:aws:apigateway:us-east-1::/restapis/id/stages/$default" + "arn:aws:sqs:aws:123456789012:MyQueue" ); } } diff --git a/bottlecap/tests/payloads/sqs_event.json b/bottlecap/tests/payloads/sqs_event.json new file mode 100644 index 000000000..e7e5eb73b --- /dev/null +++ b/bottlecap/tests/payloads/sqs_event.json @@ -0,0 +1,20 @@ +{ + "Records": [ + { + "messageId": "19dd0b57-b21e-4ac1-bd88-01bbb068cb78", + "receiptHandle": "MessageReceiptHandle", + "body": "Hello from SQS!", + "attributes": { + "ApproximateReceiveCount": "1", + "SentTimestamp": "1523232000000", + "SenderId": "123456789012", + "ApproximateFirstReceiveTimestamp": "1523232000001" + }, + "messageAttributes": {}, + "md5OfBody": "{{{md5_of_body}}}", + "eventSource": "aws:sqs", + "eventSourceARN": "arn:aws:sqs:us-east-1:123456789012:MyQueue", + "awsRegion": "us-east-1" + } + ] +} From 94b7fc789311e1737dbe57f8ebe16107a0e8625b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?jordan=20gonz=C3=A1lez?= <30836115+duncanista@users.noreply.github.com> Date: Wed, 23 Oct 2024 16:05:35 -0400 Subject: [PATCH 03/31] invert duration check --- bottlecap/src/lifecycle/invocation/span_inferrer.rs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/bottlecap/src/lifecycle/invocation/span_inferrer.rs b/bottlecap/src/lifecycle/invocation/span_inferrer.rs index 9f7552dbd..5137081f1 100644 --- a/bottlecap/src/lifecycle/invocation/span_inferrer.rs +++ b/bottlecap/src/lifecycle/invocation/span_inferrer.rs @@ -141,7 +141,8 @@ impl SpanInferrer { pub fn complete_inferred_span(&mut self, invocation_span: &Span) { if let Some(s) = &mut self.inferred_span { if self.is_async_span { - if s.duration != 0 { + // SNS to SQS span duration will be set + if s.duration == 0 { let duration = invocation_span.start - s.start; s.duration = duration; } From ea156bc1de157840299b278ab6d5e06ee7eb65ed Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?jordan=20gonz=C3=A1lez?= <30836115+duncanista@users.noreply.github.com> Date: Wed, 23 Oct 2024 16:05:48 -0400 Subject: [PATCH 04/31] remove duration set --- bottlecap/src/lifecycle/invocation/triggers/sqs_event.rs | 6 ------ 1 file changed, 6 deletions(-) diff --git a/bottlecap/src/lifecycle/invocation/triggers/sqs_event.rs b/bottlecap/src/lifecycle/invocation/triggers/sqs_event.rs index 752253e72..50e07ef25 100644 --- a/bottlecap/src/lifecycle/invocation/triggers/sqs_event.rs +++ b/bottlecap/src/lifecycle/invocation/triggers/sqs_event.rs @@ -115,12 +115,6 @@ impl Trigger for SqsRecord { span.resource.clone_from(&resource); span.r#type = "web".to_string(); span.start = start_time; - // duration is current_time_epoch - start_time - span.duration = SystemTime::now() - .duration_since(UNIX_EPOCH) - .unwrap() - .as_nanos() as i64 - - start_time; span.meta.extend(HashMap::from([ ("operation_name".to_string(), "aws.sqs".to_string()), ("receipt_handle".to_string(), self.receipt_handle.clone()), From dad27a862458a02f447c107b3c709df328530b82 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?jordan=20gonz=C3=A1lez?= <30836115+duncanista@users.noreply.github.com> Date: Wed, 23 Oct 2024 16:11:01 -0400 Subject: [PATCH 05/31] fmt and add `test_get_arn` --- .../invocation/triggers/sqs_event.rs | 55 ++++++++++--------- 1 file changed, 28 insertions(+), 27 deletions(-) diff --git a/bottlecap/src/lifecycle/invocation/triggers/sqs_event.rs b/bottlecap/src/lifecycle/invocation/triggers/sqs_event.rs index 6caff110a..8e973436a 100644 --- a/bottlecap/src/lifecycle/invocation/triggers/sqs_event.rs +++ b/bottlecap/src/lifecycle/invocation/triggers/sqs_event.rs @@ -2,7 +2,6 @@ use datadog_trace_protobuf::pb::Span; use serde::{Deserialize, Serialize}; use serde_json::Value; use std::collections::HashMap; -use std::time::{SystemTime, UNIX_EPOCH}; use tracing::debug; use crate::lifecycle::invocation::{ @@ -124,7 +123,10 @@ impl Trigger for SqsRecord { "retry_count".to_string(), self.attributes.approximate_receive_count.clone(), ), - ( "retry_count".to_string(), self.attributes.approximate_receive_count.clone()), + ( + "retry_count".to_string(), + self.attributes.approximate_receive_count.clone(), + ), ("sender_id".to_string(), self.attributes.sender_id.clone()), ("source_arn".to_string(), self.event_source_arn.clone()), ("aws_region".to_string(), self.aws_region.clone()), @@ -133,14 +135,15 @@ impl Trigger for SqsRecord { } fn get_tags(&self) -> HashMap { - let tags = HashMap::from([ - ( "retry_count".to_string(), self.attributes.approximate_receive_count.clone()), + HashMap::from([ + ( + "retry_count".to_string(), + self.attributes.approximate_receive_count.clone(), + ), ("sender_id".to_string(), self.attributes.sender_id.clone()), ("source_arn".to_string(), self.event_source_arn.clone()), ("aws_region".to_string(), self.aws_region.clone()), - ]); - - tags + ]) } fn get_arn(&self, region: &str) -> String { @@ -157,7 +160,7 @@ impl Trigger for SqsRecord { queue_name ) } else { - "".to_string() + String::new() } } @@ -179,8 +182,7 @@ mod tests { fn test_new() { let json = read_json_file("sqs_event.json"); let payload = serde_json::from_str(&json).expect("Failed to deserialize into Value"); - let result = SqsRecord::new(payload) - .expect("Failed to deserialize into Record"); + let result = SqsRecord::new(payload).expect("Failed to deserialize into Record"); let expected = SqsRecord { message_id: "19dd0b57-b21e-4ac1-bd88-01bbb068cb78".to_string(), @@ -197,17 +199,15 @@ mod tests { event_source: "aws:sqs".to_string(), event_source_arn: "arn:aws:sqs:us-east-1:123456789012:MyQueue".to_string(), aws_region: "us-east-1".to_string(), - }; -// assert_eq!(result, expected); -// } + assert_eq!(result, expected); + } #[test] fn test_is_match() { let json = read_json_file("sqs_event.json"); - let payload = - serde_json::from_str(&json).expect("Failed to deserialize SqsRecord"); + let payload = serde_json::from_str(&json).expect("Failed to deserialize SqsRecord"); assert!(SqsRecord::is_match(&payload)); } @@ -215,8 +215,7 @@ mod tests { #[test] fn test_is_not_match() { let json = read_json_file("api_gateway_http_event.json"); - let payload = - serde_json::from_str(&json).expect("Failed to deserialize SqsRecord"); + let payload = serde_json::from_str(&json).expect("Failed to deserialize SqsRecord"); assert!(!SqsRecord::is_match(&payload)); } @@ -224,8 +223,7 @@ mod tests { fn test_enrich_span() { let json = read_json_file("sqs_event.json"); let payload = serde_json::from_str(&json).expect("Failed to deserialize into Value"); - let event = - SqsRecord::new(payload).expect("Failed to deserialize SqsRecord"); + let event = SqsRecord::new(payload).expect("Failed to deserialize SqsRecord"); let mut span = Span::default(); event.enrich_span(&mut span); assert_eq!(span.name, "aws.sqs"); @@ -243,7 +241,10 @@ mod tests { ), ("retry_count".to_string(), 1.to_string()), ("sender_id".to_string(), "123456789012".to_string()), - ("source_arn".to_string(), "arn:aws:sqs:us-east-1:123456789012:MyQueue".to_string()), + ( + "source_arn".to_string(), + "arn:aws:sqs:us-east-1:123456789012:MyQueue".to_string() + ), ("aws_region".to_string(), "us-east-1".to_string()), ("resource_names".to_string(), "MyQueue".to_string()), ]) @@ -254,30 +255,30 @@ mod tests { fn test_get_tags() { let json = read_json_file("sqs_event.json"); let payload = serde_json::from_str(&json).expect("Failed to deserialize into Value"); - let event = - SqsRecord::new(payload).expect("Failed to deserialize SqsRecord"); + let event = SqsRecord::new(payload).expect("Failed to deserialize SqsRecord"); let tags = event.get_tags(); let expected = HashMap::from([ ("retry_count".to_string(), 1.to_string()), ("sender_id".to_string(), "123456789012".to_string()), - ("source_arn".to_string(), "arn:aws:sqs:us-east-1:123456789012:MyQueue".to_string()), + ( + "source_arn".to_string(), + "arn:aws:sqs:us-east-1:123456789012:MyQueue".to_string(), + ), ("aws_region".to_string(), "us-east-1".to_string()), ]); assert_eq!(tags, expected); } - #[test] fn test_get_arn() { let json = read_json_file("api_gateway_rest_event.json"); let payload = serde_json::from_str(&json).expect("Failed to deserialize into Value"); - let event = - APIGatewayRestEvent::new(payload).expect("Failed to deserialize APIGatewayRestEvent"); + let event = SqsRecord::new(payload).expect("Failed to deserialize SqsRecord"); assert_eq!( event.get_arn("us-east-1"), - "arn:aws:apigateway:us-east-1::/restapis/id/stages/$default" + "arn:aws:sqs:us-east-1:123456789012:MyQueue" ); } } From 3ae55f39513f1667b8ff71dcdf50399c3753e871 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?jordan=20gonz=C3=A1lez?= <30836115+duncanista@users.noreply.github.com> Date: Wed, 23 Oct 2024 16:11:19 -0400 Subject: [PATCH 06/31] remove unneeded reference --- bottlecap/src/lifecycle/invocation/span_inferrer.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bottlecap/src/lifecycle/invocation/span_inferrer.rs b/bottlecap/src/lifecycle/invocation/span_inferrer.rs index 5137081f1..c059bba98 100644 --- a/bottlecap/src/lifecycle/invocation/span_inferrer.rs +++ b/bottlecap/src/lifecycle/invocation/span_inferrer.rs @@ -89,7 +89,7 @@ impl SpanInferrer { self.is_async_span = t.is_async(); self.inferred_span = Some(span); } - } else if SqsRecord::is_match(&payload_value) { + } else if SqsRecord::is_match(payload_value) { if let Some(t) = SqsRecord::new(payload_value.clone()) { let mut span = Span { span_id: Self::generate_span_id(), From cc4c16ce9c0263a304e0db3e2f51fc9505196a1b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?jordan=20gonz=C3=A1lez?= <30836115+duncanista@users.noreply.github.com> Date: Wed, 23 Oct 2024 16:11:49 -0400 Subject: [PATCH 07/31] remove unneeded comments --- .../lifecycle/invocation/triggers/api_gateway_http_event.rs | 2 -- .../lifecycle/invocation/triggers/api_gateway_rest_event.rs | 3 --- 2 files changed, 5 deletions(-) diff --git a/bottlecap/src/lifecycle/invocation/triggers/api_gateway_http_event.rs b/bottlecap/src/lifecycle/invocation/triggers/api_gateway_http_event.rs index 932541a00..b73919aee 100644 --- a/bottlecap/src/lifecycle/invocation/triggers/api_gateway_http_event.rs +++ b/bottlecap/src/lifecycle/invocation/triggers/api_gateway_http_event.rs @@ -116,8 +116,6 @@ impl Trigger for APIGatewayHttpEvent { ), ("resource_names".to_string(), resource), ])); - - // todo: update global(? IsAsync if event payload is `Event` } fn get_tags(&self) -> HashMap { diff --git a/bottlecap/src/lifecycle/invocation/triggers/api_gateway_rest_event.rs b/bottlecap/src/lifecycle/invocation/triggers/api_gateway_rest_event.rs index 2ae79c40a..d2588f37a 100644 --- a/bottlecap/src/lifecycle/invocation/triggers/api_gateway_rest_event.rs +++ b/bottlecap/src/lifecycle/invocation/triggers/api_gateway_rest_event.rs @@ -115,9 +115,6 @@ impl Trigger for APIGatewayRestEvent { self.request_context.resource_path.clone(), ), ])); - - debug!("Enriched Span: {:?}", span); - // todo: update global(? IsAsync if event payload is `Event` } fn get_tags(&self) -> HashMap { From 9cf0a2ad6fc163ade7c50480408d8f8d38674047 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?jordan=20gonz=C3=A1lez?= <30836115+duncanista@users.noreply.github.com> Date: Wed, 23 Oct 2024 16:34:00 -0400 Subject: [PATCH 08/31] add `get_carrier` implementation for `SqsRecord` --- .../src/lifecycle/invocation/triggers/mod.rs | 2 ++ .../invocation/triggers/sqs_event.rs | 35 ++++++++++++++++++- 2 files changed, 36 insertions(+), 1 deletion(-) diff --git a/bottlecap/src/lifecycle/invocation/triggers/mod.rs b/bottlecap/src/lifecycle/invocation/triggers/mod.rs index 1114c4f7e..c5b3e8eb1 100644 --- a/bottlecap/src/lifecycle/invocation/triggers/mod.rs +++ b/bottlecap/src/lifecycle/invocation/triggers/mod.rs @@ -8,6 +8,8 @@ pub mod api_gateway_http_event; pub mod api_gateway_rest_event; pub mod sqs_event; +pub const DATADOG_CARRIER_KEY: &str = "_datadog"; + pub trait Trigger: Sized { fn new(payload: Value) -> Option; fn is_match(payload: &Value) -> bool; diff --git a/bottlecap/src/lifecycle/invocation/triggers/sqs_event.rs b/bottlecap/src/lifecycle/invocation/triggers/sqs_event.rs index 8e973436a..0939d410b 100644 --- a/bottlecap/src/lifecycle/invocation/triggers/sqs_event.rs +++ b/bottlecap/src/lifecycle/invocation/triggers/sqs_event.rs @@ -9,6 +9,8 @@ use crate::lifecycle::invocation::{ triggers::{get_aws_partition_by_region, Trigger}, }; +use super::DATADOG_CARRIER_KEY; + #[derive(Debug, Serialize, Deserialize, Clone, PartialEq)] pub struct SqsEvent { #[serde(rename = "Records")] @@ -169,7 +171,16 @@ impl Trigger for SqsRecord { } fn get_carrier(&self) -> HashMap { - todo!() + let mut carrier = HashMap::new(); + if let Some(ma) = self.message_attributes.get(DATADOG_CARRIER_KEY) { + if ma.data_type == "String" { + if let Some(string_value) = &ma.string_value { + carrier = serde_json::from_str(string_value).unwrap_or_default(); + } + } + } + + carrier } } @@ -281,4 +292,26 @@ mod tests { "arn:aws:sqs:us-east-1:123456789012:MyQueue" ); } + + #[test] + fn test_get_carrier() { + let json = read_json_file("sqs_event.json"); + let payload = serde_json::from_str(&json).expect("Failed to deserialize into Value"); + let event = SqsRecord::new(payload).expect("Failed to deserialize SqsRecord"); + let carrier = event.get_carrier(); + + let expected = HashMap::from([ + ( + "x-datadog-trace-id".to_string(), + "2684756524522091840".to_string(), + ), + ( + "x-datadog-parent-id".to_string(), + "7431398482019833808".to_string(), + ), + ("x-datadog-sampling-priority".to_string(), "1".to_string()), + ]); + + assert_eq!(carrier, expected); + } } From e5ed4002f2d3d84d677e61d26e96959f75ae8602 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?jordan=20gonz=C3=A1lez?= <30836115+duncanista@users.noreply.github.com> Date: Wed, 23 Oct 2024 16:34:36 -0400 Subject: [PATCH 09/31] add trace context to `sqs_event.json` --- bottlecap/tests/payloads/sqs_event.json | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/bottlecap/tests/payloads/sqs_event.json b/bottlecap/tests/payloads/sqs_event.json index e7e5eb73b..5cc7837fd 100644 --- a/bottlecap/tests/payloads/sqs_event.json +++ b/bottlecap/tests/payloads/sqs_event.json @@ -10,7 +10,14 @@ "SenderId": "123456789012", "ApproximateFirstReceiveTimestamp": "1523232000001" }, - "messageAttributes": {}, + "messageAttributes": { + "_datadog": { + "stringValue": "{\"x-datadog-trace-id\":\"2684756524522091840\",\"x-datadog-parent-id\":\"7431398482019833808\",\"x-datadog-sampling-priority\":\"1\"}", + "stringListValues": [], + "binaryListValues": [], + "dataType": "String" + } + }, "md5OfBody": "{{{md5_of_body}}}", "eventSource": "aws:sqs", "eventSourceARN": "arn:aws:sqs:us-east-1:123456789012:MyQueue", From dec3bf9c0c1920129d1b9a9f226837253d29f07b Mon Sep 17 00:00:00 2001 From: AJ Stuyvenberg Date: Thu, 24 Oct 2024 09:58:11 -0400 Subject: [PATCH 10/31] fix: resource_names is not needed --- .../lifecycle/invocation/triggers/api_gateway_http_event.rs | 3 --- .../lifecycle/invocation/triggers/api_gateway_rest_event.rs | 3 --- bottlecap/src/lifecycle/invocation/triggers/sqs_event.rs | 6 ++---- 3 files changed, 2 insertions(+), 10 deletions(-) diff --git a/bottlecap/src/lifecycle/invocation/triggers/api_gateway_http_event.rs b/bottlecap/src/lifecycle/invocation/triggers/api_gateway_http_event.rs index b73919aee..cb420f740 100644 --- a/bottlecap/src/lifecycle/invocation/triggers/api_gateway_http_event.rs +++ b/bottlecap/src/lifecycle/invocation/triggers/api_gateway_http_event.rs @@ -114,7 +114,6 @@ impl Trigger for APIGatewayHttpEvent { "request_id".to_string(), self.request_context.request_id.clone(), ), - ("resource_names".to_string(), resource), ])); } @@ -285,7 +284,6 @@ mod tests { ("http.user_agent".to_string(), "curl/7.64.1".to_string()), ("operation_name".to_string(), "aws.httpapi".to_string()), ("request_id".to_string(), "FaHnXjKCGjQEJ7A=".to_string()), - ("resource_names".to_string(), "GET /httpapi/get".to_string()), ]) ); } @@ -343,7 +341,6 @@ mod tests { ("http.user_agent".to_string(), "curl/8.1.2".to_string()), ("operation_name".to_string(), "aws.httpapi".to_string()), ("request_id".to_string(), "Ur2JtjEfGjQEPOg=".to_string()), - ("resource_names".to_string(), "GET /user/{id}".to_string()), ]) ); } diff --git a/bottlecap/src/lifecycle/invocation/triggers/api_gateway_rest_event.rs b/bottlecap/src/lifecycle/invocation/triggers/api_gateway_rest_event.rs index d2588f37a..1734f9212 100644 --- a/bottlecap/src/lifecycle/invocation/triggers/api_gateway_rest_event.rs +++ b/bottlecap/src/lifecycle/invocation/triggers/api_gateway_rest_event.rs @@ -109,7 +109,6 @@ impl Trigger for APIGatewayRestEvent { "request_id".to_string(), self.request_context.request_id.clone(), ), - ("resource_names".to_string(), resource.clone()), ( "http.route".to_string(), self.request_context.resource_path.clone(), @@ -256,7 +255,6 @@ mod tests { ("http.route".to_string(), "/path".to_string()), ("operation_name".to_string(), "aws.apigateway".to_string()), ("request_id".to_string(), "id=".to_string()), - ("resource_names".to_string(), "GET /path".to_string()), ]) ); } @@ -314,7 +312,6 @@ mod tests { "request_id".to_string(), "e16399f7-e984-463a-9931-745ba021a27f".to_string(), ), - ("resource_names".to_string(), "GET /user/{id}".to_string()), ]); assert_eq!(span.meta, expected); } diff --git a/bottlecap/src/lifecycle/invocation/triggers/sqs_event.rs b/bottlecap/src/lifecycle/invocation/triggers/sqs_event.rs index 0939d410b..b3018d5e7 100644 --- a/bottlecap/src/lifecycle/invocation/triggers/sqs_event.rs +++ b/bottlecap/src/lifecycle/invocation/triggers/sqs_event.rs @@ -132,7 +132,6 @@ impl Trigger for SqsRecord { ("sender_id".to_string(), self.attributes.sender_id.clone()), ("source_arn".to_string(), self.event_source_arn.clone()), ("aws_region".to_string(), self.aws_region.clone()), - ("resource_names".to_string(), resource.clone()), ])); } @@ -171,11 +170,11 @@ impl Trigger for SqsRecord { } fn get_carrier(&self) -> HashMap { - let mut carrier = HashMap::new(); + let carrier = HashMap::new(); if let Some(ma) = self.message_attributes.get(DATADOG_CARRIER_KEY) { if ma.data_type == "String" { if let Some(string_value) = &ma.string_value { - carrier = serde_json::from_str(string_value).unwrap_or_default(); + return serde_json::from_str(string_value).unwrap_or_default(); } } } @@ -257,7 +256,6 @@ mod tests { "arn:aws:sqs:us-east-1:123456789012:MyQueue".to_string() ), ("aws_region".to_string(), "us-east-1".to_string()), - ("resource_names".to_string(), "MyQueue".to_string()), ]) ); } From 7b99cd2075b1538618c2ecc3f8fb3daa0f7dd49c Mon Sep 17 00:00:00 2001 From: AJ Stuyvenberg Date: Thu, 24 Oct 2024 13:23:12 -0400 Subject: [PATCH 11/31] fix: don't deserialize body --- bottlecap/src/lifecycle/invocation/triggers/sqs_event.rs | 2 -- 1 file changed, 2 deletions(-) diff --git a/bottlecap/src/lifecycle/invocation/triggers/sqs_event.rs b/bottlecap/src/lifecycle/invocation/triggers/sqs_event.rs index b3018d5e7..a4fd71440 100644 --- a/bottlecap/src/lifecycle/invocation/triggers/sqs_event.rs +++ b/bottlecap/src/lifecycle/invocation/triggers/sqs_event.rs @@ -23,7 +23,6 @@ pub struct SqsRecord { pub message_id: String, #[serde(rename = "receiptHandle")] pub receipt_handle: String, - pub body: String, pub attributes: Attributes, #[serde(rename = "messageAttributes")] pub message_attributes: HashMap, @@ -197,7 +196,6 @@ mod tests { let expected = SqsRecord { message_id: "19dd0b57-b21e-4ac1-bd88-01bbb068cb78".to_string(), receipt_handle: "MessageReceiptHandle".to_string(), - body: "Hello from SQS!".to_string(), attributes: Attributes { approximate_first_receive_timestamp: "1523232000001".to_string(), approximate_receive_count: "1".to_string(), From fd2864ebb5ecaa0c19ea2c6e3fc369bf93a0028d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?jordan=20gonz=C3=A1lez?= <30836115+duncanista@users.noreply.github.com> Date: Tue, 5 Nov 2024 13:03:25 -0500 Subject: [PATCH 12/31] avoid `use super::...` --- bottlecap/src/lifecycle/invocation/triggers/sqs_event.rs | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/bottlecap/src/lifecycle/invocation/triggers/sqs_event.rs b/bottlecap/src/lifecycle/invocation/triggers/sqs_event.rs index 0939d410b..7d80648f5 100644 --- a/bottlecap/src/lifecycle/invocation/triggers/sqs_event.rs +++ b/bottlecap/src/lifecycle/invocation/triggers/sqs_event.rs @@ -6,11 +6,9 @@ use tracing::debug; use crate::lifecycle::invocation::{ processor::MS_TO_NS, - triggers::{get_aws_partition_by_region, Trigger}, + triggers::{get_aws_partition_by_region, Trigger, DATADOG_CARRIER_KEY}, }; -use super::DATADOG_CARRIER_KEY; - #[derive(Debug, Serialize, Deserialize, Clone, PartialEq)] pub struct SqsEvent { #[serde(rename = "Records")] From b4f1dffcebfa65baacf326c58832aa2ca1a57476 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?jordan=20gonz=C3=A1lez?= <30836115+duncanista@users.noreply.github.com> Date: Tue, 5 Nov 2024 15:02:42 -0500 Subject: [PATCH 13/31] fix unit tests --- .../invocation/triggers/sqs_event.rs | 25 +++++++++++++------ 1 file changed, 18 insertions(+), 7 deletions(-) diff --git a/bottlecap/src/lifecycle/invocation/triggers/sqs_event.rs b/bottlecap/src/lifecycle/invocation/triggers/sqs_event.rs index 079d399b7..5b335aa57 100644 --- a/bottlecap/src/lifecycle/invocation/triggers/sqs_event.rs +++ b/bottlecap/src/lifecycle/invocation/triggers/sqs_event.rs @@ -152,8 +152,9 @@ impl Trigger for SqsRecord { .as_slice() { format!( - "arn:aws:sqs:{}:{}:{}", + "arn:{}:sqs:{}:{}:{}", get_aws_partition_by_region(region), + region, account, queue_name ) @@ -169,13 +170,13 @@ impl Trigger for SqsRecord { fn get_carrier(&self) -> HashMap { let carrier = HashMap::new(); if let Some(ma) = self.message_attributes.get(DATADOG_CARRIER_KEY) { - if ma.data_type == "String" { - if let Some(string_value) = &ma.string_value { - return serde_json::from_str(string_value).unwrap_or_default(); - } + if let Some(string_value) = &ma.string_value { + return serde_json::from_str(string_value).unwrap_or_default(); } } + // TODO: AWSTraceHeader + carrier } } @@ -191,6 +192,16 @@ mod tests { let payload = serde_json::from_str(&json).expect("Failed to deserialize into Value"); let result = SqsRecord::new(payload).expect("Failed to deserialize into Record"); + let message_attributes = HashMap::::from([ + ("_datadog".to_string(), MessageAttribute { + string_value: Some("{\"x-datadog-trace-id\":\"2684756524522091840\",\"x-datadog-parent-id\":\"7431398482019833808\",\"x-datadog-sampling-priority\":\"1\"}".to_string()), + binary_value: None, + string_list_values: Some(vec![]), + binary_list_values: Some(vec![]), + data_type: "String".to_string(), + }) + ]); + let expected = SqsRecord { message_id: "19dd0b57-b21e-4ac1-bd88-01bbb068cb78".to_string(), receipt_handle: "MessageReceiptHandle".to_string(), @@ -200,7 +211,7 @@ mod tests { sent_timestamp: "1523232000000".to_string(), sender_id: "123456789012".to_string(), }, - message_attributes: HashMap::from([]), + message_attributes, md5_of_body: "{{{md5_of_body}}}".to_string(), event_source: "aws:sqs".to_string(), event_source_arn: "arn:aws:sqs:us-east-1:123456789012:MyQueue".to_string(), @@ -278,7 +289,7 @@ mod tests { #[test] fn test_get_arn() { - let json = read_json_file("api_gateway_rest_event.json"); + let json = read_json_file("sqs_event.json"); let payload = serde_json::from_str(&json).expect("Failed to deserialize into Value"); let event = SqsRecord::new(payload).expect("Failed to deserialize SqsRecord"); assert_eq!( From da6c083d6acb63dd08b4393a3a71ff87f847f780 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?jordan=20gonz=C3=A1lez?= <30836115+duncanista@users.noreply.github.com> Date: Tue, 5 Nov 2024 15:02:59 -0500 Subject: [PATCH 14/31] set carrier and trigger tags --- bottlecap/src/lifecycle/invocation/span_inferrer.rs | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/bottlecap/src/lifecycle/invocation/span_inferrer.rs b/bottlecap/src/lifecycle/invocation/span_inferrer.rs index a2b7d162a..9d41e81ee 100644 --- a/bottlecap/src/lifecycle/invocation/span_inferrer.rs +++ b/bottlecap/src/lifecycle/invocation/span_inferrer.rs @@ -112,7 +112,9 @@ impl SpanInferrer { t.get_arn(&aws_config.region), ), ]); - debug!("SQS span is {:?}", span); + + self.carrier = Some(t.get_carrier()); + self.trigger_tags = Some(t.get_tags()); self.is_async_span = t.is_async(); self.inferred_span = Some(span); } From 29eaaa5848a9b6dc75ba42b9e6d8480912fbb716 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?jordan=20gonz=C3=A1lez?= <30836115+duncanista@users.noreply.github.com> Date: Tue, 5 Nov 2024 21:46:53 -0500 Subject: [PATCH 15/31] remove duplicate tag --- bottlecap/src/lifecycle/invocation/triggers/sqs_event.rs | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/bottlecap/src/lifecycle/invocation/triggers/sqs_event.rs b/bottlecap/src/lifecycle/invocation/triggers/sqs_event.rs index 5b335aa57..e7ad776b0 100644 --- a/bottlecap/src/lifecycle/invocation/triggers/sqs_event.rs +++ b/bottlecap/src/lifecycle/invocation/triggers/sqs_event.rs @@ -104,7 +104,7 @@ impl Trigger for SqsRecord { let start_time = (self .attributes .sent_timestamp - .parse::() + .parse::() .unwrap_or_default() as f64 * MS_TO_NS) as i64; // todo: service mapping @@ -122,10 +122,6 @@ impl Trigger for SqsRecord { "retry_count".to_string(), self.attributes.approximate_receive_count.clone(), ), - ( - "retry_count".to_string(), - self.attributes.approximate_receive_count.clone(), - ), ("sender_id".to_string(), self.attributes.sender_id.clone()), ("source_arn".to_string(), self.event_source_arn.clone()), ("aws_region".to_string(), self.aws_region.clone()), From 5085bf4df7b5b0bef3bd82fa782d4eb524106447 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?jordan=20gonz=C3=A1lez?= <30836115+duncanista@users.noreply.github.com> Date: Tue, 5 Nov 2024 21:47:58 -0500 Subject: [PATCH 16/31] fmt --- bottlecap/src/lifecycle/invocation/span_inferrer.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bottlecap/src/lifecycle/invocation/span_inferrer.rs b/bottlecap/src/lifecycle/invocation/span_inferrer.rs index 9d41e81ee..f778572b7 100644 --- a/bottlecap/src/lifecycle/invocation/span_inferrer.rs +++ b/bottlecap/src/lifecycle/invocation/span_inferrer.rs @@ -112,7 +112,7 @@ impl SpanInferrer { t.get_arn(&aws_config.region), ), ]); - + self.carrier = Some(t.get_carrier()); self.trigger_tags = Some(t.get_tags()); self.is_async_span = t.is_async(); From f5fe9abdaa6c155d99b79cd2ac41bdadf7289418 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?jordan=20gonz=C3=A1lez?= <30836115+duncanista@users.noreply.github.com> Date: Wed, 6 Nov 2024 16:03:33 -0500 Subject: [PATCH 17/31] pass headers to `on_invocation_end` --- bottlecap/src/lifecycle/listener.rs | 27 ++------------------------- 1 file changed, 2 insertions(+), 25 deletions(-) diff --git a/bottlecap/src/lifecycle/listener.rs b/bottlecap/src/lifecycle/listener.rs index a4d39310b..b255ec491 100644 --- a/bottlecap/src/lifecycle/listener.rs +++ b/bottlecap/src/lifecycle/listener.rs @@ -148,34 +148,11 @@ impl Listener { if let Some(status_code) = parsed_body.unwrap_or_default().get("statusCode") { parsed_status = Some(status_code.to_string()); } - let headers = parts.headers; let mut processor = invocation_processor.lock().await; - // todo: fix this, code is a copy of the existing logic in Go, not accounting - // when a 128 bit trace id exist - let mut trace_id = 0; - if let Some(header) = headers.get("x-datadog-trace-id") { - if let Ok(header_value) = header.to_str() { - trace_id = header_value.parse::().unwrap_or(0); - } - } - - let mut span_id = 0; - if let Some(header) = headers.get("x-datadog-span-id") { - if let Ok(header_value) = header.to_str() { - span_id = header_value.parse::().unwrap_or(0); - } - } - - let mut parent_id = 0; - if let Some(header) = headers.get("x-datadog-parent-id") { - if let Ok(header_value) = header.to_str() { - parent_id = header_value.parse::().unwrap_or(0); - } - } - - processor.on_invocation_end(trace_id, span_id, parent_id, parsed_status); + let headers = Self::headers_to_map(parts.headers); + processor.on_invocation_end(headers, parsed_status); drop(processor); Response::builder() From 0cf4dbe84890ae02831bd312e4e426c9dd59ecc1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?jordan=20gonz=C3=A1lez?= <30836115+duncanista@users.noreply.github.com> Date: Wed, 6 Nov 2024 16:04:58 -0500 Subject: [PATCH 18/31] infer first, then extract or else theres nothing to extract, reset values also for next inferr, no need to keep state after we complete --- .../src/lifecycle/invocation/processor.rs | 44 +++++++++++++++---- 1 file changed, 36 insertions(+), 8 deletions(-) diff --git a/bottlecap/src/lifecycle/invocation/processor.rs b/bottlecap/src/lifecycle/invocation/processor.rs index 82a06724b..9b6afdae6 100644 --- a/bottlecap/src/lifecycle/invocation/processor.rs +++ b/bottlecap/src/lifecycle/invocation/processor.rs @@ -205,14 +205,15 @@ impl Processor { self.span.trace_id = 0; self.span.parent_id = 0; self.span.span_id = 0; + self.extracted_span_context = None; let payload_value = match serde_json::from_slice::(&payload) { Ok(value) => value, Err(_) => json!({}), }; - self.extracted_span_context = self.extract_span_context(&headers, &payload_value); self.inferrer.infer_span(&payload_value, &self.aws_config); + self.extracted_span_context = self.extract_span_context(&headers, &payload_value); if let Some(sc) = &self.extracted_span_context { self.span.trace_id = sc.trace_id; @@ -264,19 +265,46 @@ impl Processor { /// pub fn on_invocation_end( &mut self, - trace_id: u64, - span_id: u64, - parent_id: u64, + headers: HashMap, status_code: Option, ) { - self.span.trace_id = trace_id; - self.span.span_id = span_id; - + self.update_span_context(headers); if self.inferrer.inferred_span.is_some() { if let Some(status_code) = status_code { self.inferrer.set_status_code(status_code); } - } else { + } + } + + fn update_span_context(&mut self, headers: HashMap) { + // todo: fix this, code is a copy of the existing logic in Go, not accounting + // when a 128 bit trace id exist + let mut trace_id = 0; + let mut span_id = 0; + let mut parent_id = 0; + + // If we have a trace context, update the span context + if let Some(sc) = &mut self.extracted_span_context { + trace_id = sc.trace_id; + span_id = sc.span_id; + } + + if let Some(header) = headers.get("x-datadog-trace-id") { + trace_id = header.parse::().unwrap_or(0); + } + + if let Some(header) = headers.get("x-datadog-span-id") { + span_id = header.parse::().unwrap_or(0); + } + + if let Some(header) = headers.get("x-datadog-parent-id") { + parent_id = header.parse::().unwrap_or(0); + } + + self.span.trace_id = trace_id; + self.span.span_id = span_id; + + if self.inferrer.inferred_span.is_none() { self.span.parent_id = parent_id; } } From b13637324e78b0cce27bccfafee9f196d5910970 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?jordan=20gonz=C3=A1lez?= <30836115+duncanista@users.noreply.github.com> Date: Wed, 6 Nov 2024 16:05:08 -0500 Subject: [PATCH 19/31] reset values on every infer --- bottlecap/src/lifecycle/invocation/span_inferrer.rs | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/bottlecap/src/lifecycle/invocation/span_inferrer.rs b/bottlecap/src/lifecycle/invocation/span_inferrer.rs index f778572b7..ecd8d24dc 100644 --- a/bottlecap/src/lifecycle/invocation/span_inferrer.rs +++ b/bottlecap/src/lifecycle/invocation/span_inferrer.rs @@ -46,6 +46,10 @@ impl SpanInferrer { /// pub fn infer_span(&mut self, payload_value: &Value, aws_config: &AwsConfig) { self.inferred_span = None; + self.is_async_span = false; + self.carrier = None; + self.trigger_tags = None; + if APIGatewayHttpEvent::is_match(payload_value) { if let Some(t) = APIGatewayHttpEvent::new(payload_value.clone()) { let mut span = Span { From b9edb96717a486d9fd9f85d8586f54503f958e6c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?jordan=20gonz=C3=A1lez?= <30836115+duncanista@users.noreply.github.com> Date: Thu, 7 Nov 2024 13:25:32 -0500 Subject: [PATCH 20/31] add `sns_event.rs` --- .../invocation/triggers/sns_event.rs | 308 ++++++++++++++++++ 1 file changed, 308 insertions(+) create mode 100644 bottlecap/src/lifecycle/invocation/triggers/sns_event.rs diff --git a/bottlecap/src/lifecycle/invocation/triggers/sns_event.rs b/bottlecap/src/lifecycle/invocation/triggers/sns_event.rs new file mode 100644 index 000000000..6f02b85df --- /dev/null +++ b/bottlecap/src/lifecycle/invocation/triggers/sns_event.rs @@ -0,0 +1,308 @@ +use std::collections::HashMap; + +use chrono::{DateTime, Utc}; +use serde::{Deserialize, Serialize}; +use serde_json::Value; +use tracing::debug; + +use crate::lifecycle::invocation::{ + processor::MS_TO_NS, + triggers::{base64_to_string, Trigger, DATADOG_CARRIER_KEY}, +}; + +use super::{FUNCTION_TRIGGER_EVENT_SOURCE_ARN_TAG, FUNCTION_TRIGGER_EVENT_SOURCE_TAG}; + +#[derive(Debug, Serialize, Deserialize, Clone, PartialEq)] +pub struct SnsEvent { + #[serde(rename = "Records")] + pub records: Vec, +} + +#[derive(Debug, Serialize, Deserialize, Clone, PartialEq)] +pub struct SnsRecord { + #[serde(rename = "Sns")] + pub sns: SnsEntity, + #[serde(rename = "EventSubscriptionArn")] + pub event_subscription_arn: Option, +} + +#[derive(Debug, Serialize, Deserialize, Clone, PartialEq)] +pub struct SnsEntity { + #[serde(rename = "MessageId")] + pub message_id: String, + #[serde(rename = "Type")] + pub r#type: String, + #[serde(rename = "TopicArn")] + pub topic_arn: String, + #[serde(rename = "MessageAttributes")] + pub message_attributes: HashMap, + #[serde(rename = "Timestamp")] + pub timestamp: DateTime, + #[serde(rename = "Subject")] + pub subject: Option, +} + +#[derive(Debug, Serialize, Deserialize, Clone, PartialEq)] +pub struct MessageAttribute { + #[serde(rename = "Type")] + pub r#type: String, + #[serde(rename = "Value")] + pub value: String, +} + +impl Trigger for SnsRecord { + fn new(payload: serde_json::Value) -> Option { + match payload.get("Records").and_then(Value::as_array) { + Some(records) => match serde_json::from_value::(records[0].clone()) { + Ok(record) => Some(record), + Err(e) => { + debug!("Failed to deserialize SNS Record: {e}"); + None + } + }, + None => None, + } + } + + fn is_match(payload: &serde_json::Value) -> bool { + if let Some(first_record) = payload + .get("Records") + .and_then(Value::as_array) + .and_then(|r| r.first()) + .take() + { + return first_record.get("Sns").is_some(); + } + + false + } + + #[allow(clippy::cast_possible_truncation)] + fn enrich_span(&self, span: &mut datadog_trace_protobuf::pb::Span) { + debug!("Enriching an Inferred Span for an SNS Event"); + let resource = self + .sns + .topic_arn + .clone() + .split(':') + .last() + .unwrap_or_default() + .to_string(); + + let start_time = self + .sns + .timestamp + .timestamp_nanos_opt() + .unwrap_or((self.sns.timestamp.timestamp_millis() as f64 * MS_TO_NS) as i64); + // todo: service mapping + let service_name = "sns".to_string(); + + span.name = "aws.sns".to_string(); + span.service = service_name.to_string(); + span.resource.clone_from(&resource); + span.r#type = "web".to_string(); + span.start = start_time; + span.meta.extend([ + ("operation_name".to_string(), "aws.sqs".to_string()), + ("topicname".to_string(), resource), + ("topic_arn".to_string(), self.sns.topic_arn.clone()), + ("message_id".to_string(), self.sns.message_id.clone()), + ("type".to_string(), self.sns.r#type.clone()), + ]); + + if let Some(subject) = &self.sns.subject { + span.meta.insert("subject".to_string(), subject.clone()); + } + + if let Some(event_subscription_arn) = &self.event_subscription_arn { + span.meta.insert( + "event_subscription_arn".to_string(), + event_subscription_arn.clone(), + ); + } + } + + fn get_tags(&self) -> HashMap { + HashMap::from([ + ( + FUNCTION_TRIGGER_EVENT_SOURCE_TAG.to_string(), + "sns".to_string(), + ), + ( + FUNCTION_TRIGGER_EVENT_SOURCE_ARN_TAG.to_string(), + self.sns.topic_arn.clone(), + ), + ]) + } + + fn get_arn(&self, _region: &str) -> String { + self.sns.topic_arn.clone() + } + + fn get_carrier(&self) -> HashMap { + let carrier = HashMap::new(); + if let Some(ma) = self.sns.message_attributes.get(DATADOG_CARRIER_KEY) { + match ma.r#type.as_str() { + "String" => return serde_json::from_str(&ma.value).unwrap_or_default(), + "Binary" => { + return serde_json::from_str(&base64_to_string(&ma.value)).unwrap_or_default() + } + _ => { + debug!("Unsupported type in SNS message attribute"); + } + } + } + + carrier + } + + fn is_async(&self) -> bool { + true + } +} + +#[cfg(test)] +mod tests { + use datadog_trace_protobuf::pb::Span; + + use super::*; + use crate::lifecycle::invocation::triggers::test_utils::read_json_file; + + #[test] + fn test_new() { + let json = read_json_file("sns_event.json"); + let payload = serde_json::from_str(&json).expect("Failed to deserialize into Value"); + let result = SnsRecord::new(payload).expect("Failed to deserialize into SnsRecord"); + + let message_attributes = HashMap::::from([ + ("_datadog".to_string(), MessageAttribute { + r#type: "String".to_string(), + value: "{\"x-datadog-trace-id\": \"4948377316357291421\", \"x-datadog-parent-id\": \"6746998015037429512\", \"x-datadog-sampling-priority\": \"1\"}".to_string(), + }) + ]); + + let expected = SnsRecord { + event_subscription_arn: Some("arn:aws:sns:sa-east-1:425362996713:serverlessTracingTopicPy:224b60ba-befc-4830-ad96-f1f0ac94eb04".to_string()), + sns: SnsEntity { + message_id: "87056a47-f506-5d77-908b-303605d3b197".to_string(), + r#type: "Notification".to_string(), + topic_arn: "arn:aws:sns:sa-east-1:425362996713:serverlessTracingTopicPy" + .to_string(), + message_attributes, + timestamp: DateTime::parse_from_rfc3339("2022-01-31T14:13:41.637Z") + .unwrap() + .with_timezone(&Utc), + subject: None, + }, + }; + + assert_eq!(result, expected); + } + + #[test] + fn test_is_match() { + let json = read_json_file("sns_event.json"); + let payload = serde_json::from_str(&json).expect("Failed to deserialize SnsRecord"); + + assert!(SnsRecord::is_match(&payload)); + } + + #[test] + fn test_is_not_match() { + let json = read_json_file("sqs_event.json"); + let payload = serde_json::from_str(&json).expect("Failed to deserialize SqsRecord"); + assert!(!SnsRecord::is_match(&payload)); + } + + #[test] + fn test_enrich_span() { + let json = read_json_file("sns_event.json"); + let payload = serde_json::from_str(&json).expect("Failed to deserialize into Value"); + let event = SnsRecord::new(payload).expect("Failed to deserialize SnsRecord"); + let mut span = Span::default(); + event.enrich_span(&mut span); + assert_eq!(span.name, "aws.sns"); + assert_eq!(span.service, "sns"); + assert_eq!(span.resource, "serverlessTracingTopicPy"); + assert_eq!(span.r#type, "web"); + + assert_eq!( + span.meta, + HashMap::from([ + ("operation_name".to_string(), "aws.sns".to_string()), + ("topicname".to_string(), "serverlessTracingTopicPy".to_string()), + ("topic_arn".to_string(), "arn:aws:sns:sa-east-1:425362996713:serverlessTracingTopicPy".to_string()), + ("message_id".to_string(), "87056a47-f506-5d77-908b-303605d3b197".to_string()), + ("type".to_string(), "Notification".to_string()), + ("event_subscription_arn".to_string(), "arn:aws:sns:sa-east-1:425362996713:serverlessTracingTopicPy:224b60ba-befc-4830-ad96-f1f0ac94eb04".to_string()) + ]) + ); + } + + #[test] + fn test_get_tags() { + let json = read_json_file("sns_event.json"); + let payload = serde_json::from_str(&json).expect("Failed to deserialize into Value"); + let event = SnsRecord::new(payload).expect("Failed to deserialize SnsRecord"); + let tags = event.get_tags(); + + let expected = HashMap::from([]); + + assert_eq!(tags, expected); + } + + #[test] + fn test_get_arn() { + let json = read_json_file("sns_event.json"); + let payload = serde_json::from_str(&json).expect("Failed to deserialize into Value"); + let event = SnsRecord::new(payload).expect("Failed to deserialize SnsRecord"); + assert_eq!( + event.get_arn("us-east-1"), + "arn:aws:sns:sa-east-1:425362996713:serverlessTracingTopicPy" + ); + } + + #[test] + fn test_get_carrier() { + let json = read_json_file("sns_event.json"); + let payload = serde_json::from_str(&json).expect("Failed to deserialize into Value"); + let event = SnsRecord::new(payload).expect("Failed to deserialize SnsRecord"); + let carrier = event.get_carrier(); + + let expected = HashMap::from([ + ( + "x-datadog-trace-id".to_string(), + "4948377316357291421".to_string(), + ), + ( + "x-datadog-parent-id".to_string(), + "6746998015037429512".to_string(), + ), + ("x-datadog-sampling-priority".to_string(), "1".to_string()), + ]); + + assert_eq!(carrier, expected); + } + + #[test] + fn test_get_carrier_from_binary_value() { + let json = read_json_file("sns_event_binary.json"); + let payload = serde_json::from_str(&json).expect("Failed to deserialize into Value"); + let event = SnsRecord::new(payload).expect("Failed to deserialize SnsRecord"); + let carrier = event.get_carrier(); + + let expected = HashMap::from([ + ( + "x-datadog-trace-id".to_string(), + "4948377316357291421".to_string(), + ), + ( + "x-datadog-parent-id".to_string(), + "6746998015037429512".to_string(), + ), + ("x-datadog-sampling-priority".to_string(), "1".to_string()), + ]); + + assert_eq!(carrier, expected); + } +} From 8232e6a165608fb71ad1d8f5638f35f7e1f63d97 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?jordan=20gonz=C3=A1lez?= <30836115+duncanista@users.noreply.github.com> Date: Thu, 7 Nov 2024 13:25:50 -0500 Subject: [PATCH 21/31] add `sns_event*.json` payloads --- bottlecap/tests/payloads/sns_event.json | 50 +++++++++++++++++++ .../tests/payloads/sns_event_binary.json | 27 ++++++++++ 2 files changed, 77 insertions(+) create mode 100644 bottlecap/tests/payloads/sns_event.json create mode 100644 bottlecap/tests/payloads/sns_event_binary.json diff --git a/bottlecap/tests/payloads/sns_event.json b/bottlecap/tests/payloads/sns_event.json new file mode 100644 index 000000000..ef8062a0e --- /dev/null +++ b/bottlecap/tests/payloads/sns_event.json @@ -0,0 +1,50 @@ +{ + "Records": [ + { + "EventSource": "aws:sns", + "EventVersion": "1.0", + "EventSubscriptionArn": "arn:aws:sns:sa-east-1:425362996713:serverlessTracingTopicPy:224b60ba-befc-4830-ad96-f1f0ac94eb04", + "Sns": { + "Type": "Notification", + "MessageId": "87056a47-f506-5d77-908b-303605d3b197", + "TopicArn": "arn:aws:sns:sa-east-1:425362996713:serverlessTracingTopicPy", + "Subject": null, + "Message": "Asynchronously invoking a Lambda function with SNS.", + "Timestamp": "2022-01-31T14:13:41.637Z", + "SignatureVersion": "1", + "Signature": "BmwnJb0Ku2KgQef9QOgaSSTwLyUsbkRq90lzD5Vn4mAcRUOq2ForfMOYbxMB6idljWIWy9t/jK4AIMxPGk/eOGiRcENx3BvAcGcoDayBRFY13+xUGaPn5Lfoht/ZJ7/hmCgFWKRa8ooATZL+AwGAw6Id8qzf0R3M3k2asy5Vxa4ODKiFW9OzWY/zFgsYJhddR3JrQl9YOMRyIobNNHT96o1TwjGsSUTEemrxA6jQtb3QbardEKO+2SuataLEZki7gE2D2sA300WqZecumI339q7la+OIj6VDGDwFoppE2sh8hzJYXAH7oo11giwltE0V3/eLFCVhsE8Y1KD/yDPPsA==", + "SigningCertUrl": "https://sns.sa-east-1.amazonaws.com/SimpleNotificationService-7ff5318490ec183fbaddaa2a969abfda.pem", + "UnsubscribeUrl": "https://sns.sa-east-1.amazonaws.com/?Action=Unsubscribe&SubscriptionArn=arn:aws:sns:sa-east-1:425362996713:serverlessTracingTopicPy:224b60ba-befc-4830-ad96-f1f0ac94eb04", + "MessageAttributes": { + "_datadog": { + "Type": "String", + "Value": "{\"x-datadog-trace-id\": \"4948377316357291421\", \"x-datadog-parent-id\": \"6746998015037429512\", \"x-datadog-sampling-priority\": \"1\"}" + } + } + } + }, + { + "EventSource": "aws:sns", + "EventVersion": "1.0", + "EventSubscriptionArn": "arn:aws:sns:sa-east-1:425362996713:serverlessTracingTopicPy:224b60ba-befc-4830-ad96-f1f0ac94eb04", + "Sns": { + "Type": "Notification", + "MessageId": "87056a47-f506-5d77-908b-303605d3b197", + "TopicArn": "arn:aws:sns:sa-east-1:425362996713:serverlessTracingTopicPy", + "Subject": null, + "Message": "Asynchronously invoking a Lambda function with SNS.", + "Timestamp": "2022-01-31T14:13:41.637Z", + "SignatureVersion": "1", + "Signature": "BmwnJb0Ku2KgQef9QOgaSSTwLyUsbkRq90lzD5Vn4mAcRUOq2ForfMOYbxMB6idljWIWy9t/jK4AIMxPGk/eOGiRcENx3BvAcGcoDayBRFY13+xUGaPn5Lfoht/ZJ7/hmCgFWKRa8ooATZL+AwGAw6Id8qzf0R3M3k2asy5Vxa4ODKiFW9OzWY/zFgsYJhddR3JrQl9YOMRyIobNNHT96o1TwjGsSUTEemrxA6jQtb3QbardEKO+2SuataLEZki7gE2D2sA300WqZecumI339q7la+OIj6VDGDwFoppE2sh8hzJYXAH7oo11giwltE0V3/eLFCVhsE8Y1KD/yDPPsA==", + "SigningCertUrl": "https://sns.sa-east-1.amazonaws.com/SimpleNotificationService-7ff5318490ec183fbaddaa2a969abfda.pem", + "UnsubscribeUrl": "https://sns.sa-east-1.amazonaws.com/?Action=Unsubscribe&SubscriptionArn=arn:aws:sns:sa-east-1:425362996713:serverlessTracingTopicPy:224b60ba-befc-4830-ad96-f1f0ac94eb04", + "MessageAttributes": { + "_datadog": { + "Type": "String", + "Value": "{\"x-datadog-trace-id\": \"4948377316357291421\", \"x-datadog-parent-id\": \"6746998015037429512\", \"x-datadog-sampling-priority\": \"1\"}" + } + } + } + } + ] +} diff --git a/bottlecap/tests/payloads/sns_event_binary.json b/bottlecap/tests/payloads/sns_event_binary.json new file mode 100644 index 000000000..4a9a2b500 --- /dev/null +++ b/bottlecap/tests/payloads/sns_event_binary.json @@ -0,0 +1,27 @@ +{ + "Records": [ + { + "EventSource": "aws:sns", + "EventVersion": "1.0", + "EventSubscriptionArn": "arn:aws:sns:eu-west-1:601427279990:serverlessTracingTopicPy:224b60ba-befc-4830-ad96-f1f0ac94eb04", + "Sns": { + "Type": "Notification", + "MessageId": "87056a47-f506-5d77-908b-303605d3b197", + "TopicArn": "arn:aws:sns:eu-west-1:601427279990:serverlessTracingTopicPy", + "Subject": null, + "Message": "Asynchronously invoking a Lambda function with SNS.", + "Timestamp": "2022-01-31T14:13:41.637Z", + "SignatureVersion": "1", + "Signature": "BmwnJb0Ku2KgQef9QOgaSSTwLyUsbkRq90lzD5Vn4mAcRUOq2ForfMOYbxMB6idljWIWy9t/jK4AIMxPGk/eOGiRcENx3BvAcGcoDayBRFY13+xUGaPn5Lfoht/ZJ7/hmCgFWKRa8ooATZL+AwGAw6Id8qzf0R3M3k2asy5Vxa4ODKiFW9OzWY/zFgsYJhddR3JrQl9YOMRyIobNNHT96o1TwjGsSUTEemrxA6jQtb3QbardEKO+2SuataLEZki7gE2D2sA300WqZecumI339q7la+OIj6VDGDwFoppE2sh8hzJYXAH7oo11giwltE0V3/eLFCVhsE8Y1KD/yDPPsA==", + "SigningCertUrl": "https://sns.eu-west-1.amazonaws.com/SimpleNotificationService-7ff5318490ec183fbaddaa2a969abfda.pem", + "UnsubscribeUrl": "https://sns.eu-west-1.amazonaws.com/?Action=Unsubscribe&SubscriptionArn=arn:aws:sns:eu-west-1:601427279990:serverlessTracingTopicPy:224b60ba-befc-4830-ad96-f1f0ac94eb04", + "MessageAttributes": { + "_datadog": { + "Type": "Binary", + "Value": "eyJ4LWRhdGFkb2ctdHJhY2UtaWQiOiI0OTQ4Mzc3MzE2MzU3MjkxNDIxIiwieC1kYXRhZG9nLXBhcmVudC1pZCI6IjY3NDY5OTgwMTUwMzc0Mjk1MTIiLCJ4LWRhdGFkb2ctc2FtcGxpbmctcHJpb3JpdHkiOiIxIn0=" + } + } + } + } + ] +} From 1daeb7519c31d26d115a1931f10572ead4864c81 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?jordan=20gonz=C3=A1lez?= <30836115+duncanista@users.noreply.github.com> Date: Thu, 7 Nov 2024 13:26:13 -0500 Subject: [PATCH 22/31] add `base64_to_string` method and also move some variables --- bottlecap/src/lifecycle/invocation/triggers/mod.rs | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/bottlecap/src/lifecycle/invocation/triggers/mod.rs b/bottlecap/src/lifecycle/invocation/triggers/mod.rs index c5b3e8eb1..eed14a00d 100644 --- a/bottlecap/src/lifecycle/invocation/triggers/mod.rs +++ b/bottlecap/src/lifecycle/invocation/triggers/mod.rs @@ -1,14 +1,18 @@ use std::{collections::HashMap, hash::BuildHasher}; +use base64::{engine::general_purpose, Engine}; use datadog_trace_protobuf::pb::Span; use serde::{ser::SerializeMap, Serializer}; use serde_json::Value; pub mod api_gateway_http_event; pub mod api_gateway_rest_event; +pub mod sns_event; pub mod sqs_event; pub const DATADOG_CARRIER_KEY: &str = "_datadog"; +pub const FUNCTION_TRIGGER_EVENT_SOURCE_TAG: &str = "function_trigger.event_source"; +pub const FUNCTION_TRIGGER_EVENT_SOURCE_ARN_TAG: &str = "function_trigger.event_source_arn"; pub trait Trigger: Sized { fn new(payload: Value) -> Option; @@ -29,6 +33,14 @@ pub fn get_aws_partition_by_region(region: &str) -> String { } } +#[must_use] +pub fn base64_to_string(base64_string: &str) -> String { + let bytes = general_purpose::STANDARD + .decode(base64_string) + .unwrap_or_default(); + String::from_utf8_lossy(&bytes).to_string() +} + /// Serialize a `HashMap` with lowercase keys /// pub fn lowercase_key( From fcc148e050dbea6169b49ff80a1453f0ab70c070 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?jordan=20gonz=C3=A1lez?= <30836115+duncanista@users.noreply.github.com> Date: Thu, 7 Nov 2024 13:26:36 -0500 Subject: [PATCH 23/31] surrender resource --- bottlecap/src/lifecycle/invocation/triggers/sqs_event.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bottlecap/src/lifecycle/invocation/triggers/sqs_event.rs b/bottlecap/src/lifecycle/invocation/triggers/sqs_event.rs index e7ad776b0..0f69b0d3e 100644 --- a/bottlecap/src/lifecycle/invocation/triggers/sqs_event.rs +++ b/bottlecap/src/lifecycle/invocation/triggers/sqs_event.rs @@ -112,7 +112,7 @@ impl Trigger for SqsRecord { span.name = "aws.sqs".to_string(); span.service = service_name.to_string(); - span.resource.clone_from(&resource); + span.resource = resource; span.r#type = "web".to_string(); span.start = start_time; span.meta.extend(HashMap::from([ From 0e17bae1f7d49177fff2294bc54c836c6f5a2a97 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?jordan=20gonz=C3=A1lez?= <30836115+duncanista@users.noreply.github.com> Date: Thu, 7 Nov 2024 13:26:57 -0500 Subject: [PATCH 24/31] use `SnsRecord` for inferred spans --- .../src/lifecycle/invocation/span_inferrer.rs | 20 +++++++++++++++---- 1 file changed, 16 insertions(+), 4 deletions(-) diff --git a/bottlecap/src/lifecycle/invocation/span_inferrer.rs b/bottlecap/src/lifecycle/invocation/span_inferrer.rs index ecd8d24dc..43a01d32c 100644 --- a/bottlecap/src/lifecycle/invocation/span_inferrer.rs +++ b/bottlecap/src/lifecycle/invocation/span_inferrer.rs @@ -9,13 +9,11 @@ use crate::config::AwsConfig; use crate::lifecycle::invocation::triggers::{ api_gateway_http_event::APIGatewayHttpEvent, api_gateway_rest_event::APIGatewayRestEvent, - sqs_event::SqsRecord, Trigger, + sns_event::SnsRecord, sqs_event::SqsRecord, Trigger, FUNCTION_TRIGGER_EVENT_SOURCE_ARN_TAG, + FUNCTION_TRIGGER_EVENT_SOURCE_TAG, }; use crate::tags::lambda::tags::{INIT_TYPE, SNAP_START_VALUE}; -const FUNCTION_TRIGGER_EVENT_SOURCE_TAG: &str = "function_trigger.event_source"; -const FUNCTION_TRIGGER_EVENT_SOURCE_ARN_TAG: &str = "function_trigger.event_source_arn"; - pub struct SpanInferrer { pub inferred_span: Option, is_async_span: bool, @@ -117,6 +115,20 @@ impl SpanInferrer { ), ]); + self.carrier = Some(t.get_carrier()); + self.trigger_tags = Some(t.get_tags()); + self.is_async_span = t.is_async(); + self.inferred_span = Some(span); + } + } else if SnsRecord::is_match(payload_value) { + if let Some(t) = SnsRecord::new(payload_value.clone()) { + let mut span = Span { + span_id: Self::generate_span_id(), + ..Default::default() + }; + + t.enrich_span(&mut span); + self.carrier = Some(t.get_carrier()); self.trigger_tags = Some(t.get_tags()); self.is_async_span = t.is_async(); From 9fe1c495e82021400eb97569d7f25f09b4f33157 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?jordan=20gonz=C3=A1lez?= <30836115+duncanista@users.noreply.github.com> Date: Thu, 7 Nov 2024 13:42:07 -0500 Subject: [PATCH 25/31] move some constants --- .../src/lifecycle/invocation/span_inferrer.rs | 56 +++++++------------ .../src/lifecycle/invocation/triggers/mod.rs | 2 + 2 files changed, 21 insertions(+), 37 deletions(-) diff --git a/bottlecap/src/lifecycle/invocation/span_inferrer.rs b/bottlecap/src/lifecycle/invocation/span_inferrer.rs index ecd8d24dc..4ad90dffa 100644 --- a/bottlecap/src/lifecycle/invocation/span_inferrer.rs +++ b/bottlecap/src/lifecycle/invocation/span_inferrer.rs @@ -9,13 +9,10 @@ use crate::config::AwsConfig; use crate::lifecycle::invocation::triggers::{ api_gateway_http_event::APIGatewayHttpEvent, api_gateway_rest_event::APIGatewayRestEvent, - sqs_event::SqsRecord, Trigger, + sqs_event::SqsRecord, Trigger, FUNCTION_TRIGGER_EVENT_SOURCE_ARN_TAG, }; use crate::tags::lambda::tags::{INIT_TYPE, SNAP_START_VALUE}; -const FUNCTION_TRIGGER_EVENT_SOURCE_TAG: &str = "function_trigger.event_source"; -const FUNCTION_TRIGGER_EVENT_SOURCE_ARN_TAG: &str = "function_trigger.event_source_arn"; - pub struct SpanInferrer { pub inferred_span: Option, is_async_span: bool, @@ -58,19 +55,14 @@ impl SpanInferrer { }; t.enrich_span(&mut span); - span.meta.extend([ - ( - FUNCTION_TRIGGER_EVENT_SOURCE_TAG.to_string(), - "api_gateway".to_string(), - ), - ( - FUNCTION_TRIGGER_EVENT_SOURCE_ARN_TAG.to_string(), - t.get_arn(&aws_config.region), - ), - ]); + let mut tt = t.get_tags(); + tt.extend([( + FUNCTION_TRIGGER_EVENT_SOURCE_ARN_TAG.to_string(), + t.get_arn(&aws_config.region), + )]); self.carrier = Some(t.get_carrier()); - self.trigger_tags = Some(t.get_tags()); + self.trigger_tags = Some(tt); self.is_async_span = t.is_async(); self.inferred_span = Some(span); } @@ -82,19 +74,14 @@ impl SpanInferrer { }; t.enrich_span(&mut span); - span.meta.extend([ - ( - FUNCTION_TRIGGER_EVENT_SOURCE_TAG.to_string(), - "api_gateway".to_string(), - ), - ( - FUNCTION_TRIGGER_EVENT_SOURCE_ARN_TAG.to_string(), - t.get_arn(&aws_config.region), - ), - ]); + let mut tt = t.get_tags(); + tt.extend([( + FUNCTION_TRIGGER_EVENT_SOURCE_ARN_TAG.to_string(), + t.get_arn(&aws_config.region), + )]); self.carrier = Some(t.get_carrier()); - self.trigger_tags = Some(t.get_tags()); + self.trigger_tags = Some(tt); self.is_async_span = t.is_async(); self.inferred_span = Some(span); } @@ -106,19 +93,14 @@ impl SpanInferrer { }; t.enrich_span(&mut span); - span.meta.extend([ - ( - FUNCTION_TRIGGER_EVENT_SOURCE_TAG.to_string(), - "sqs".to_string(), - ), - ( - FUNCTION_TRIGGER_EVENT_SOURCE_ARN_TAG.to_string(), - t.get_arn(&aws_config.region), - ), - ]); + let mut tt = t.get_tags(); + tt.extend([( + FUNCTION_TRIGGER_EVENT_SOURCE_ARN_TAG.to_string(), + t.get_arn(&aws_config.region), + )]); self.carrier = Some(t.get_carrier()); - self.trigger_tags = Some(t.get_tags()); + self.trigger_tags = Some(tt); self.is_async_span = t.is_async(); self.inferred_span = Some(span); } diff --git a/bottlecap/src/lifecycle/invocation/triggers/mod.rs b/bottlecap/src/lifecycle/invocation/triggers/mod.rs index c5b3e8eb1..a989ce009 100644 --- a/bottlecap/src/lifecycle/invocation/triggers/mod.rs +++ b/bottlecap/src/lifecycle/invocation/triggers/mod.rs @@ -9,6 +9,8 @@ pub mod api_gateway_rest_event; pub mod sqs_event; pub const DATADOG_CARRIER_KEY: &str = "_datadog"; +pub const FUNCTION_TRIGGER_EVENT_SOURCE_TAG: &str = "function_trigger.event_source"; +pub const FUNCTION_TRIGGER_EVENT_SOURCE_ARN_TAG: &str = "function_trigger.event_source_arn"; pub trait Trigger: Sized { fn new(payload: Value) -> Option; From e8006f4d23ba76e493388e1a69b133a9e1a75a3e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?jordan=20gonz=C3=A1lez?= <30836115+duncanista@users.noreply.github.com> Date: Thu, 7 Nov 2024 13:42:26 -0500 Subject: [PATCH 26/31] add missing trigger tags --- .../triggers/api_gateway_http_event.rs | 12 +++++++++++- .../triggers/api_gateway_rest_event.rs | 16 +++++++++++++++- .../lifecycle/invocation/triggers/sqs_event.rs | 13 ++++++++++++- 3 files changed, 38 insertions(+), 3 deletions(-) diff --git a/bottlecap/src/lifecycle/invocation/triggers/api_gateway_http_event.rs b/bottlecap/src/lifecycle/invocation/triggers/api_gateway_http_event.rs index cb420f740..1a110baee 100644 --- a/bottlecap/src/lifecycle/invocation/triggers/api_gateway_http_event.rs +++ b/bottlecap/src/lifecycle/invocation/triggers/api_gateway_http_event.rs @@ -6,7 +6,9 @@ use tracing::debug; use crate::lifecycle::invocation::{ processor::MS_TO_NS, - triggers::{get_aws_partition_by_region, lowercase_key, Trigger}, + triggers::{ + get_aws_partition_by_region, lowercase_key, Trigger, FUNCTION_TRIGGER_EVENT_SOURCE_TAG, + }, }; #[derive(Debug, Serialize, Deserialize, Clone, PartialEq)] @@ -137,6 +139,10 @@ impl Trigger for APIGatewayHttpEvent { "http.method".to_string(), self.request_context.http.method.clone(), ), + ( + FUNCTION_TRIGGER_EVENT_SOURCE_TAG.to_string(), + "api-gateway".to_string(), + ), ]); // route is parameterized // /users/{id}/profile @@ -307,6 +313,10 @@ mod tests { ("http.method".to_string(), "GET".to_string()), ("http.route".to_string(), "/httpapi/get".to_string()), ("http.user_agent".to_string(), "curl/7.64.1".to_string()), + ( + "function_trigger.event_source".to_string(), + "api-gateway".to_string(), + ), ]); assert_eq!(tags, expected); diff --git a/bottlecap/src/lifecycle/invocation/triggers/api_gateway_rest_event.rs b/bottlecap/src/lifecycle/invocation/triggers/api_gateway_rest_event.rs index 1734f9212..e8fc443dd 100644 --- a/bottlecap/src/lifecycle/invocation/triggers/api_gateway_rest_event.rs +++ b/bottlecap/src/lifecycle/invocation/triggers/api_gateway_rest_event.rs @@ -6,7 +6,9 @@ use tracing::debug; use crate::lifecycle::invocation::{ processor::MS_TO_NS, - triggers::{get_aws_partition_by_region, lowercase_key, Trigger}, + triggers::{ + get_aws_partition_by_region, lowercase_key, Trigger, FUNCTION_TRIGGER_EVENT_SOURCE_TAG, + }, }; #[derive(Debug, Serialize, Deserialize, Clone, PartialEq)] @@ -142,6 +144,10 @@ impl Trigger for APIGatewayRestEvent { "http.user_agent".to_string(), self.request_context.identity.user_agent.to_string(), ), + ( + FUNCTION_TRIGGER_EVENT_SOURCE_TAG.to_string(), + "api-gateway".to_string(), + ), ]); if let Some(referer) = self.headers.get("referer") { @@ -276,6 +282,10 @@ mod tests { ("http.method".to_string(), "GET".to_string()), ("http.route".to_string(), "/path".to_string()), ("http.user_agent".to_string(), "user-agent".to_string()), + ( + "function_trigger.event_source".to_string(), + "api-gateway".to_string(), + ), ]); assert_eq!(tags, expected); @@ -339,6 +349,10 @@ mod tests { ("http.method".to_string(), "GET".to_string()), ("http.route".to_string(), "/user/{id}".to_string()), ("http.user_agent".to_string(), "curl/8.1.2".to_string()), + ( + "function_trigger.event_source".to_string(), + "api-gateway".to_string() + ), ]) ); } diff --git a/bottlecap/src/lifecycle/invocation/triggers/sqs_event.rs b/bottlecap/src/lifecycle/invocation/triggers/sqs_event.rs index e7ad776b0..0daed4385 100644 --- a/bottlecap/src/lifecycle/invocation/triggers/sqs_event.rs +++ b/bottlecap/src/lifecycle/invocation/triggers/sqs_event.rs @@ -6,7 +6,10 @@ use tracing::debug; use crate::lifecycle::invocation::{ processor::MS_TO_NS, - triggers::{get_aws_partition_by_region, Trigger, DATADOG_CARRIER_KEY}, + triggers::{ + get_aws_partition_by_region, Trigger, DATADOG_CARRIER_KEY, + FUNCTION_TRIGGER_EVENT_SOURCE_TAG, + }, }; #[derive(Debug, Serialize, Deserialize, Clone, PartialEq)] @@ -137,6 +140,10 @@ impl Trigger for SqsRecord { ("sender_id".to_string(), self.attributes.sender_id.clone()), ("source_arn".to_string(), self.event_source_arn.clone()), ("aws_region".to_string(), self.aws_region.clone()), + ( + FUNCTION_TRIGGER_EVENT_SOURCE_TAG.to_string(), + "sqs".to_string(), + ), ]) } @@ -278,6 +285,10 @@ mod tests { "arn:aws:sqs:us-east-1:123456789012:MyQueue".to_string(), ), ("aws_region".to_string(), "us-east-1".to_string()), + ( + "function_trigger.event_source".to_string(), + "sqs".to_string(), + ), ]); assert_eq!(tags, expected); From d3e36704007fb9e4f195c6f3340c1b6d9a2bfd7f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?jordan=20gonz=C3=A1lez?= <30836115+duncanista@users.noreply.github.com> Date: Thu, 7 Nov 2024 13:46:03 -0500 Subject: [PATCH 27/31] missed one case --- .../lifecycle/invocation/triggers/api_gateway_http_event.rs | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/bottlecap/src/lifecycle/invocation/triggers/api_gateway_http_event.rs b/bottlecap/src/lifecycle/invocation/triggers/api_gateway_http_event.rs index 1a110baee..e07d86692 100644 --- a/bottlecap/src/lifecycle/invocation/triggers/api_gateway_http_event.rs +++ b/bottlecap/src/lifecycle/invocation/triggers/api_gateway_http_event.rs @@ -372,6 +372,10 @@ mod tests { ("http.method".to_string(), "GET".to_string()), ("http.route".to_string(), "/user/{id}".to_string()), ("http.user_agent".to_string(), "curl/8.1.2".to_string()), + ( + "function_trigger.event_source".to_string(), + "api-gateway".to_string(), + ), ]); assert_eq!(tags, expected); } From 792697f387e0ef24c7e2f6ce407db0eebf6ed6d3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?jordan=20gonz=C3=A1lez?= <30836115+duncanista@users.noreply.github.com> Date: Thu, 7 Nov 2024 13:51:08 -0500 Subject: [PATCH 28/31] update unit tests --- bottlecap/src/lifecycle/invocation/triggers/sns_event.rs | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/bottlecap/src/lifecycle/invocation/triggers/sns_event.rs b/bottlecap/src/lifecycle/invocation/triggers/sns_event.rs index 6f02b85df..e4acd3faa 100644 --- a/bottlecap/src/lifecycle/invocation/triggers/sns_event.rs +++ b/bottlecap/src/lifecycle/invocation/triggers/sns_event.rs @@ -246,7 +246,13 @@ mod tests { let event = SnsRecord::new(payload).expect("Failed to deserialize SnsRecord"); let tags = event.get_tags(); - let expected = HashMap::from([]); + let expected = HashMap::from([ + ("function_trigger.event_source".to_string(), "sns".to_string()), + ( + "function_trigger.event_source_arn".to_string(), + "arn:aws:sns:sa-east-1:425362996713:serverlessTracingTopicPy".to_string(), + ), + ]); assert_eq!(tags, expected); } From 22f20ed7ab0e1eda1b89f702f6da0eec910f9633 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?jordan=20gonz=C3=A1lez?= <30836115+duncanista@users.noreply.github.com> Date: Thu, 7 Nov 2024 13:54:36 -0500 Subject: [PATCH 29/31] update `tt` to `t.get_tags()` --- bottlecap/src/lifecycle/invocation/span_inferrer.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bottlecap/src/lifecycle/invocation/span_inferrer.rs b/bottlecap/src/lifecycle/invocation/span_inferrer.rs index ae513222a..bc6ac7eac 100644 --- a/bottlecap/src/lifecycle/invocation/span_inferrer.rs +++ b/bottlecap/src/lifecycle/invocation/span_inferrer.rs @@ -114,7 +114,7 @@ impl SpanInferrer { t.enrich_span(&mut span); self.carrier = Some(t.get_carrier()); - self.trigger_tags = Some(tt); + self.trigger_tags = Some(t.get_tags()); self.is_async_span = t.is_async(); self.inferred_span = Some(span); } From d0860fd703be2aa0da27b82e0875f40fdb5d98ad Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?jordan=20gonz=C3=A1lez?= <30836115+duncanista@users.noreply.github.com> Date: Thu, 7 Nov 2024 13:55:01 -0500 Subject: [PATCH 30/31] fmt --- bottlecap/src/lifecycle/invocation/triggers/sns_event.rs | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/bottlecap/src/lifecycle/invocation/triggers/sns_event.rs b/bottlecap/src/lifecycle/invocation/triggers/sns_event.rs index e4acd3faa..e3bbebd82 100644 --- a/bottlecap/src/lifecycle/invocation/triggers/sns_event.rs +++ b/bottlecap/src/lifecycle/invocation/triggers/sns_event.rs @@ -247,7 +247,10 @@ mod tests { let tags = event.get_tags(); let expected = HashMap::from([ - ("function_trigger.event_source".to_string(), "sns".to_string()), + ( + "function_trigger.event_source".to_string(), + "sns".to_string(), + ), ( "function_trigger.event_source_arn".to_string(), "arn:aws:sns:sa-east-1:425362996713:serverlessTracingTopicPy".to_string(), From bd3830f5d7b689675d7f71437991d4a8c0d8bf26 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?jordan=20gonz=C3=A1lez?= <30836115+duncanista@users.noreply.github.com> Date: Thu, 7 Nov 2024 14:00:39 -0500 Subject: [PATCH 31/31] typo --- bottlecap/src/lifecycle/invocation/triggers/sns_event.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bottlecap/src/lifecycle/invocation/triggers/sns_event.rs b/bottlecap/src/lifecycle/invocation/triggers/sns_event.rs index e3bbebd82..443a6ada9 100644 --- a/bottlecap/src/lifecycle/invocation/triggers/sns_event.rs +++ b/bottlecap/src/lifecycle/invocation/triggers/sns_event.rs @@ -103,7 +103,7 @@ impl Trigger for SnsRecord { span.r#type = "web".to_string(); span.start = start_time; span.meta.extend([ - ("operation_name".to_string(), "aws.sqs".to_string()), + ("operation_name".to_string(), "aws.sns".to_string()), ("topicname".to_string(), resource), ("topic_arn".to_string(), self.sns.topic_arn.clone()), ("message_id".to_string(), self.sns.message_id.clone()),