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; } } diff --git a/bottlecap/src/lifecycle/invocation/span_inferrer.rs b/bottlecap/src/lifecycle/invocation/span_inferrer.rs index b63f2a82d..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, - 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, @@ -46,6 +43,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 { @@ -54,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); } @@ -78,24 +74,38 @@ 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(tt); + 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.clone()) { + let mut span = Span { + span_id: Self::generate_span_id(), + ..Default::default() + }; + + t.enrich_span(&mut span); + 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); } } else { - debug!("Unable to infer span from payload"); + debug!("Unable to infer span from payload: no matching trigger found"); } } @@ -124,7 +134,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; } 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..e07d86692 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)] @@ -114,10 +116,7 @@ impl Trigger for APIGatewayHttpEvent { "request_id".to_string(), self.request_context.request_id.clone(), ), - ("resource_names".to_string(), resource), ])); - - // todo: update global(? IsAsync if event payload is `Event` } fn get_tags(&self) -> HashMap { @@ -140,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 @@ -287,7 +290,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()), ]) ); } @@ -311,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); @@ -345,7 +351,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()), ]) ); } @@ -367,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); } 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..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)] @@ -109,7 +111,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(), @@ -143,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") { @@ -256,7 +261,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()), ]) ); } @@ -278,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); @@ -314,7 +322,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); } @@ -342,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/mod.rs b/bottlecap/src/lifecycle/invocation/triggers/mod.rs index f04db8a81..a989ce009 100644 --- a/bottlecap/src/lifecycle/invocation/triggers/mod.rs +++ b/bottlecap/src/lifecycle/invocation/triggers/mod.rs @@ -6,6 +6,11 @@ use serde_json::Value; pub mod api_gateway_http_event; 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; 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..0daed4385 --- /dev/null +++ b/bottlecap/src/lifecycle/invocation/triggers/sqs_event.rs @@ -0,0 +1,329 @@ +use datadog_trace_protobuf::pb::Span; +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, DATADOG_CARRIER_KEY, + FUNCTION_TRIGGER_EVENT_SOURCE_TAG, + }, +}; + +#[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 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, + #[serde(rename = "SenderId")] + pub sender_id: 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 SQS Record: {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; + 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.attributes.sender_id.clone()), + ("source_arn".to_string(), self.event_source_arn.clone()), + ("aws_region".to_string(), self.aws_region.clone()), + ])); + } + + fn get_tags(&self) -> HashMap { + 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()), + ( + FUNCTION_TRIGGER_EVENT_SOURCE_TAG.to_string(), + "sqs".to_string(), + ), + ]) + } + + fn get_arn(&self, region: &str) -> String { + if let [_, _, _, _, account, queue_name] = self + .event_source_arn + .split(':') + .collect::>() + .as_slice() + { + format!( + "arn:{}:sqs:{}:{}:{}", + get_aws_partition_by_region(region), + region, + account, + queue_name + ) + } else { + String::new() + } + } + + fn is_async(&self) -> bool { + true + } + + fn get_carrier(&self) -> HashMap { + let carrier = HashMap::new(); + if let Some(ma) = self.message_attributes.get(DATADOG_CARRIER_KEY) { + if let Some(string_value) = &ma.string_value { + return serde_json::from_str(string_value).unwrap_or_default(); + } + } + + // TODO: AWSTraceHeader + + carrier + } +} + +#[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("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 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(), + 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, + 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); + } + + #[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"); + + 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 SqsRecord"); + assert!(!SqsRecord::is_match(&payload)); + } + + #[test] + 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 mut span = Span::default(); + event.enrich_span(&mut span); + 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([ + ("operation_name".to_string(), "aws.sqs".to_string()), + ( + "receipt_handle".to_string(), + "MessageReceiptHandle".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()), + ]) + ); + } + + #[test] + 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 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(), + ), + ("aws_region".to_string(), "us-east-1".to_string()), + ( + "function_trigger.event_source".to_string(), + "sqs".to_string(), + ), + ]); + + assert_eq!(tags, expected); + } + + #[test] + fn test_get_arn() { + 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!( + event.get_arn("us-east-1"), + "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); + } +} 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() diff --git a/bottlecap/tests/payloads/sqs_event.json b/bottlecap/tests/payloads/sqs_event.json new file mode 100644 index 000000000..5cc7837fd --- /dev/null +++ b/bottlecap/tests/payloads/sqs_event.json @@ -0,0 +1,27 @@ +{ + "Records": [ + { + "messageId": "19dd0b57-b21e-4ac1-bd88-01bbb068cb78", + "receiptHandle": "MessageReceiptHandle", + "body": "Hello from SQS!", + "attributes": { + "ApproximateReceiveCount": "1", + "SentTimestamp": "1523232000000", + "SenderId": "123456789012", + "ApproximateFirstReceiveTimestamp": "1523232000001" + }, + "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", + "awsRegion": "us-east-1" + } + ] +}