From 93eda61a0b4e4de2cba2c9b0402cba4af29252ad Mon Sep 17 00:00:00 2001 From: Zarir Hamza Date: Fri, 18 Jul 2025 01:27:21 -0400 Subject: [PATCH 01/15] add instance_name for service name --- bottlecap/src/lifecycle/invocation/processor.rs | 2 +- .../src/lifecycle/invocation/triggers/alb_event.rs | 6 ++++-- .../invocation/triggers/api_gateway_http_event.rs | 7 ++++--- .../invocation/triggers/api_gateway_rest_event.rs | 7 ++++--- .../triggers/api_gateway_websocket_event.rs | 7 ++++--- .../lifecycle/invocation/triggers/dynamodb_event.rs | 6 +++--- .../invocation/triggers/event_bridge_event.rs | 6 +++--- .../lifecycle/invocation/triggers/kinesis_event.rs | 6 +++--- .../invocation/triggers/lambda_function_url_event.rs | 6 +++--- bottlecap/src/lifecycle/invocation/triggers/mod.rs | 11 +++++++++-- .../src/lifecycle/invocation/triggers/msk_event.rs | 6 +++--- .../src/lifecycle/invocation/triggers/s3_event.rs | 6 +++--- .../src/lifecycle/invocation/triggers/sns_event.rs | 6 +++--- .../src/lifecycle/invocation/triggers/sqs_event.rs | 6 +++--- 14 files changed, 50 insertions(+), 38 deletions(-) diff --git a/bottlecap/src/lifecycle/invocation/processor.rs b/bottlecap/src/lifecycle/invocation/processor.rs index 1f8c246b0..51553939e 100644 --- a/bottlecap/src/lifecycle/invocation/processor.rs +++ b/bottlecap/src/lifecycle/invocation/processor.rs @@ -89,11 +89,11 @@ impl Processor { aws_config: Arc, metrics_aggregator: Arc>, ) -> Self { - let service = config.service.clone().unwrap_or(String::from("aws.lambda")); let resource = tags_provider .get_canonical_resource_name() .unwrap_or(String::from("aws.lambda")); + let service = config.service.clone().unwrap_or(resource.clone()); let propagator = DatadogCompositePropagator::new(Arc::clone(&config)); Processor { diff --git a/bottlecap/src/lifecycle/invocation/triggers/alb_event.rs b/bottlecap/src/lifecycle/invocation/triggers/alb_event.rs index 3a46a7059..2358fef97 100644 --- a/bottlecap/src/lifecycle/invocation/triggers/alb_event.rs +++ b/bottlecap/src/lifecycle/invocation/triggers/alb_event.rs @@ -230,7 +230,8 @@ mod tests { assert_eq!( event.resolve_service_name( &specific_service_mapping, - &event.request_context.elb.target_group_arn + &event.request_context.elb.target_group_arn, + None ), "specific-service" ); @@ -242,7 +243,8 @@ mod tests { assert_eq!( event.resolve_service_name( &generic_service_mapping, - &event.request_context.elb.target_group_arn + &event.request_context.elb.target_group_arn, + None ), "generic-service" ); 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 f98776ea4..1d1b61222 100644 --- a/bottlecap/src/lifecycle/invocation/triggers/api_gateway_http_event.rs +++ b/bottlecap/src/lifecycle/invocation/triggers/api_gateway_http_event.rs @@ -79,7 +79,7 @@ impl Trigger for APIGatewayHttpEvent { let start_time = (self.request_context.time_epoch as f64 * MS_TO_NS) as i64; let service_name = - self.resolve_service_name(service_mapping, &self.request_context.domain_name); + self.resolve_service_name(service_mapping, &self.request_context.domain_name, None); span.name = "aws.httpapi".to_string(); span.service = service_name; @@ -421,7 +421,8 @@ mod tests { assert_eq!( event.resolve_service_name( &specific_service_mapping, - &event.request_context.domain_name + &event.request_context.domain_name, + None ), "specific-service" ); @@ -432,7 +433,7 @@ mod tests { )]); assert_eq!( event - .resolve_service_name(&generic_service_mapping, &event.request_context.domain_name), + .resolve_service_name(&generic_service_mapping, &event.request_context.domain_name, None), "generic-service" ); } 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 669256a8a..00a2ee373 100644 --- a/bottlecap/src/lifecycle/invocation/triggers/api_gateway_rest_event.rs +++ b/bottlecap/src/lifecycle/invocation/triggers/api_gateway_rest_event.rs @@ -82,7 +82,7 @@ impl Trigger for APIGatewayRestEvent { let start_time = (self.request_context.time_epoch as f64 * MS_TO_NS) as i64; let service_name = - self.resolve_service_name(service_mapping, &self.request_context.domain_name); + self.resolve_service_name(service_mapping, &self.request_context.domain_name, None); span.name = "aws.apigateway".to_string(); span.service = service_name; @@ -437,7 +437,8 @@ mod tests { assert_eq!( event.resolve_service_name( &specific_service_mapping, - &event.request_context.domain_name + &event.request_context.domain_name, + None ), "specific-service" ); @@ -448,7 +449,7 @@ mod tests { )]); assert_eq!( event - .resolve_service_name(&generic_service_mapping, &event.request_context.domain_name), + .resolve_service_name(&generic_service_mapping, &event.request_context.domain_name, None), "generic-service" ); } diff --git a/bottlecap/src/lifecycle/invocation/triggers/api_gateway_websocket_event.rs b/bottlecap/src/lifecycle/invocation/triggers/api_gateway_websocket_event.rs index 273e9772d..21c341bee 100644 --- a/bottlecap/src/lifecycle/invocation/triggers/api_gateway_websocket_event.rs +++ b/bottlecap/src/lifecycle/invocation/triggers/api_gateway_websocket_event.rs @@ -72,7 +72,7 @@ impl Trigger for APIGatewayWebSocketEvent { let start_time = (self.request_context.time_epoch as f64 * MS_TO_NS) as i64; let service_name = - self.resolve_service_name(service_mapping, &self.request_context.domain_name); + self.resolve_service_name(service_mapping, &self.request_context.domain_name, None); span.name = "aws.apigateway".to_string(); span.service = service_name; @@ -362,7 +362,8 @@ mod tests { assert_eq!( event.resolve_service_name( &specific_service_mapping, - &event.request_context.domain_name + &event.request_context.domain_name, + None ), "specific-service" ); @@ -374,7 +375,7 @@ mod tests { assert_eq!( event - .resolve_service_name(&generic_service_mapping, &event.request_context.domain_name), + .resolve_service_name(&generic_service_mapping, &event.request_context.domain_name, None), "generic-service" ); } diff --git a/bottlecap/src/lifecycle/invocation/triggers/dynamodb_event.rs b/bottlecap/src/lifecycle/invocation/triggers/dynamodb_event.rs index bde95f035..10e911cff 100644 --- a/bottlecap/src/lifecycle/invocation/triggers/dynamodb_event.rs +++ b/bottlecap/src/lifecycle/invocation/triggers/dynamodb_event.rs @@ -108,7 +108,7 @@ impl Trigger for DynamoDbRecord { let start_time = (self.dynamodb.approximate_creation_date_time * S_TO_NS) as i64; - let service_name = self.resolve_service_name(service_mapping, "dynamodb"); + let service_name = self.resolve_service_name(service_mapping, &table_name, "dynamodb"); span.name = String::from("aws.dynamodb"); span.service = service_name.to_string(); @@ -355,14 +355,14 @@ mod tests { ]); assert_eq!( - event.resolve_service_name(&specific_service_mapping, "dynamodb"), + event.resolve_service_name(&specific_service_mapping, &event.get_specific_identifier(), "dynamodb"), "specific-service" ); let generic_service_mapping = HashMap::from([("lambda_dynamodb".to_string(), "generic-service".to_string())]); assert_eq!( - event.resolve_service_name(&generic_service_mapping, "dynamodb"), + event.resolve_service_name(&generic_service_mapping, &event.get_specific_identifier(), "dynamodb"), "generic-service" ); } diff --git a/bottlecap/src/lifecycle/invocation/triggers/event_bridge_event.rs b/bottlecap/src/lifecycle/invocation/triggers/event_bridge_event.rs index b790dfd2c..01aa61435 100644 --- a/bottlecap/src/lifecycle/invocation/triggers/event_bridge_event.rs +++ b/bottlecap/src/lifecycle/invocation/triggers/event_bridge_event.rs @@ -65,7 +65,7 @@ impl Trigger for EventBridgeEvent { .and_then(|s| s.parse::().ok()) .map_or(start_time_seconds, |s| (s * MS_TO_NS) as i64); - let service_name = self.resolve_service_name(service_mapping, "eventbridge"); + let service_name = self.resolve_service_name(service_mapping, &resource_name, "eventbridge"); span.name = String::from("aws.eventbridge"); span.service = service_name.to_string(); @@ -271,7 +271,7 @@ mod tests { ]); assert_eq!( - event.resolve_service_name(&specific_service_mapping, "eventbridge"), + event.resolve_service_name(&specific_service_mapping, &event.get_specific_identifier(), "eventbridge"), "specific-service" ); @@ -280,7 +280,7 @@ mod tests { "generic-service".to_string(), )]); assert_eq!( - event.resolve_service_name(&generic_service_mapping, "eventbridge"), + event.resolve_service_name(&generic_service_mapping, &event.get_specific_identifier(), "eventbridge"), "generic-service" ); } diff --git a/bottlecap/src/lifecycle/invocation/triggers/kinesis_event.rs b/bottlecap/src/lifecycle/invocation/triggers/kinesis_event.rs index ae55add0c..665dfa343 100644 --- a/bottlecap/src/lifecycle/invocation/triggers/kinesis_event.rs +++ b/bottlecap/src/lifecycle/invocation/triggers/kinesis_event.rs @@ -74,7 +74,7 @@ impl Trigger for KinesisRecord { fn enrich_span(&self, span: &mut Span, service_mapping: &HashMap) { let stream_name = self.get_specific_identifier(); let shard_id = self.event_id.split(':').next().unwrap_or_default(); - let service_name = self.resolve_service_name(service_mapping, "kinesis"); + let service_name = self.resolve_service_name(service_mapping, &stream_name, "kinesis"); span.name = String::from("aws.kinesis"); span.service = service_name; @@ -280,14 +280,14 @@ mod tests { ]); assert_eq!( - event.resolve_service_name(&specific_service_mapping, "kinesis"), + event.resolve_service_name(&specific_service_mapping, &event.get_specific_identifier(), "kinesis"), "specific-service" ); let generic_service_mapping = HashMap::from([("lambda_kinesis".to_string(), "generic-service".to_string())]); assert_eq!( - event.resolve_service_name(&generic_service_mapping, "kinesis"), + event.resolve_service_name(&generic_service_mapping, &event.get_specific_identifier(), "kinesis"), "generic-service" ); } diff --git a/bottlecap/src/lifecycle/invocation/triggers/lambda_function_url_event.rs b/bottlecap/src/lifecycle/invocation/triggers/lambda_function_url_event.rs index 951f5a4d6..7c670d0fc 100644 --- a/bottlecap/src/lifecycle/invocation/triggers/lambda_function_url_event.rs +++ b/bottlecap/src/lifecycle/invocation/triggers/lambda_function_url_event.rs @@ -78,7 +78,7 @@ impl Trigger for LambdaFunctionUrlEvent { let start_time = (self.request_context.time_epoch as f64 * MS_TO_NS) as i64; let service_name = - self.resolve_service_name(service_mapping, &self.request_context.domain_name); + self.resolve_service_name(service_mapping, &self.request_context.domain_name, None); span.name = String::from("aws.lambda.url"); span.service = service_name; @@ -336,14 +336,14 @@ mod tests { ]); assert_eq!( - event.resolve_service_name(&specific_service_mapping, "domain-name"), + event.resolve_service_name(&specific_service_mapping, "domain-name", None), "specific-service" ); let generic_service_mapping = HashMap::from([("lambda_url".to_string(), "generic-service".to_string())]); assert_eq!( - event.resolve_service_name(&generic_service_mapping, "domain-name"), + event.resolve_service_name(&generic_service_mapping, "domain-name", None), "generic-service" ); } diff --git a/bottlecap/src/lifecycle/invocation/triggers/mod.rs b/bottlecap/src/lifecycle/invocation/triggers/mod.rs index a92c46f38..70d39d7f7 100644 --- a/bottlecap/src/lifecycle/invocation/triggers/mod.rs +++ b/bottlecap/src/lifecycle/invocation/triggers/mod.rs @@ -113,12 +113,19 @@ pub trait Trigger: ServiceNameResolver { fn resolve_service_name( &self, service_mapping: &HashMap, - fallback: &str, + instance_name: &str, + fallback: Option<&str>, ) -> String { service_mapping .get(&self.get_specific_identifier()) .or_else(|| service_mapping.get(self.get_generic_identifier())) - .unwrap_or(&fallback.to_string()) + .unwrap_or_else(|| { + if !instance_name.is_empty() { + instance_name.to_string() + } else { + fallback.unwrap_or_default().to_string() + } + }) .to_string() } } diff --git a/bottlecap/src/lifecycle/invocation/triggers/msk_event.rs b/bottlecap/src/lifecycle/invocation/triggers/msk_event.rs index 50e74427f..20b3c67d2 100644 --- a/bottlecap/src/lifecycle/invocation/triggers/msk_event.rs +++ b/bottlecap/src/lifecycle/invocation/triggers/msk_event.rs @@ -63,7 +63,7 @@ impl Trigger for MSKEvent { debug!("Enriching an Inferred Span for an MSK event"); span.name = String::from("aws.msk"); - span.service = self.resolve_service_name(service_mapping, "msk"); + span.service = self.resolve_service_name(service_mapping, &self.get_specific_identifier(), "msk"); span.r#type = String::from("web"); let first_value = self.records.values().find_map(|arr| arr.first()); @@ -241,14 +241,14 @@ mod tests { ]); assert_eq!( - event.resolve_service_name(&specific_service_mapping, "msk"), + event.resolve_service_name(&specific_service_mapping, &event.get_specific_identifier(), "msk"), "specific-service" ); let generic_service_mapping = HashMap::from([("lambda_msk".to_string(), "generic-service".to_string())]); assert_eq!( - event.resolve_service_name(&generic_service_mapping, "msk"), + event.resolve_service_name(&generic_service_mapping, &event.get_specific_identifier(), "msk"), "generic-service" ); } diff --git a/bottlecap/src/lifecycle/invocation/triggers/s3_event.rs b/bottlecap/src/lifecycle/invocation/triggers/s3_event.rs index 2172700b4..aa54b5bea 100644 --- a/bottlecap/src/lifecycle/invocation/triggers/s3_event.rs +++ b/bottlecap/src/lifecycle/invocation/triggers/s3_event.rs @@ -86,7 +86,7 @@ impl Trigger for S3Record { .timestamp_nanos_opt() .unwrap_or((self.event_time.timestamp_millis() as f64 * MS_TO_NS) as i64); - let service_name = self.resolve_service_name(service_mapping, "s3"); + let service_name = self.resolve_service_name(service_mapping, &bucket_name, "s3"); span.name = String::from("aws.s3"); span.service = service_name.to_string(); @@ -286,14 +286,14 @@ mod tests { ]); assert_eq!( - event.resolve_service_name(&specific_service_mapping, "s3"), + event.resolve_service_name(&specific_service_mapping, &event.get_specific_identifier(), "s3"), "specific-service" ); let generic_service_mapping = HashMap::from([("lambda_s3".to_string(), "generic-service".to_string())]); assert_eq!( - event.resolve_service_name(&generic_service_mapping, "s3"), + event.resolve_service_name(&generic_service_mapping, &event.get_specific_identifier(), "s3"), "generic-service" ); } diff --git a/bottlecap/src/lifecycle/invocation/triggers/sns_event.rs b/bottlecap/src/lifecycle/invocation/triggers/sns_event.rs index 091dcdf53..00690323b 100644 --- a/bottlecap/src/lifecycle/invocation/triggers/sns_event.rs +++ b/bottlecap/src/lifecycle/invocation/triggers/sns_event.rs @@ -93,7 +93,7 @@ impl Trigger for SnsRecord { .timestamp_nanos_opt() .unwrap_or((self.sns.timestamp.timestamp_millis() as f64 * MS_TO_NS) as i64); - let service_name = self.resolve_service_name(service_mapping, "sns"); + let service_name = self.resolve_service_name(service_mapping, &resource_name, "sns"); span.name = "aws.sns".to_string(); span.service = service_name.to_string(); @@ -369,14 +369,14 @@ mod tests { ]); assert_eq!( - event.resolve_service_name(&specific_service_mapping, "sns"), + event.resolve_service_name(&specific_service_mapping, &event.get_specific_identifier(), "sns"), "specific-service" ); let generic_service_mapping = HashMap::from([("lambda_sns".to_string(), "generic-service".to_string())]); assert_eq!( - event.resolve_service_name(&generic_service_mapping, "sns"), + event.resolve_service_name(&generic_service_mapping, &event.get_specific_identifier(), "sns"), "generic-service" ); } diff --git a/bottlecap/src/lifecycle/invocation/triggers/sqs_event.rs b/bottlecap/src/lifecycle/invocation/triggers/sqs_event.rs index 0a3472996..b9c4427bf 100644 --- a/bottlecap/src/lifecycle/invocation/triggers/sqs_event.rs +++ b/bottlecap/src/lifecycle/invocation/triggers/sqs_event.rs @@ -110,7 +110,7 @@ impl Trigger for SqsRecord { .unwrap_or_default() as f64 * MS_TO_NS) as i64; - let service_name = self.resolve_service_name(service_mapping, "sqs"); + let service_name = self.resolve_service_name(service_mapping, &resource, "sqs"); span.name = "aws.sqs".to_string(); span.service = service_name.to_string(); @@ -506,14 +506,14 @@ mod tests { ]); assert_eq!( - event.resolve_service_name(&specific_service_mapping, "sqs"), + event.resolve_service_name(&specific_service_mapping, &event.get_specific_identifier(), "sqs"), "specific-service" ); let generic_service_mapping = HashMap::from([("lambda_sqs".to_string(), "generic-service".to_string())]); assert_eq!( - event.resolve_service_name(&generic_service_mapping, "sqs"), + event.resolve_service_name(&generic_service_mapping, &event.get_specific_identifier(), "sqs"), "generic-service" ); } From 629f372830f38f7b98f5abaf16d0e01152f75836 Mon Sep 17 00:00:00 2001 From: Zarir Hamza Date: Fri, 18 Jul 2025 01:28:54 -0400 Subject: [PATCH 02/15] remove reliance on resource --- .../src/lifecycle/invocation/triggers/event_bridge_event.rs | 2 +- bottlecap/src/lifecycle/invocation/triggers/sns_event.rs | 2 +- bottlecap/src/lifecycle/invocation/triggers/sqs_event.rs | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/bottlecap/src/lifecycle/invocation/triggers/event_bridge_event.rs b/bottlecap/src/lifecycle/invocation/triggers/event_bridge_event.rs index 01aa61435..723bde2b8 100644 --- a/bottlecap/src/lifecycle/invocation/triggers/event_bridge_event.rs +++ b/bottlecap/src/lifecycle/invocation/triggers/event_bridge_event.rs @@ -65,7 +65,7 @@ impl Trigger for EventBridgeEvent { .and_then(|s| s.parse::().ok()) .map_or(start_time_seconds, |s| (s * MS_TO_NS) as i64); - let service_name = self.resolve_service_name(service_mapping, &resource_name, "eventbridge"); + let service_name = self.resolve_service_name(service_mapping, &self.get_specific_identifier(), "eventbridge"); span.name = String::from("aws.eventbridge"); span.service = service_name.to_string(); diff --git a/bottlecap/src/lifecycle/invocation/triggers/sns_event.rs b/bottlecap/src/lifecycle/invocation/triggers/sns_event.rs index 00690323b..aa0dec255 100644 --- a/bottlecap/src/lifecycle/invocation/triggers/sns_event.rs +++ b/bottlecap/src/lifecycle/invocation/triggers/sns_event.rs @@ -93,7 +93,7 @@ impl Trigger for SnsRecord { .timestamp_nanos_opt() .unwrap_or((self.sns.timestamp.timestamp_millis() as f64 * MS_TO_NS) as i64); - let service_name = self.resolve_service_name(service_mapping, &resource_name, "sns"); + let service_name = self.resolve_service_name(service_mapping, &self.get_specific_identifier(), "sns"); span.name = "aws.sns".to_string(); span.service = service_name.to_string(); diff --git a/bottlecap/src/lifecycle/invocation/triggers/sqs_event.rs b/bottlecap/src/lifecycle/invocation/triggers/sqs_event.rs index b9c4427bf..8a4bd802f 100644 --- a/bottlecap/src/lifecycle/invocation/triggers/sqs_event.rs +++ b/bottlecap/src/lifecycle/invocation/triggers/sqs_event.rs @@ -110,7 +110,7 @@ impl Trigger for SqsRecord { .unwrap_or_default() as f64 * MS_TO_NS) as i64; - let service_name = self.resolve_service_name(service_mapping, &resource, "sqs"); + let service_name = self.resolve_service_name(service_mapping, &self.get_specific_identifier(), "sqs"); span.name = "aws.sqs".to_string(); span.service = service_name.to_string(); From b0f5b4672002afd636f062104f5e62aff87808bc Mon Sep 17 00:00:00 2001 From: Zarir Hamza Date: Fri, 18 Jul 2025 01:50:19 -0400 Subject: [PATCH 03/15] add span.kind --- bottlecap/src/lifecycle/invocation/span_inferrer.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/bottlecap/src/lifecycle/invocation/span_inferrer.rs b/bottlecap/src/lifecycle/invocation/span_inferrer.rs index 32839b8f0..1ad288266 100644 --- a/bottlecap/src/lifecycle/invocation/span_inferrer.rs +++ b/bottlecap/src/lifecycle/invocation/span_inferrer.rs @@ -282,6 +282,7 @@ impl SpanInferrer { String::from("peer.service"), invocation_span.service.clone(), ); + s.meta.insert("span.kind".to_string(), "server".to_string()); if let Some(ws) = &mut self.wrapped_inferred_span { ws.trace_id = invocation_span.trace_id; From 42009b261b607c3b6b4d488c0bd1789a5f76abc0 Mon Sep 17 00:00:00 2001 From: Zarir Hamza Date: Fri, 18 Jul 2025 02:17:19 -0400 Subject: [PATCH 04/15] fix formatter and tests --- .../lifecycle/invocation/triggers/alb_event.rs | 4 ++-- .../triggers/api_gateway_http_event.rs | 16 +++++++++++----- .../triggers/api_gateway_rest_event.rs | 16 +++++++++++----- .../triggers/api_gateway_websocket_event.rs | 16 +++++++++++----- .../invocation/triggers/dynamodb_event.rs | 14 +++++++++++--- .../invocation/triggers/event_bridge_event.rs | 18 +++++++++++++++--- .../invocation/triggers/kinesis_event.rs | 14 +++++++++++--- .../triggers/lambda_function_url_event.rs | 11 +++++++---- .../src/lifecycle/invocation/triggers/mod.rs | 6 +++--- .../lifecycle/invocation/triggers/msk_event.rs | 17 +++++++++++++---- .../lifecycle/invocation/triggers/s3_event.rs | 14 +++++++++++--- .../lifecycle/invocation/triggers/sns_event.rs | 17 +++++++++++++---- .../lifecycle/invocation/triggers/sqs_event.rs | 17 +++++++++++++---- bottlecap/tests/payloads/eventbridge_span.json | 2 +- 14 files changed, 133 insertions(+), 49 deletions(-) diff --git a/bottlecap/src/lifecycle/invocation/triggers/alb_event.rs b/bottlecap/src/lifecycle/invocation/triggers/alb_event.rs index 2358fef97..912a6e073 100644 --- a/bottlecap/src/lifecycle/invocation/triggers/alb_event.rs +++ b/bottlecap/src/lifecycle/invocation/triggers/alb_event.rs @@ -231,7 +231,7 @@ mod tests { event.resolve_service_name( &specific_service_mapping, &event.request_context.elb.target_group_arn, - None + "lambda_application_load_balancer" ), "specific-service" ); @@ -244,7 +244,7 @@ mod tests { event.resolve_service_name( &generic_service_mapping, &event.request_context.elb.target_group_arn, - None + "lambda_application_load_balancer" ), "generic-service" ); 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 1d1b61222..b63d4f145 100644 --- a/bottlecap/src/lifecycle/invocation/triggers/api_gateway_http_event.rs +++ b/bottlecap/src/lifecycle/invocation/triggers/api_gateway_http_event.rs @@ -78,8 +78,11 @@ impl Trigger for APIGatewayHttpEvent { ); let start_time = (self.request_context.time_epoch as f64 * MS_TO_NS) as i64; - let service_name = - self.resolve_service_name(service_mapping, &self.request_context.domain_name, None); + let service_name = self.resolve_service_name( + service_mapping, + &self.request_context.domain_name, + "api_gateway_http", + ); span.name = "aws.httpapi".to_string(); span.service = service_name; @@ -422,7 +425,7 @@ mod tests { event.resolve_service_name( &specific_service_mapping, &event.request_context.domain_name, - None + "api_gateway_http" ), "specific-service" ); @@ -432,8 +435,11 @@ mod tests { "generic-service".to_string(), )]); assert_eq!( - event - .resolve_service_name(&generic_service_mapping, &event.request_context.domain_name, None), + event.resolve_service_name( + &generic_service_mapping, + &event.request_context.domain_name, + "api_gateway_http" + ), "generic-service" ); } 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 00a2ee373..305103c52 100644 --- a/bottlecap/src/lifecycle/invocation/triggers/api_gateway_rest_event.rs +++ b/bottlecap/src/lifecycle/invocation/triggers/api_gateway_rest_event.rs @@ -81,8 +81,11 @@ impl Trigger for APIGatewayRestEvent { ); let start_time = (self.request_context.time_epoch as f64 * MS_TO_NS) as i64; - let service_name = - self.resolve_service_name(service_mapping, &self.request_context.domain_name, None); + let service_name = self.resolve_service_name( + service_mapping, + &self.request_context.domain_name, + "api_gateway_rest", + ); span.name = "aws.apigateway".to_string(); span.service = service_name; @@ -438,7 +441,7 @@ mod tests { event.resolve_service_name( &specific_service_mapping, &event.request_context.domain_name, - None + "api_gateway_rest" ), "specific-service" ); @@ -448,8 +451,11 @@ mod tests { "generic-service".to_string(), )]); assert_eq!( - event - .resolve_service_name(&generic_service_mapping, &event.request_context.domain_name, None), + event.resolve_service_name( + &generic_service_mapping, + &event.request_context.domain_name, + "api_gateway_rest" + ), "generic-service" ); } diff --git a/bottlecap/src/lifecycle/invocation/triggers/api_gateway_websocket_event.rs b/bottlecap/src/lifecycle/invocation/triggers/api_gateway_websocket_event.rs index 21c341bee..e178df246 100644 --- a/bottlecap/src/lifecycle/invocation/triggers/api_gateway_websocket_event.rs +++ b/bottlecap/src/lifecycle/invocation/triggers/api_gateway_websocket_event.rs @@ -71,8 +71,11 @@ impl Trigger for APIGatewayWebSocketEvent { ); let start_time = (self.request_context.time_epoch as f64 * MS_TO_NS) as i64; - let service_name = - self.resolve_service_name(service_mapping, &self.request_context.domain_name, None); + let service_name = self.resolve_service_name( + service_mapping, + &self.request_context.domain_name, + "api_gateway_websocket", + ); span.name = "aws.apigateway".to_string(); span.service = service_name; @@ -363,7 +366,7 @@ mod tests { event.resolve_service_name( &specific_service_mapping, &event.request_context.domain_name, - None + "api_gateway_websocket" ), "specific-service" ); @@ -374,8 +377,11 @@ mod tests { )]); assert_eq!( - event - .resolve_service_name(&generic_service_mapping, &event.request_context.domain_name, None), + event.resolve_service_name( + &generic_service_mapping, + &event.request_context.domain_name, + "api_gateway_websocket" + ), "generic-service" ); } diff --git a/bottlecap/src/lifecycle/invocation/triggers/dynamodb_event.rs b/bottlecap/src/lifecycle/invocation/triggers/dynamodb_event.rs index 10e911cff..20ab0242f 100644 --- a/bottlecap/src/lifecycle/invocation/triggers/dynamodb_event.rs +++ b/bottlecap/src/lifecycle/invocation/triggers/dynamodb_event.rs @@ -279,7 +279,7 @@ mod tests { let service_mapping = HashMap::new(); event.enrich_span(&mut span, &service_mapping); assert_eq!(span.name, "aws.dynamodb"); - assert_eq!(span.service, "dynamodb"); + assert_eq!(span.service, "ExampleTableWithStream"); assert_eq!(span.resource, "INSERT ExampleTableWithStream"); assert_eq!(span.r#type, "web"); @@ -355,14 +355,22 @@ mod tests { ]); assert_eq!( - event.resolve_service_name(&specific_service_mapping, &event.get_specific_identifier(), "dynamodb"), + event.resolve_service_name( + &specific_service_mapping, + &event.get_specific_identifier(), + "dynamodb" + ), "specific-service" ); let generic_service_mapping = HashMap::from([("lambda_dynamodb".to_string(), "generic-service".to_string())]); assert_eq!( - event.resolve_service_name(&generic_service_mapping, &event.get_specific_identifier(), "dynamodb"), + event.resolve_service_name( + &generic_service_mapping, + &event.get_specific_identifier(), + "dynamodb" + ), "generic-service" ); } diff --git a/bottlecap/src/lifecycle/invocation/triggers/event_bridge_event.rs b/bottlecap/src/lifecycle/invocation/triggers/event_bridge_event.rs index 723bde2b8..06cf29361 100644 --- a/bottlecap/src/lifecycle/invocation/triggers/event_bridge_event.rs +++ b/bottlecap/src/lifecycle/invocation/triggers/event_bridge_event.rs @@ -65,7 +65,11 @@ impl Trigger for EventBridgeEvent { .and_then(|s| s.parse::().ok()) .map_or(start_time_seconds, |s| (s * MS_TO_NS) as i64); - let service_name = self.resolve_service_name(service_mapping, &self.get_specific_identifier(), "eventbridge"); + let service_name = self.resolve_service_name( + service_mapping, + &self.get_specific_identifier(), + "eventbridge", + ); span.name = String::from("aws.eventbridge"); span.service = service_name.to_string(); @@ -271,7 +275,11 @@ mod tests { ]); assert_eq!( - event.resolve_service_name(&specific_service_mapping, &event.get_specific_identifier(), "eventbridge"), + event.resolve_service_name( + &specific_service_mapping, + &event.get_specific_identifier(), + "eventbridge" + ), "specific-service" ); @@ -280,7 +288,11 @@ mod tests { "generic-service".to_string(), )]); assert_eq!( - event.resolve_service_name(&generic_service_mapping, &event.get_specific_identifier(), "eventbridge"), + event.resolve_service_name( + &generic_service_mapping, + &event.get_specific_identifier(), + "eventbridge" + ), "generic-service" ); } diff --git a/bottlecap/src/lifecycle/invocation/triggers/kinesis_event.rs b/bottlecap/src/lifecycle/invocation/triggers/kinesis_event.rs index 665dfa343..ada1a5d1e 100644 --- a/bottlecap/src/lifecycle/invocation/triggers/kinesis_event.rs +++ b/bottlecap/src/lifecycle/invocation/triggers/kinesis_event.rs @@ -193,7 +193,7 @@ mod tests { let service_mapping = HashMap::new(); event.enrich_span(&mut span, &service_mapping); assert_eq!(span.name, "aws.kinesis"); - assert_eq!(span.service, "kinesis"); + assert_eq!(span.service, "kinesisStream"); assert_eq!(span.resource, "kinesisStream"); assert_eq!(span.r#type, "web"); @@ -280,14 +280,22 @@ mod tests { ]); assert_eq!( - event.resolve_service_name(&specific_service_mapping, &event.get_specific_identifier(), "kinesis"), + event.resolve_service_name( + &specific_service_mapping, + &event.get_specific_identifier(), + "kinesis" + ), "specific-service" ); let generic_service_mapping = HashMap::from([("lambda_kinesis".to_string(), "generic-service".to_string())]); assert_eq!( - event.resolve_service_name(&generic_service_mapping, &event.get_specific_identifier(), "kinesis"), + event.resolve_service_name( + &generic_service_mapping, + &event.get_specific_identifier(), + "kinesis" + ), "generic-service" ); } diff --git a/bottlecap/src/lifecycle/invocation/triggers/lambda_function_url_event.rs b/bottlecap/src/lifecycle/invocation/triggers/lambda_function_url_event.rs index 7c670d0fc..2f35d3056 100644 --- a/bottlecap/src/lifecycle/invocation/triggers/lambda_function_url_event.rs +++ b/bottlecap/src/lifecycle/invocation/triggers/lambda_function_url_event.rs @@ -77,8 +77,11 @@ impl Trigger for LambdaFunctionUrlEvent { let start_time = (self.request_context.time_epoch as f64 * MS_TO_NS) as i64; - let service_name = - self.resolve_service_name(service_mapping, &self.request_context.domain_name, None); + let service_name = self.resolve_service_name( + service_mapping, + &self.request_context.domain_name, + "lambda_url", + ); span.name = String::from("aws.lambda.url"); span.service = service_name; @@ -336,14 +339,14 @@ mod tests { ]); assert_eq!( - event.resolve_service_name(&specific_service_mapping, "domain-name", None), + event.resolve_service_name(&specific_service_mapping, "domain-name", "lambda_url"), "specific-service" ); let generic_service_mapping = HashMap::from([("lambda_url".to_string(), "generic-service".to_string())]); assert_eq!( - event.resolve_service_name(&generic_service_mapping, "domain-name", None), + event.resolve_service_name(&generic_service_mapping, "domain-name", "lambda_url"), "generic-service" ); } diff --git a/bottlecap/src/lifecycle/invocation/triggers/mod.rs b/bottlecap/src/lifecycle/invocation/triggers/mod.rs index 70d39d7f7..f7d04e70b 100644 --- a/bottlecap/src/lifecycle/invocation/triggers/mod.rs +++ b/bottlecap/src/lifecycle/invocation/triggers/mod.rs @@ -114,19 +114,19 @@ pub trait Trigger: ServiceNameResolver { &self, service_mapping: &HashMap, instance_name: &str, - fallback: Option<&str>, + fallback: &str, ) -> String { service_mapping .get(&self.get_specific_identifier()) .or_else(|| service_mapping.get(self.get_generic_identifier())) + .cloned() .unwrap_or_else(|| { if !instance_name.is_empty() { instance_name.to_string() } else { - fallback.unwrap_or_default().to_string() + fallback.to_string() } }) - .to_string() } } diff --git a/bottlecap/src/lifecycle/invocation/triggers/msk_event.rs b/bottlecap/src/lifecycle/invocation/triggers/msk_event.rs index 20b3c67d2..1075c12bb 100644 --- a/bottlecap/src/lifecycle/invocation/triggers/msk_event.rs +++ b/bottlecap/src/lifecycle/invocation/triggers/msk_event.rs @@ -63,7 +63,8 @@ impl Trigger for MSKEvent { debug!("Enriching an Inferred Span for an MSK event"); span.name = String::from("aws.msk"); - span.service = self.resolve_service_name(service_mapping, &self.get_specific_identifier(), "msk"); + span.service = + self.resolve_service_name(service_mapping, &self.get_specific_identifier(), "msk"); span.r#type = String::from("web"); let first_value = self.records.values().find_map(|arr| arr.first()); @@ -170,7 +171,7 @@ mod tests { event.enrich_span(&mut span, &service_mapping); assert_eq!(span.name, "aws.msk"); - assert_eq!(span.service, "msk"); + assert_eq!(span.service, "demo-cluster"); assert_eq!(span.r#type, "web"); assert_eq!(span.resource, "topic1"); assert_eq!(span.start, 1745846213022000128); @@ -241,14 +242,22 @@ mod tests { ]); assert_eq!( - event.resolve_service_name(&specific_service_mapping, &event.get_specific_identifier(), "msk"), + event.resolve_service_name( + &specific_service_mapping, + &event.get_specific_identifier(), + "msk" + ), "specific-service" ); let generic_service_mapping = HashMap::from([("lambda_msk".to_string(), "generic-service".to_string())]); assert_eq!( - event.resolve_service_name(&generic_service_mapping, &event.get_specific_identifier(), "msk"), + event.resolve_service_name( + &generic_service_mapping, + &event.get_specific_identifier(), + "msk" + ), "generic-service" ); } diff --git a/bottlecap/src/lifecycle/invocation/triggers/s3_event.rs b/bottlecap/src/lifecycle/invocation/triggers/s3_event.rs index aa54b5bea..458bb0378 100644 --- a/bottlecap/src/lifecycle/invocation/triggers/s3_event.rs +++ b/bottlecap/src/lifecycle/invocation/triggers/s3_event.rs @@ -214,7 +214,7 @@ mod tests { let service_mapping = HashMap::new(); event.enrich_span(&mut span, &service_mapping); assert_eq!(span.name, "aws.s3"); - assert_eq!(span.service, "s3"); + assert_eq!(span.service, "example-bucket"); assert_eq!(span.resource, "example-bucket"); assert_eq!(span.r#type, "web"); @@ -286,14 +286,22 @@ mod tests { ]); assert_eq!( - event.resolve_service_name(&specific_service_mapping, &event.get_specific_identifier(), "s3"), + event.resolve_service_name( + &specific_service_mapping, + &event.get_specific_identifier(), + "s3" + ), "specific-service" ); let generic_service_mapping = HashMap::from([("lambda_s3".to_string(), "generic-service".to_string())]); assert_eq!( - event.resolve_service_name(&generic_service_mapping, &event.get_specific_identifier(), "s3"), + event.resolve_service_name( + &generic_service_mapping, + &event.get_specific_identifier(), + "s3" + ), "generic-service" ); } diff --git a/bottlecap/src/lifecycle/invocation/triggers/sns_event.rs b/bottlecap/src/lifecycle/invocation/triggers/sns_event.rs index aa0dec255..ecb2c50d2 100644 --- a/bottlecap/src/lifecycle/invocation/triggers/sns_event.rs +++ b/bottlecap/src/lifecycle/invocation/triggers/sns_event.rs @@ -93,7 +93,8 @@ impl Trigger for SnsRecord { .timestamp_nanos_opt() .unwrap_or((self.sns.timestamp.timestamp_millis() as f64 * MS_TO_NS) as i64); - let service_name = self.resolve_service_name(service_mapping, &self.get_specific_identifier(), "sns"); + let service_name = + self.resolve_service_name(service_mapping, &self.get_specific_identifier(), "sns"); span.name = "aws.sns".to_string(); span.service = service_name.to_string(); @@ -237,7 +238,7 @@ mod tests { let service_mapping = HashMap::new(); event.enrich_span(&mut span, &service_mapping); assert_eq!(span.name, "aws.sns"); - assert_eq!(span.service, "sns"); + assert_eq!(span.service, "serverlessTracingTopicPy"); assert_eq!(span.resource, "serverlessTracingTopicPy"); assert_eq!(span.r#type, "web"); @@ -369,14 +370,22 @@ mod tests { ]); assert_eq!( - event.resolve_service_name(&specific_service_mapping, &event.get_specific_identifier(), "sns"), + event.resolve_service_name( + &specific_service_mapping, + &event.get_specific_identifier(), + "sns" + ), "specific-service" ); let generic_service_mapping = HashMap::from([("lambda_sns".to_string(), "generic-service".to_string())]); assert_eq!( - event.resolve_service_name(&generic_service_mapping, &event.get_specific_identifier(), "sns"), + event.resolve_service_name( + &generic_service_mapping, + &event.get_specific_identifier(), + "sns" + ), "generic-service" ); } diff --git a/bottlecap/src/lifecycle/invocation/triggers/sqs_event.rs b/bottlecap/src/lifecycle/invocation/triggers/sqs_event.rs index 8a4bd802f..2d646f88b 100644 --- a/bottlecap/src/lifecycle/invocation/triggers/sqs_event.rs +++ b/bottlecap/src/lifecycle/invocation/triggers/sqs_event.rs @@ -110,7 +110,8 @@ impl Trigger for SqsRecord { .unwrap_or_default() as f64 * MS_TO_NS) as i64; - let service_name = self.resolve_service_name(service_mapping, &self.get_specific_identifier(), "sqs"); + let service_name = + self.resolve_service_name(service_mapping, &self.get_specific_identifier(), "sqs"); span.name = "aws.sqs".to_string(); span.service = service_name.to_string(); @@ -329,7 +330,7 @@ mod tests { let service_mapping = HashMap::new(); event.enrich_span(&mut span, &service_mapping); assert_eq!(span.name, "aws.sqs"); - assert_eq!(span.service, "sqs"); + assert_eq!(span.service, "MyQueue"); assert_eq!(span.resource, "MyQueue"); assert_eq!(span.r#type, "web"); @@ -506,14 +507,22 @@ mod tests { ]); assert_eq!( - event.resolve_service_name(&specific_service_mapping, &event.get_specific_identifier(), "sqs"), + event.resolve_service_name( + &specific_service_mapping, + &event.get_specific_identifier(), + "sqs" + ), "specific-service" ); let generic_service_mapping = HashMap::from([("lambda_sqs".to_string(), "generic-service".to_string())]); assert_eq!( - event.resolve_service_name(&generic_service_mapping, &event.get_specific_identifier(), "sqs"), + event.resolve_service_name( + &generic_service_mapping, + &event.get_specific_identifier(), + "sqs" + ), "generic-service" ); } diff --git a/bottlecap/tests/payloads/eventbridge_span.json b/bottlecap/tests/payloads/eventbridge_span.json index 0515abd69..9d74502c2 100644 --- a/bottlecap/tests/payloads/eventbridge_span.json +++ b/bottlecap/tests/payloads/eventbridge_span.json @@ -1,5 +1,5 @@ { - "service": "eventbridge", + "service": "testBus", "name": "aws.eventbridge", "resource": "testBus", "trace_id": 0, From e57d71ec967338ebaf98baf1fbb3a3d3a9185a56 Mon Sep 17 00:00:00 2001 From: Zarir Hamza Date: Fri, 18 Jul 2025 02:22:55 -0400 Subject: [PATCH 05/15] linter --- bottlecap/src/lifecycle/invocation/triggers/mod.rs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/bottlecap/src/lifecycle/invocation/triggers/mod.rs b/bottlecap/src/lifecycle/invocation/triggers/mod.rs index f7d04e70b..debed1391 100644 --- a/bottlecap/src/lifecycle/invocation/triggers/mod.rs +++ b/bottlecap/src/lifecycle/invocation/triggers/mod.rs @@ -121,10 +121,10 @@ pub trait Trigger: ServiceNameResolver { .or_else(|| service_mapping.get(self.get_generic_identifier())) .cloned() .unwrap_or_else(|| { - if !instance_name.is_empty() { - instance_name.to_string() - } else { + if instance_name.is_empty() { fallback.to_string() + } else { + instance_name.to_string() } }) } From b2a32072bf6dcf7a667d81aadabed6c973b14634 Mon Sep 17 00:00:00 2001 From: Zarir Hamza Date: Fri, 18 Jul 2025 02:41:06 -0400 Subject: [PATCH 06/15] add tests --- bottlecap/tests/service_naming_test.rs | 82 ++++++++++++++++++++++++++ 1 file changed, 82 insertions(+) create mode 100644 bottlecap/tests/service_naming_test.rs diff --git a/bottlecap/tests/service_naming_test.rs b/bottlecap/tests/service_naming_test.rs new file mode 100644 index 000000000..79e0eea1f --- /dev/null +++ b/bottlecap/tests/service_naming_test.rs @@ -0,0 +1,82 @@ +use std::collections::HashMap; + +use bottlecap::lifecycle::invocation::triggers::{ + dynamodb_event::DynamoDbRecord, kinesis_event::KinesisRecord, msk_event::MSKEvent, + s3_event::S3Record, sns_event::SnsRecord, sqs_event::SqsRecord, Trigger, +}; + +use bottlecap::lifecycle::invocation::triggers::ServiceNameResolver; + +/// Small helper for integration tests: loads a payload JSON file from +/// `bottlecap/tests/payloads/` and returns its content as `String`. +fn read_json_file(file_name: &str) -> String { + use std::fs; + use std::path::PathBuf; + + // `CARGO_MANIFEST_DIR` points at the `bottlecap` crate root when this + // integration test is compiled. + let mut path = PathBuf::from(env!("CARGO_MANIFEST_DIR")); + path.push("tests/payloads"); + path.push(file_name); + fs::read_to_string(path).expect("Failed to read test payload file") +} + +#[test] +fn test_dynamodb_service_name_instance_fallback() { + let json = read_json_file("dynamodb_event.json"); + let payload = serde_json::from_str(&json).expect("Failed to deserialize"); + let event = DynamoDbRecord::new(payload).expect("deserialize DynamoDbRecord"); + let table_name = event.get_specific_identifier(); + let service = event.resolve_service_name(&HashMap::new(), &table_name, "dynamodb"); + assert_eq!(service, "ExampleTableWithStream"); +} + +#[test] +fn test_s3_service_name_instance_fallback() { + let json = read_json_file("s3_event.json"); + let payload = serde_json::from_str(&json).expect("Failed to deserialize"); + let event = S3Record::new(payload).expect("deserialize S3Record"); + let bucket_name = event.get_specific_identifier(); + let service = event.resolve_service_name(&HashMap::new(), &bucket_name, "s3"); + assert_eq!(service, "example-bucket"); +} + +#[test] +fn test_sqs_service_name_instance_fallback() { + let json = read_json_file("sqs_event.json"); + let payload = serde_json::from_str(&json).expect("Failed to deserialize"); + let event = SqsRecord::new(payload).expect("deserialize SqsRecord"); + let queue_name = event.get_specific_identifier(); + let service = event.resolve_service_name(&HashMap::new(), &queue_name, "sqs"); + assert_eq!(service, "MyQueue"); +} + +#[test] +fn test_kinesis_service_name_instance_fallback() { + let json = read_json_file("kinesis_event.json"); + let payload = serde_json::from_str(&json).expect("Failed to deserialize"); + let event = KinesisRecord::new(payload).expect("deserialize KinesisRecord"); + let stream_name = event.get_specific_identifier(); + let service = event.resolve_service_name(&HashMap::new(), &stream_name, "kinesis"); + assert_eq!(service, "kinesisStream"); +} + +#[test] +fn test_msk_service_name_instance_fallback() { + let json = read_json_file("msk_event.json"); + let payload = serde_json::from_str(&json).expect("Failed to deserialize"); + let event = MSKEvent::new(payload).expect("deserialize MSKEvent"); + let cluster_name = event.get_specific_identifier(); + let service = event.resolve_service_name(&HashMap::new(), &cluster_name, "msk"); + assert_eq!(service, "demo-cluster"); +} + +#[test] +fn test_sns_service_name_instance_fallback() { + let json = read_json_file("sns_event.json"); + let payload = serde_json::from_str(&json).expect("Failed to deserialize"); + let event = SnsRecord::new(payload).expect("deserialize SnsRecord"); + let topic_name = event.get_specific_identifier(); + let service = event.resolve_service_name(&HashMap::new(), &topic_name, "sns"); + assert_eq!(service, "serverlessTracingTopicPy"); +} From 37191f6057ddf19b2ea284e6d9035bbfd86e8511 Mon Sep 17 00:00:00 2001 From: Zarir Hamza Date: Fri, 18 Jul 2025 17:16:44 -0400 Subject: [PATCH 07/15] add span.kind for lambda --- bottlecap/src/lifecycle/invocation/processor.rs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/bottlecap/src/lifecycle/invocation/processor.rs b/bottlecap/src/lifecycle/invocation/processor.rs index 51553939e..d256970ff 100644 --- a/bottlecap/src/lifecycle/invocation/processor.rs +++ b/bottlecap/src/lifecycle/invocation/processor.rs @@ -194,6 +194,9 @@ impl Processor { self.dynamic_tags .insert(String::from("cold_start"), cold_start.to_string()); + self.dynamic_tags + .insert(String::from("span.kind"), "server".to_string()); + if proactive_initialization { self.dynamic_tags.insert( String::from("proactive_initialization"), From 984c84b7be1f764ee1ceb3a827b6c52c3f35d20d Mon Sep 17 00:00:00 2001 From: Zarir Hamza Date: Fri, 18 Jul 2025 19:32:42 -0400 Subject: [PATCH 08/15] remove service overrides for synthetic spans --- bottlecap/src/traces/trace_processor.rs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/bottlecap/src/traces/trace_processor.rs b/bottlecap/src/traces/trace_processor.rs index ca65bb405..14dc3032b 100644 --- a/bottlecap/src/traces/trace_processor.rs +++ b/bottlecap/src/traces/trace_processor.rs @@ -52,6 +52,9 @@ impl TraceChunkProcessor for ChunkProcessor { } } + // Remove the _dd.base_service tag for unintentional service name override + span.meta.remove("_dd.base_service"); + self.tags_provider.get_tags_map().iter().for_each(|(k, v)| { span.meta.insert(k.clone(), v.clone()); }); From 49ce25583027f5b139a30c6f3e035d8f557090d9 Mon Sep 17 00:00:00 2001 From: Zarir Hamza Date: Tue, 5 Aug 2025 20:10:23 -0400 Subject: [PATCH 09/15] add env var --- .../src/lifecycle/invocation/processor.rs | 38 +++++++++- .../src/lifecycle/invocation/triggers/mod.rs | 9 +++ bottlecap/tests/service_naming_test.rs | 73 ++++++++++++++++++- 3 files changed, 117 insertions(+), 3 deletions(-) diff --git a/bottlecap/src/lifecycle/invocation/processor.rs b/bottlecap/src/lifecycle/invocation/processor.rs index d256970ff..155a3fb32 100644 --- a/bottlecap/src/lifecycle/invocation/processor.rs +++ b/bottlecap/src/lifecycle/invocation/processor.rs @@ -93,7 +93,10 @@ impl Processor { .get_canonical_resource_name() .unwrap_or(String::from("aws.lambda")); - let service = config.service.clone().unwrap_or(resource.clone()); + let service = match std::env::var("DD_TRACE_AWS_SERVICE_REPRESENTATION_ENABLED") { + Ok(val) if val.eq_ignore_ascii_case("false") => String::from("aws.lambda"), + _ => config.service.clone().unwrap_or(resource.clone()), + }; let propagator = DatadogCompositePropagator::new(Arc::clone(&config)); Processor { @@ -968,6 +971,7 @@ mod tests { use base64::{engine::general_purpose::STANDARD, Engine}; use dogstatsd::aggregator::Aggregator; use dogstatsd::metric::EMPTY_TAGS; + use serial_test::serial; fn setup() -> Processor { let aws_config = Arc::new(AwsConfig { @@ -1174,4 +1178,36 @@ mod tests { "200" ); } + + #[test] + #[serial] + fn test_service_name_env_var_false_returns_aws_lambda() { + const VAR: &str = "DD_TRACE_AWS_SERVICE_REPRESENTATION_ENABLED"; + let original = std::env::var(VAR).ok(); + std::env::set_var(VAR, "false"); + + let p = setup(); + assert_eq!(p.service, "aws.lambda"); + + if let Some(v) = original { + std::env::set_var(VAR, v); + } else { + std::env::remove_var(VAR); + } + } + + #[test] + #[serial] + fn test_service_name_env_var_unset_uses_config_service() { + const VAR: &str = "DD_TRACE_AWS_SERVICE_REPRESENTATION_ENABLED"; + let original = std::env::var(VAR).ok(); + std::env::remove_var(VAR); + + let p = setup(); + assert_eq!(p.service, "test-service"); + + if let Some(v) = original { + std::env::set_var(VAR, v); + } + } } diff --git a/bottlecap/src/lifecycle/invocation/triggers/mod.rs b/bottlecap/src/lifecycle/invocation/triggers/mod.rs index debed1391..6c01bb50e 100644 --- a/bottlecap/src/lifecycle/invocation/triggers/mod.rs +++ b/bottlecap/src/lifecycle/invocation/triggers/mod.rs @@ -116,6 +116,15 @@ pub trait Trigger: ServiceNameResolver { instance_name: &str, fallback: &str, ) -> String { + // If DD_TRACE_AWS_SERVICE_REPRESENTATION_ENABLED is explicitly set to "false", + // always use the fallback value (typically "aws.lambda"). + if std::env::var("DD_TRACE_AWS_SERVICE_REPRESENTATION_ENABLED") + .map(|v| v.eq_ignore_ascii_case("false")) + .unwrap_or(false) + { + return fallback.to_string(); + } + service_mapping .get(&self.get_specific_identifier()) .or_else(|| service_mapping.get(self.get_generic_identifier())) diff --git a/bottlecap/tests/service_naming_test.rs b/bottlecap/tests/service_naming_test.rs index 79e0eea1f..9a1d17264 100644 --- a/bottlecap/tests/service_naming_test.rs +++ b/bottlecap/tests/service_naming_test.rs @@ -1,8 +1,9 @@ use std::collections::HashMap; use bottlecap::lifecycle::invocation::triggers::{ - dynamodb_event::DynamoDbRecord, kinesis_event::KinesisRecord, msk_event::MSKEvent, - s3_event::S3Record, sns_event::SnsRecord, sqs_event::SqsRecord, Trigger, + dynamodb_event::DynamoDbRecord, kinesis_event::KinesisRecord, + lambda_function_url_event::LambdaFunctionUrlEvent, msk_event::MSKEvent, s3_event::S3Record, + sns_event::SnsRecord, sqs_event::SqsRecord, Trigger, }; use bottlecap::lifecycle::invocation::triggers::ServiceNameResolver; @@ -22,7 +23,9 @@ fn read_json_file(file_name: &str) -> String { } #[test] +#[serial] fn test_dynamodb_service_name_instance_fallback() { + std::env::remove_var("DD_TRACE_AWS_SERVICE_REPRESENTATION_ENABLED"); let json = read_json_file("dynamodb_event.json"); let payload = serde_json::from_str(&json).expect("Failed to deserialize"); let event = DynamoDbRecord::new(payload).expect("deserialize DynamoDbRecord"); @@ -32,7 +35,9 @@ fn test_dynamodb_service_name_instance_fallback() { } #[test] +#[serial] fn test_s3_service_name_instance_fallback() { + std::env::remove_var("DD_TRACE_AWS_SERVICE_REPRESENTATION_ENABLED"); let json = read_json_file("s3_event.json"); let payload = serde_json::from_str(&json).expect("Failed to deserialize"); let event = S3Record::new(payload).expect("deserialize S3Record"); @@ -42,7 +47,9 @@ fn test_s3_service_name_instance_fallback() { } #[test] +#[serial] fn test_sqs_service_name_instance_fallback() { + std::env::remove_var("DD_TRACE_AWS_SERVICE_REPRESENTATION_ENABLED"); let json = read_json_file("sqs_event.json"); let payload = serde_json::from_str(&json).expect("Failed to deserialize"); let event = SqsRecord::new(payload).expect("deserialize SqsRecord"); @@ -52,7 +59,9 @@ fn test_sqs_service_name_instance_fallback() { } #[test] +#[serial] fn test_kinesis_service_name_instance_fallback() { + std::env::remove_var("DD_TRACE_AWS_SERVICE_REPRESENTATION_ENABLED"); let json = read_json_file("kinesis_event.json"); let payload = serde_json::from_str(&json).expect("Failed to deserialize"); let event = KinesisRecord::new(payload).expect("deserialize KinesisRecord"); @@ -62,7 +71,9 @@ fn test_kinesis_service_name_instance_fallback() { } #[test] +#[serial] fn test_msk_service_name_instance_fallback() { + std::env::remove_var("DD_TRACE_AWS_SERVICE_REPRESENTATION_ENABLED"); let json = read_json_file("msk_event.json"); let payload = serde_json::from_str(&json).expect("Failed to deserialize"); let event = MSKEvent::new(payload).expect("deserialize MSKEvent"); @@ -72,7 +83,9 @@ fn test_msk_service_name_instance_fallback() { } #[test] +#[serial] fn test_sns_service_name_instance_fallback() { + std::env::remove_var("DD_TRACE_AWS_SERVICE_REPRESENTATION_ENABLED"); let json = read_json_file("sns_event.json"); let payload = serde_json::from_str(&json).expect("Failed to deserialize"); let event = SnsRecord::new(payload).expect("deserialize SnsRecord"); @@ -80,3 +93,59 @@ fn test_sns_service_name_instance_fallback() { let service = event.resolve_service_name(&HashMap::new(), &topic_name, "sns"); assert_eq!(service, "serverlessTracingTopicPy"); } + +use serial_test::serial; +use std::env; + +fn with_env_var(f: F) { + // Helper to set env var to "false" and restore original value after running closure + const VAR: &str = "DD_TRACE_AWS_SERVICE_REPRESENTATION_ENABLED"; + let original = env::var(VAR).ok(); + env::set_var(VAR, "false"); + f(); + if let Some(val) = original { + env::set_var(VAR, val); + } else { + env::remove_var(VAR); + } +} + +#[test] +#[serial] +fn test_dynamodb_service_name_env_var_false_uses_fallback() { + with_env_var(|| { + let json = read_json_file("dynamodb_event.json"); + let payload = serde_json::from_str(&json).expect("Failed to deserialize"); + let event = DynamoDbRecord::new(payload).expect("deserialize DynamoDbRecord"); + let table_name = event.get_specific_identifier(); + let service = event.resolve_service_name(&HashMap::new(), &table_name, "dynamodb"); + assert_eq!(service, "dynamodb"); + }); +} + +#[test] +#[serial] +fn test_s3_service_name_env_var_false_uses_fallback() { + with_env_var(|| { + let json = read_json_file("s3_event.json"); + let payload = serde_json::from_str(&json).expect("Failed to deserialize"); + let event = S3Record::new(payload).expect("deserialize S3Record"); + let bucket_name = event.get_specific_identifier(); + let service = event.resolve_service_name(&HashMap::new(), &bucket_name, "s3"); + assert_eq!(service, "s3"); + }); +} + +#[test] +#[serial] +fn test_lambda_url_service_name_env_var_false_uses_fallback() { + with_env_var(|| { + let json = read_json_file("lambda_function_url_event.json"); + let payload = serde_json::from_str(&json).expect("Failed to deserialize"); + let event = + LambdaFunctionUrlEvent::new(payload).expect("deserialize LambdaFunctionUrlEvent"); + let domain = event.get_specific_identifier(); + let service = event.resolve_service_name(&HashMap::new(), &domain, "lambda_url"); + assert_eq!(service, "lambda_url"); + }); +} From 3ebab6fbf95340aa5bea0d3ef3cfcfa8504c7bc8 Mon Sep 17 00:00:00 2001 From: Zarir Hamza Date: Tue, 5 Aug 2025 20:47:07 -0400 Subject: [PATCH 10/15] fix tests --- .../src/lifecycle/invocation/processor.rs | 10 +++++----- bottlecap/tests/service_naming_test.rs | 18 +++++++++--------- 2 files changed, 14 insertions(+), 14 deletions(-) diff --git a/bottlecap/src/lifecycle/invocation/processor.rs b/bottlecap/src/lifecycle/invocation/processor.rs index f5cb71feb..457871322 100644 --- a/bottlecap/src/lifecycle/invocation/processor.rs +++ b/bottlecap/src/lifecycle/invocation/processor.rs @@ -1224,15 +1224,15 @@ mod tests { fn test_service_name_env_var_false_returns_aws_lambda() { const VAR: &str = "DD_TRACE_AWS_SERVICE_REPRESENTATION_ENABLED"; let original = std::env::var(VAR).ok(); - std::env::set_var(VAR, "false"); + unsafe { std::env::set_var(VAR, "false") }; let p = setup(); assert_eq!(p.service, "aws.lambda"); if let Some(v) = original { - std::env::set_var(VAR, v); + unsafe { std::env::set_var(VAR, v) }; } else { - std::env::remove_var(VAR); + unsafe { std::env::remove_var(VAR) }; } } @@ -1241,13 +1241,13 @@ mod tests { fn test_service_name_env_var_unset_uses_config_service() { const VAR: &str = "DD_TRACE_AWS_SERVICE_REPRESENTATION_ENABLED"; let original = std::env::var(VAR).ok(); - std::env::remove_var(VAR); + unsafe { std::env::remove_var(VAR) }; let p = setup(); assert_eq!(p.service, "test-service"); if let Some(v) = original { - std::env::set_var(VAR, v); + unsafe { std::env::set_var(VAR, v) }; } } } diff --git a/bottlecap/tests/service_naming_test.rs b/bottlecap/tests/service_naming_test.rs index 9a1d17264..359fb2d72 100644 --- a/bottlecap/tests/service_naming_test.rs +++ b/bottlecap/tests/service_naming_test.rs @@ -25,7 +25,7 @@ fn read_json_file(file_name: &str) -> String { #[test] #[serial] fn test_dynamodb_service_name_instance_fallback() { - std::env::remove_var("DD_TRACE_AWS_SERVICE_REPRESENTATION_ENABLED"); + unsafe { std::env::remove_var("DD_TRACE_AWS_SERVICE_REPRESENTATION_ENABLED") }; let json = read_json_file("dynamodb_event.json"); let payload = serde_json::from_str(&json).expect("Failed to deserialize"); let event = DynamoDbRecord::new(payload).expect("deserialize DynamoDbRecord"); @@ -37,7 +37,7 @@ fn test_dynamodb_service_name_instance_fallback() { #[test] #[serial] fn test_s3_service_name_instance_fallback() { - std::env::remove_var("DD_TRACE_AWS_SERVICE_REPRESENTATION_ENABLED"); + unsafe { std::env::remove_var("DD_TRACE_AWS_SERVICE_REPRESENTATION_ENABLED") }; let json = read_json_file("s3_event.json"); let payload = serde_json::from_str(&json).expect("Failed to deserialize"); let event = S3Record::new(payload).expect("deserialize S3Record"); @@ -49,7 +49,7 @@ fn test_s3_service_name_instance_fallback() { #[test] #[serial] fn test_sqs_service_name_instance_fallback() { - std::env::remove_var("DD_TRACE_AWS_SERVICE_REPRESENTATION_ENABLED"); + unsafe { std::env::remove_var("DD_TRACE_AWS_SERVICE_REPRESENTATION_ENABLED") }; let json = read_json_file("sqs_event.json"); let payload = serde_json::from_str(&json).expect("Failed to deserialize"); let event = SqsRecord::new(payload).expect("deserialize SqsRecord"); @@ -61,7 +61,7 @@ fn test_sqs_service_name_instance_fallback() { #[test] #[serial] fn test_kinesis_service_name_instance_fallback() { - std::env::remove_var("DD_TRACE_AWS_SERVICE_REPRESENTATION_ENABLED"); + unsafe { std::env::remove_var("DD_TRACE_AWS_SERVICE_REPRESENTATION_ENABLED") }; let json = read_json_file("kinesis_event.json"); let payload = serde_json::from_str(&json).expect("Failed to deserialize"); let event = KinesisRecord::new(payload).expect("deserialize KinesisRecord"); @@ -73,7 +73,7 @@ fn test_kinesis_service_name_instance_fallback() { #[test] #[serial] fn test_msk_service_name_instance_fallback() { - std::env::remove_var("DD_TRACE_AWS_SERVICE_REPRESENTATION_ENABLED"); + unsafe { std::env::remove_var("DD_TRACE_AWS_SERVICE_REPRESENTATION_ENABLED") }; let json = read_json_file("msk_event.json"); let payload = serde_json::from_str(&json).expect("Failed to deserialize"); let event = MSKEvent::new(payload).expect("deserialize MSKEvent"); @@ -85,7 +85,7 @@ fn test_msk_service_name_instance_fallback() { #[test] #[serial] fn test_sns_service_name_instance_fallback() { - std::env::remove_var("DD_TRACE_AWS_SERVICE_REPRESENTATION_ENABLED"); + unsafe { std::env::remove_var("DD_TRACE_AWS_SERVICE_REPRESENTATION_ENABLED") }; let json = read_json_file("sns_event.json"); let payload = serde_json::from_str(&json).expect("Failed to deserialize"); let event = SnsRecord::new(payload).expect("deserialize SnsRecord"); @@ -101,12 +101,12 @@ fn with_env_var(f: F) { // Helper to set env var to "false" and restore original value after running closure const VAR: &str = "DD_TRACE_AWS_SERVICE_REPRESENTATION_ENABLED"; let original = env::var(VAR).ok(); - env::set_var(VAR, "false"); + unsafe { env::set_var(VAR, "false") }; f(); if let Some(val) = original { - env::set_var(VAR, val); + unsafe { env::set_var(VAR, val) }; } else { - env::remove_var(VAR); + unsafe { env::remove_var(VAR) }; } } From c7bc91d62eb40725efde2d1bc1e88c4df4f5d9a7 Mon Sep 17 00:00:00 2001 From: Zarir Hamza Date: Tue, 5 Aug 2025 21:58:12 -0400 Subject: [PATCH 11/15] fix to include old behavior for apigw --- .../lifecycle/invocation/triggers/api_gateway_http_event.rs | 2 +- .../lifecycle/invocation/triggers/api_gateway_rest_event.rs | 2 +- .../invocation/triggers/api_gateway_websocket_event.rs | 2 +- .../invocation/triggers/lambda_function_url_event.rs | 2 +- bottlecap/tests/service_naming_test.rs | 4 ++-- 5 files changed, 6 insertions(+), 6 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 bbf51a8f6..93f727d43 100644 --- a/bottlecap/src/lifecycle/invocation/triggers/api_gateway_http_event.rs +++ b/bottlecap/src/lifecycle/invocation/triggers/api_gateway_http_event.rs @@ -81,7 +81,7 @@ impl Trigger for APIGatewayHttpEvent { let service_name = self.resolve_service_name( service_mapping, &self.request_context.domain_name, - "api_gateway_http", + &self.request_context.domain_name, ); span.name = "aws.httpapi".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 cbfd3d762..53f79a273 100644 --- a/bottlecap/src/lifecycle/invocation/triggers/api_gateway_rest_event.rs +++ b/bottlecap/src/lifecycle/invocation/triggers/api_gateway_rest_event.rs @@ -84,7 +84,7 @@ impl Trigger for APIGatewayRestEvent { let service_name = self.resolve_service_name( service_mapping, &self.request_context.domain_name, - "api_gateway_rest", + &self.request_context.domain_name, ); span.name = "aws.apigateway".to_string(); diff --git a/bottlecap/src/lifecycle/invocation/triggers/api_gateway_websocket_event.rs b/bottlecap/src/lifecycle/invocation/triggers/api_gateway_websocket_event.rs index 38e7c3b98..6b192ed19 100644 --- a/bottlecap/src/lifecycle/invocation/triggers/api_gateway_websocket_event.rs +++ b/bottlecap/src/lifecycle/invocation/triggers/api_gateway_websocket_event.rs @@ -366,7 +366,7 @@ mod tests { event.resolve_service_name( &specific_service_mapping, &event.request_context.domain_name, - "api_gateway_websocket" + &event.request_context.domain_name, ), "specific-service" ); diff --git a/bottlecap/src/lifecycle/invocation/triggers/lambda_function_url_event.rs b/bottlecap/src/lifecycle/invocation/triggers/lambda_function_url_event.rs index 75b7e0f38..052f1bb1d 100644 --- a/bottlecap/src/lifecycle/invocation/triggers/lambda_function_url_event.rs +++ b/bottlecap/src/lifecycle/invocation/triggers/lambda_function_url_event.rs @@ -80,7 +80,7 @@ impl Trigger for LambdaFunctionUrlEvent { let service_name = self.resolve_service_name( service_mapping, &self.request_context.domain_name, - "lambda_url", + &self.request_context.domain_name, ); span.name = String::from("aws.lambda.url"); diff --git a/bottlecap/tests/service_naming_test.rs b/bottlecap/tests/service_naming_test.rs index 359fb2d72..b67556506 100644 --- a/bottlecap/tests/service_naming_test.rs +++ b/bottlecap/tests/service_naming_test.rs @@ -1,9 +1,9 @@ use std::collections::HashMap; use bottlecap::lifecycle::invocation::triggers::{ - dynamodb_event::DynamoDbRecord, kinesis_event::KinesisRecord, + Trigger, dynamodb_event::DynamoDbRecord, kinesis_event::KinesisRecord, lambda_function_url_event::LambdaFunctionUrlEvent, msk_event::MSKEvent, s3_event::S3Record, - sns_event::SnsRecord, sqs_event::SqsRecord, Trigger, + sns_event::SnsRecord, sqs_event::SqsRecord, }; use bottlecap::lifecycle::invocation::triggers::ServiceNameResolver; From 130ba0d06ab2bb8e5a67387ddf13bb8b516add0c Mon Sep 17 00:00:00 2001 From: Zarir Hamza Date: Wed, 6 Aug 2025 17:54:46 -0400 Subject: [PATCH 12/15] use config and remove tests --- bottlecap/src/config/env.rs | 8 + bottlecap/src/config/mod.rs | 2 + bottlecap/src/config/yaml.rs | 9 ++ .../src/lifecycle/invocation/processor.rs | 49 ++---- .../src/lifecycle/invocation/span_inferrer.rs | 93 +++++++++-- .../invocation/triggers/alb_event.rs | 14 +- .../triggers/api_gateway_http_event.rs | 18 ++- .../triggers/api_gateway_rest_event.rs | 18 ++- .../triggers/api_gateway_websocket_event.rs | 14 +- .../invocation/triggers/dynamodb_event.rs | 22 ++- .../invocation/triggers/event_bridge_event.rs | 20 ++- .../invocation/triggers/kinesis_event.rs | 22 ++- .../triggers/lambda_function_url_event.rs | 19 ++- .../src/lifecycle/invocation/triggers/mod.rs | 40 +++-- .../invocation/triggers/msk_event.rs | 23 ++- .../lifecycle/invocation/triggers/s3_event.rs | 64 ++++---- .../invocation/triggers/sns_event.rs | 23 ++- .../invocation/triggers/sqs_event.rs | 23 ++- .../triggers/step_function_event.rs | 1 + bottlecap/tests/service_naming_test.rs | 151 ------------------ 20 files changed, 321 insertions(+), 312 deletions(-) delete mode 100644 bottlecap/tests/service_naming_test.rs diff --git a/bottlecap/src/config/env.rs b/bottlecap/src/config/env.rs index ad91db2f2..46b2717f4 100644 --- a/bottlecap/src/config/env.rs +++ b/bottlecap/src/config/env.rs @@ -178,6 +178,11 @@ pub struct EnvConfig { /// #[serde(deserialize_with = "deserialize_additional_endpoints")] pub apm_additional_endpoints: HashMap>, + /// @env `DD_TRACE_AWS_SERVICE_REPRESENTATION_ENABLED` + /// + /// Enable the new AWS-resource naming logic in the tracer. + #[serde(deserialize_with = "deserialize_optional_bool_from_anything")] + pub trace_aws_service_representation_enabled: Option, // // Trace Propagation /// @env `DD_TRACE_PROPAGATION_STYLE` @@ -362,6 +367,7 @@ fn merge_config(config: &mut Config, env_config: &EnvConfig) { merge_option_to_value!(config, env_config, apm_config_compression_level); merge_vec!(config, env_config, apm_features); merge_hashmap!(config, env_config, apm_additional_endpoints); + merge_option_to_value!(config, env_config, trace_aws_service_representation_enabled); // Trace Propagation merge_vec!(config, env_config, trace_propagation_style); @@ -560,6 +566,7 @@ mod tests { jail.set_env("DD_TRACE_PROPAGATION_STYLE_EXTRACT", "b3"); jail.set_env("DD_TRACE_PROPAGATION_EXTRACT_FIRST", "true"); jail.set_env("DD_TRACE_PROPAGATION_HTTP_BAGGAGE_ENABLED", "true"); + jail.set_env("DD_TRACE_AWS_SERVICE_REPRESENTATION_ENABLED", "true"); // OTLP jail.set_env("DD_OTLP_CONFIG_TRACES_ENABLED", "false"); @@ -709,6 +716,7 @@ mod tests { trace_propagation_style_extract: vec![TracePropagationStyle::B3], trace_propagation_extract_first: true, trace_propagation_http_baggage_enabled: true, + trace_aws_service_representation_enabled: true, otlp_config_traces_enabled: false, otlp_config_traces_span_name_as_resource_name: true, otlp_config_traces_span_name_remappings: HashMap::from([( diff --git a/bottlecap/src/config/mod.rs b/bottlecap/src/config/mod.rs index 2f7d43085..3a5f2f845 100644 --- a/bottlecap/src/config/mod.rs +++ b/bottlecap/src/config/mod.rs @@ -283,6 +283,7 @@ pub struct Config { pub trace_propagation_style_extract: Vec, pub trace_propagation_extract_first: bool, pub trace_propagation_http_baggage_enabled: bool, + pub trace_aws_service_representation_enabled: bool, // OTLP // @@ -371,6 +372,7 @@ impl Default for Config { apm_config_compression_level: 6, apm_features: vec![], apm_additional_endpoints: HashMap::new(), + trace_aws_service_representation_enabled: true, trace_propagation_style: vec![ TracePropagationStyle::Datadog, TracePropagationStyle::TraceContext, diff --git a/bottlecap/src/config/yaml.rs b/bottlecap/src/config/yaml.rs index 1045fd660..7620f5ab5 100644 --- a/bottlecap/src/config/yaml.rs +++ b/bottlecap/src/config/yaml.rs @@ -64,6 +64,8 @@ pub struct YamlConfig { pub apm_config: ApmConfig, #[serde(deserialize_with = "deserialize_service_mapping")] pub service_mapping: HashMap, + #[serde(deserialize_with = "deserialize_optional_bool_from_anything")] + pub trace_aws_service_representation_enabled: Option, // Trace Propagation #[serde(deserialize_with = "deserialize_trace_propagation_style")] pub trace_propagation_style: Vec, @@ -451,6 +453,11 @@ fn merge_config(config: &mut Config, yaml_config: &YamlConfig) { merge_vec!(config, yaml_config, trace_propagation_style_extract); merge_option_to_value!(config, yaml_config, trace_propagation_extract_first); merge_option_to_value!(config, yaml_config, trace_propagation_http_baggage_enabled); + merge_option_to_value!( + config, + yaml_config, + trace_aws_service_representation_enabled + ); // OTLP if let Some(otlp_config) = &yaml_config.otlp_config { @@ -711,6 +718,7 @@ trace_propagation_style: "datadog" trace_propagation_style_extract: "b3" trace_propagation_extract_first: true trace_propagation_http_baggage_enabled: true +trace_aws_service_representation_enabled: true # OTLP otlp_config: @@ -839,6 +847,7 @@ extension_version: "compatibility" trace_propagation_style_extract: vec![TracePropagationStyle::B3], trace_propagation_extract_first: true, trace_propagation_http_baggage_enabled: true, + trace_aws_service_representation_enabled: true, otlp_config_traces_enabled: false, otlp_config_traces_span_name_as_resource_name: true, otlp_config_traces_span_name_remappings: HashMap::from([( diff --git a/bottlecap/src/lifecycle/invocation/processor.rs b/bottlecap/src/lifecycle/invocation/processor.rs index 457871322..abf6b9012 100644 --- a/bottlecap/src/lifecycle/invocation/processor.rs +++ b/bottlecap/src/lifecycle/invocation/processor.rs @@ -39,6 +39,8 @@ use crate::{ }, }; +use crate::lifecycle::invocation::triggers::get_default_service_name; + pub const MS_TO_NS: f64 = 1_000_000.0; pub const S_TO_MS: u64 = 1_000; pub const S_TO_NS: f64 = 1_000_000_000.0; @@ -93,15 +95,19 @@ impl Processor { .get_canonical_resource_name() .unwrap_or(String::from("aws.lambda")); - let service = match std::env::var("DD_TRACE_AWS_SERVICE_REPRESENTATION_ENABLED") { - Ok(val) if val.eq_ignore_ascii_case("false") => String::from("aws.lambda"), - _ => config.service.clone().unwrap_or(resource.clone()), - }; + let service = get_default_service_name( + &config.service.clone().unwrap_or(resource.clone()), + "aws.lambda", + config.trace_aws_service_representation_enabled, + ); let propagator = DatadogCompositePropagator::new(Arc::clone(&config)); Processor { context_buffer: ContextBuffer::default(), - inferrer: SpanInferrer::new(config.service_mapping.clone()), + inferrer: SpanInferrer::new( + config.service_mapping.clone(), + config.trace_aws_service_representation_enabled, + ), propagator, enhanced_metrics: EnhancedMetrics::new(metrics_aggregator, Arc::clone(&config)), aws_config, @@ -976,7 +982,6 @@ mod tests { use base64::{Engine, engine::general_purpose::STANDARD}; use dogstatsd::aggregator::Aggregator; use dogstatsd::metric::EMPTY_TAGS; - use serial_test::serial; fn setup() -> Processor { let aws_config = Arc::new(AwsConfig { @@ -1218,36 +1223,4 @@ mod tests { "UNUSED_INIT metric should be created when invoked_received=false" ); } - - #[test] - #[serial] - fn test_service_name_env_var_false_returns_aws_lambda() { - const VAR: &str = "DD_TRACE_AWS_SERVICE_REPRESENTATION_ENABLED"; - let original = std::env::var(VAR).ok(); - unsafe { std::env::set_var(VAR, "false") }; - - let p = setup(); - assert_eq!(p.service, "aws.lambda"); - - if let Some(v) = original { - unsafe { std::env::set_var(VAR, v) }; - } else { - unsafe { std::env::remove_var(VAR) }; - } - } - - #[test] - #[serial] - fn test_service_name_env_var_unset_uses_config_service() { - const VAR: &str = "DD_TRACE_AWS_SERVICE_REPRESENTATION_ENABLED"; - let original = std::env::var(VAR).ok(); - unsafe { std::env::remove_var(VAR) }; - - let p = setup(); - assert_eq!(p.service, "test-service"); - - if let Some(v) = original { - unsafe { std::env::set_var(VAR, v) }; - } - } } diff --git a/bottlecap/src/lifecycle/invocation/span_inferrer.rs b/bottlecap/src/lifecycle/invocation/span_inferrer.rs index d0facddc3..12973c781 100644 --- a/bottlecap/src/lifecycle/invocation/span_inferrer.rs +++ b/bottlecap/src/lifecycle/invocation/span_inferrer.rs @@ -32,6 +32,7 @@ use crate::{ #[derive(Default)] pub struct SpanInferrer { service_mapping: HashMap, + aws_service_representation_enabled: bool, // Span inferred from the Lambda incoming request payload pub inferred_span: Option, // Nested span inferred from the Lambda incoming request payload @@ -50,9 +51,13 @@ pub struct SpanInferrer { impl SpanInferrer { #[must_use] - pub fn new(service_mapping: HashMap) -> Self { + pub fn new( + service_mapping: HashMap, + aws_service_representation_enabled: bool, + ) -> Self { Self { service_mapping, + aws_service_representation_enabled, inferred_span: None, wrapped_inferred_span: None, is_async_span: false, @@ -87,19 +92,31 @@ impl SpanInferrer { if APIGatewayHttpEvent::is_match(payload_value) { if let Some(t) = APIGatewayHttpEvent::new(payload_value.clone()) { - t.enrich_span(&mut inferred_span, &self.service_mapping); + t.enrich_span( + &mut inferred_span, + &self.service_mapping, + self.aws_service_representation_enabled, + ); trigger = Some(Box::new(t)); } } else if APIGatewayRestEvent::is_match(payload_value) { if let Some(t) = APIGatewayRestEvent::new(payload_value.clone()) { - t.enrich_span(&mut inferred_span, &self.service_mapping); + t.enrich_span( + &mut inferred_span, + &self.service_mapping, + self.aws_service_representation_enabled, + ); trigger = Some(Box::new(t)); } } else if APIGatewayWebSocketEvent::is_match(payload_value) { if let Some(t) = APIGatewayWebSocketEvent::new(payload_value.clone()) { - t.enrich_span(&mut inferred_span, &self.service_mapping); + t.enrich_span( + &mut inferred_span, + &self.service_mapping, + self.aws_service_representation_enabled, + ); trigger = Some(Box::new(t)); } @@ -110,19 +127,31 @@ impl SpanInferrer { } } else if LambdaFunctionUrlEvent::is_match(payload_value) { if let Some(t) = LambdaFunctionUrlEvent::new(payload_value.clone()) { - t.enrich_span(&mut inferred_span, &self.service_mapping); + t.enrich_span( + &mut inferred_span, + &self.service_mapping, + self.aws_service_representation_enabled, + ); trigger = Some(Box::new(t)); } } else if MSKEvent::is_match(payload_value) { if let Some(t) = MSKEvent::new(payload_value.clone()) { - t.enrich_span(&mut inferred_span, &self.service_mapping); + t.enrich_span( + &mut inferred_span, + &self.service_mapping, + self.aws_service_representation_enabled, + ); trigger = Some(Box::new(t)); } } else if SqsRecord::is_match(payload_value) { if let Some(t) = SqsRecord::new(payload_value.clone()) { - t.enrich_span(&mut inferred_span, &self.service_mapping); + t.enrich_span( + &mut inferred_span, + &self.service_mapping, + self.aws_service_representation_enabled, + ); self.generated_span_context = extract_trace_context_from_aws_trace_header( t.attributes.aws_trace_header.clone(), @@ -140,7 +169,11 @@ impl SpanInferrer { sns: sns_entity, event_subscription_arn: None, }; - wt.enrich_span(&mut wrapped_inferred_span, &self.service_mapping); + wt.enrich_span( + &mut wrapped_inferred_span, + &self.service_mapping, + self.aws_service_representation_enabled, + ); inferred_span.meta.extend(wt.get_tags()); wrapped_inferred_span.duration = @@ -155,8 +188,11 @@ impl SpanInferrer { ..Default::default() }; - event_bridge_entity - .enrich_span(&mut wrapped_inferred_span, &self.service_mapping); + event_bridge_entity.enrich_span( + &mut wrapped_inferred_span, + &self.service_mapping, + self.aws_service_representation_enabled, + ); inferred_span.meta.extend(event_bridge_entity.get_tags()); wrapped_inferred_span.duration = @@ -169,7 +205,11 @@ impl SpanInferrer { } } else if SnsRecord::is_match(payload_value) { if let Some(t) = SnsRecord::new(payload_value.clone()) { - t.enrich_span(&mut inferred_span, &self.service_mapping); + t.enrich_span( + &mut inferred_span, + &self.service_mapping, + self.aws_service_representation_enabled, + ); if let Some(message) = &t.sns.message { if let Ok(event_bridge_wrapper_message) = @@ -180,8 +220,11 @@ impl SpanInferrer { ..Default::default() }; - event_bridge_wrapper_message - .enrich_span(&mut wrapped_inferred_span, &self.service_mapping); + event_bridge_wrapper_message.enrich_span( + &mut wrapped_inferred_span, + &self.service_mapping, + self.aws_service_representation_enabled, + ); inferred_span .meta .extend(event_bridge_wrapper_message.get_tags()); @@ -197,27 +240,43 @@ impl SpanInferrer { } } else if DynamoDbRecord::is_match(payload_value) { if let Some(t) = DynamoDbRecord::new(payload_value.clone()) { - t.enrich_span(&mut inferred_span, &self.service_mapping); + t.enrich_span( + &mut inferred_span, + &self.service_mapping, + self.aws_service_representation_enabled, + ); self.span_pointers = t.get_span_pointers(); trigger = Some(Box::new(t)); } } else if S3Record::is_match(payload_value) { if let Some(t) = S3Record::new(payload_value.clone()) { - t.enrich_span(&mut inferred_span, &self.service_mapping); + t.enrich_span( + &mut inferred_span, + &self.service_mapping, + self.aws_service_representation_enabled, + ); self.span_pointers = t.get_span_pointers(); trigger = Some(Box::new(t)); } } else if EventBridgeEvent::is_match(payload_value) { if let Some(t) = EventBridgeEvent::new(payload_value.clone()) { - t.enrich_span(&mut inferred_span, &self.service_mapping); + t.enrich_span( + &mut inferred_span, + &self.service_mapping, + self.aws_service_representation_enabled, + ); trigger = Some(Box::new(t)); } } else if KinesisRecord::is_match(payload_value) { if let Some(t) = KinesisRecord::new(payload_value.clone()) { - t.enrich_span(&mut inferred_span, &self.service_mapping); + t.enrich_span( + &mut inferred_span, + &self.service_mapping, + self.aws_service_representation_enabled, + ); trigger = Some(Box::new(t)); } diff --git a/bottlecap/src/lifecycle/invocation/triggers/alb_event.rs b/bottlecap/src/lifecycle/invocation/triggers/alb_event.rs index 134aa85a0..f9832d351 100644 --- a/bottlecap/src/lifecycle/invocation/triggers/alb_event.rs +++ b/bottlecap/src/lifecycle/invocation/triggers/alb_event.rs @@ -47,7 +47,13 @@ impl Trigger for ALBEvent { target_group_arn.is_some() } - fn enrich_span(&self, _span: &mut Span, _service_mapping: &HashMap) {} + fn enrich_span( + &self, + _span: &mut Span, + _service_mapping: &HashMap, + _aws_service_representation_enabled: bool, + ) { + } fn get_tags(&self) -> HashMap { HashMap::from([ @@ -231,7 +237,8 @@ mod tests { event.resolve_service_name( &specific_service_mapping, &event.request_context.elb.target_group_arn, - "lambda_application_load_balancer" + "lambda_application_load_balancer", + true ), "specific-service" ); @@ -244,7 +251,8 @@ mod tests { event.resolve_service_name( &generic_service_mapping, &event.request_context.elb.target_group_arn, - "lambda_application_load_balancer" + "lambda_application_load_balancer", + true ), "generic-service" ); 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 93f727d43..0f5f22a62 100644 --- a/bottlecap/src/lifecycle/invocation/triggers/api_gateway_http_event.rs +++ b/bottlecap/src/lifecycle/invocation/triggers/api_gateway_http_event.rs @@ -64,7 +64,12 @@ impl Trigger for APIGatewayHttpEvent { } #[allow(clippy::cast_possible_truncation)] - fn enrich_span(&self, span: &mut Span, service_mapping: &HashMap) { + fn enrich_span( + &self, + span: &mut Span, + service_mapping: &HashMap, + aws_service_representation_enabled: bool, + ) { debug!("Enriching an Inferred Span for an API Gateway HTTP Event"); let resource = format!( "{http_method} {parameterized_route}", @@ -82,6 +87,7 @@ impl Trigger for APIGatewayHttpEvent { service_mapping, &self.request_context.domain_name, &self.request_context.domain_name, + aws_service_representation_enabled, ); span.name = "aws.httpapi".to_string(); @@ -278,7 +284,7 @@ mod tests { APIGatewayHttpEvent::new(payload).expect("Failed to deserialize APIGatewayHttpEvent"); let mut span = Span::default(); let service_mapping = HashMap::new(); - event.enrich_span(&mut span, &service_mapping); + event.enrich_span(&mut span, &service_mapping, true); assert_eq!(span.name, "aws.httpapi"); assert_eq!( span.service, @@ -342,7 +348,7 @@ mod tests { APIGatewayHttpEvent::new(payload).expect("Failed to deserialize APIGatewayHttpEvent"); let mut span = Span::default(); let service_mapping = HashMap::new(); - event.enrich_span(&mut span, &service_mapping); + event.enrich_span(&mut span, &service_mapping, true); assert_eq!(span.name, "aws.httpapi"); assert_eq!( span.service, @@ -425,7 +431,8 @@ mod tests { event.resolve_service_name( &specific_service_mapping, &event.request_context.domain_name, - "api_gateway_http" + "api_gateway_http", + true ), "specific-service" ); @@ -438,7 +445,8 @@ mod tests { event.resolve_service_name( &generic_service_mapping, &event.request_context.domain_name, - "api_gateway_http" + "api_gateway_http", + true ), "generic-service" ); 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 53f79a273..34406689f 100644 --- a/bottlecap/src/lifecycle/invocation/triggers/api_gateway_rest_event.rs +++ b/bottlecap/src/lifecycle/invocation/triggers/api_gateway_rest_event.rs @@ -67,7 +67,12 @@ impl Trigger for APIGatewayRestEvent { } #[allow(clippy::cast_possible_truncation)] - fn enrich_span(&self, span: &mut Span, service_mapping: &HashMap) { + fn enrich_span( + &self, + span: &mut Span, + service_mapping: &HashMap, + aws_service_representation_enabled: bool, + ) { debug!("Enriching an Inferred Span for an API Gateway REST Event"); let resource = format!( "{http_method} {path}", @@ -85,6 +90,7 @@ impl Trigger for APIGatewayRestEvent { service_mapping, &self.request_context.domain_name, &self.request_context.domain_name, + aws_service_representation_enabled, ); span.name = "aws.apigateway".to_string(); @@ -290,7 +296,7 @@ mod tests { APIGatewayRestEvent::new(payload).expect("Failed to deserialize APIGatewayRestEvent"); let mut span = Span::default(); let service_mapping = HashMap::new(); - event.enrich_span(&mut span, &service_mapping); + event.enrich_span(&mut span, &service_mapping, true); assert_eq!(span.name, "aws.apigateway"); assert_eq!(span.service, "id.execute-api.us-east-1.amazonaws.com"); assert_eq!(span.resource, "GET /my/path"); @@ -349,7 +355,7 @@ mod tests { APIGatewayRestEvent::new(payload).expect("Failed to deserialize APIGatewayRestEvent"); let mut span = Span::default(); let service_mapping = HashMap::new(); - event.enrich_span(&mut span, &service_mapping); + event.enrich_span(&mut span, &service_mapping, true); assert_eq!(span.name, "aws.apigateway"); assert_eq!( span.service, @@ -441,7 +447,8 @@ mod tests { event.resolve_service_name( &specific_service_mapping, &event.request_context.domain_name, - "api_gateway_rest" + "api_gateway_rest", + true ), "specific-service" ); @@ -454,7 +461,8 @@ mod tests { event.resolve_service_name( &generic_service_mapping, &event.request_context.domain_name, - "api_gateway_rest" + "api_gateway_rest", + true ), "generic-service" ); diff --git a/bottlecap/src/lifecycle/invocation/triggers/api_gateway_websocket_event.rs b/bottlecap/src/lifecycle/invocation/triggers/api_gateway_websocket_event.rs index 6b192ed19..9c5f8952f 100644 --- a/bottlecap/src/lifecycle/invocation/triggers/api_gateway_websocket_event.rs +++ b/bottlecap/src/lifecycle/invocation/triggers/api_gateway_websocket_event.rs @@ -61,7 +61,12 @@ impl Trigger for APIGatewayWebSocketEvent { } #[allow(clippy::cast_possible_truncation)] - fn enrich_span(&self, span: &mut Span, service_mapping: &HashMap) { + fn enrich_span( + &self, + span: &mut Span, + service_mapping: &HashMap, + aws_service_representation_enabled: bool, + ) { debug!("Enriching an Inferred Span for an API Gateway WebSocket Event"); let resource = &self.request_context.route_key; let http_url = format!( @@ -75,6 +80,7 @@ impl Trigger for APIGatewayWebSocketEvent { service_mapping, &self.request_context.domain_name, "api_gateway_websocket", + aws_service_representation_enabled, ); span.name = "aws.apigateway".to_string(); @@ -280,7 +286,7 @@ mod tests { let mut span = Span::default(); let service_mapping = HashMap::new(); - event.enrich_span(&mut span, &service_mapping); + event.enrich_span(&mut span, &service_mapping, true); assert_eq!(span.name, "aws.apigateway"); assert_eq!( @@ -367,6 +373,7 @@ mod tests { &specific_service_mapping, &event.request_context.domain_name, &event.request_context.domain_name, + true ), "specific-service" ); @@ -380,7 +387,8 @@ mod tests { event.resolve_service_name( &generic_service_mapping, &event.request_context.domain_name, - "api_gateway_websocket" + "api_gateway_websocket", + true ), "generic-service" ); diff --git a/bottlecap/src/lifecycle/invocation/triggers/dynamodb_event.rs b/bottlecap/src/lifecycle/invocation/triggers/dynamodb_event.rs index bb3508699..6a9ccf597 100644 --- a/bottlecap/src/lifecycle/invocation/triggers/dynamodb_event.rs +++ b/bottlecap/src/lifecycle/invocation/triggers/dynamodb_event.rs @@ -100,14 +100,24 @@ impl Trigger for DynamoDbRecord { } #[allow(clippy::cast_possible_truncation)] - fn enrich_span(&self, span: &mut Span, service_mapping: &HashMap) { + fn enrich_span( + &self, + span: &mut Span, + service_mapping: &HashMap, + aws_service_representation_enabled: bool, + ) { debug!("Enriching an Inferred Span for a DynamoDB event"); let table_name = self.get_specific_identifier(); let resource = format!("{} {}", self.event_name.clone(), table_name); let start_time = (self.dynamodb.approximate_creation_date_time * S_TO_NS) as i64; - let service_name = self.resolve_service_name(service_mapping, &table_name, "dynamodb"); + let service_name = self.resolve_service_name( + service_mapping, + &table_name, + "dynamodb", + aws_service_representation_enabled, + ); span.name = String::from("aws.dynamodb"); span.service = service_name.to_string(); @@ -278,7 +288,7 @@ mod tests { let event = DynamoDbRecord::new(payload).expect("Failed to deserialize DynamoDbRecord"); let mut span = Span::default(); let service_mapping = HashMap::new(); - event.enrich_span(&mut span, &service_mapping); + event.enrich_span(&mut span, &service_mapping, true); assert_eq!(span.name, "aws.dynamodb"); assert_eq!(span.service, "ExampleTableWithStream"); assert_eq!(span.resource, "INSERT ExampleTableWithStream"); @@ -359,7 +369,8 @@ mod tests { event.resolve_service_name( &specific_service_mapping, &event.get_specific_identifier(), - "dynamodb" + "dynamodb", + true ), "specific-service" ); @@ -370,7 +381,8 @@ mod tests { event.resolve_service_name( &generic_service_mapping, &event.get_specific_identifier(), - "dynamodb" + "dynamodb", + true ), "generic-service" ); diff --git a/bottlecap/src/lifecycle/invocation/triggers/event_bridge_event.rs b/bottlecap/src/lifecycle/invocation/triggers/event_bridge_event.rs index bbc53ba79..b795a9bb0 100644 --- a/bottlecap/src/lifecycle/invocation/triggers/event_bridge_event.rs +++ b/bottlecap/src/lifecycle/invocation/triggers/event_bridge_event.rs @@ -51,7 +51,12 @@ impl Trigger for EventBridgeEvent { } #[allow(clippy::cast_possible_truncation)] - fn enrich_span(&self, span: &mut Span, service_mapping: &HashMap) { + fn enrich_span( + &self, + span: &mut Span, + service_mapping: &HashMap, + aws_service_representation_enabled: bool, + ) { // EventBridge events have a timestamp resolution in seconds let start_time_seconds = self .time @@ -69,6 +74,7 @@ impl Trigger for EventBridgeEvent { service_mapping, &self.get_specific_identifier(), "eventbridge", + aws_service_representation_enabled, ); span.name = String::from("aws.eventbridge"); @@ -185,7 +191,7 @@ mod tests { let mut span = Span::default(); let service_mapping = HashMap::new(); - event.enrich_span(&mut span, &service_mapping); + event.enrich_span(&mut span, &service_mapping, true); let expected = serde_json::from_str(&read_json_file("eventbridge_span.json")) .expect("Failed to deserialize into Span"); @@ -201,7 +207,7 @@ mod tests { let mut span = Span::default(); let service_mapping = HashMap::new(); - event.enrich_span(&mut span, &service_mapping); + event.enrich_span(&mut span, &service_mapping, true); assert_eq!(span.resource, "my.event"); } @@ -215,7 +221,7 @@ mod tests { let mut span = Span::default(); let service_mapping = HashMap::new(); - event.enrich_span(&mut span, &service_mapping); + event.enrich_span(&mut span, &service_mapping, true); assert_eq!(span.resource, "testBus"); // Seconds resolution @@ -278,7 +284,8 @@ mod tests { event.resolve_service_name( &specific_service_mapping, &event.get_specific_identifier(), - "eventbridge" + "eventbridge", + true ), "specific-service" ); @@ -291,7 +298,8 @@ mod tests { event.resolve_service_name( &generic_service_mapping, &event.get_specific_identifier(), - "eventbridge" + "eventbridge", + true ), "generic-service" ); diff --git a/bottlecap/src/lifecycle/invocation/triggers/kinesis_event.rs b/bottlecap/src/lifecycle/invocation/triggers/kinesis_event.rs index 3bad94875..a6b263b3e 100644 --- a/bottlecap/src/lifecycle/invocation/triggers/kinesis_event.rs +++ b/bottlecap/src/lifecycle/invocation/triggers/kinesis_event.rs @@ -70,10 +70,20 @@ impl Trigger for KinesisRecord { } #[allow(clippy::cast_possible_truncation)] - fn enrich_span(&self, span: &mut Span, service_mapping: &HashMap) { + fn enrich_span( + &self, + span: &mut Span, + service_mapping: &HashMap, + aws_service_representation_enabled: bool, + ) { let stream_name = self.get_specific_identifier(); let shard_id = self.event_id.split(':').next().unwrap_or_default(); - let service_name = self.resolve_service_name(service_mapping, &stream_name, "kinesis"); + let service_name = self.resolve_service_name( + service_mapping, + &stream_name, + "kinesis", + aws_service_representation_enabled, + ); span.name = String::from("aws.kinesis"); span.service = service_name; @@ -190,7 +200,7 @@ mod tests { let event = KinesisRecord::new(payload).expect("Failed to deserialize S3Record"); let mut span = Span::default(); let service_mapping = HashMap::new(); - event.enrich_span(&mut span, &service_mapping); + event.enrich_span(&mut span, &service_mapping, true); assert_eq!(span.name, "aws.kinesis"); assert_eq!(span.service, "kinesisStream"); assert_eq!(span.resource, "kinesisStream"); @@ -282,7 +292,8 @@ mod tests { event.resolve_service_name( &specific_service_mapping, &event.get_specific_identifier(), - "kinesis" + "kinesis", + true ), "specific-service" ); @@ -293,7 +304,8 @@ mod tests { event.resolve_service_name( &generic_service_mapping, &event.get_specific_identifier(), - "kinesis" + "kinesis", + true ), "generic-service" ); diff --git a/bottlecap/src/lifecycle/invocation/triggers/lambda_function_url_event.rs b/bottlecap/src/lifecycle/invocation/triggers/lambda_function_url_event.rs index 052f1bb1d..930845efb 100644 --- a/bottlecap/src/lifecycle/invocation/triggers/lambda_function_url_event.rs +++ b/bottlecap/src/lifecycle/invocation/triggers/lambda_function_url_event.rs @@ -63,7 +63,12 @@ impl Trigger for LambdaFunctionUrlEvent { } #[allow(clippy::cast_possible_truncation)] - fn enrich_span(&self, span: &mut Span, service_mapping: &HashMap) { + fn enrich_span( + &self, + span: &mut Span, + service_mapping: &HashMap, + aws_service_representation_enabled: bool, + ) { let resource = format!( "{} {}", self.request_context.http.method, self.request_context.http.path @@ -81,6 +86,7 @@ impl Trigger for LambdaFunctionUrlEvent { service_mapping, &self.request_context.domain_name, &self.request_context.domain_name, + aws_service_representation_enabled, ); span.name = String::from("aws.lambda.url"); @@ -270,7 +276,7 @@ mod tests { .expect("Failed to deserialize LambdaFunctionUrlEvent"); let mut span = Span::default(); let service_mapping = HashMap::new(); - event.enrich_span(&mut span, &service_mapping); + event.enrich_span(&mut span, &service_mapping, true); assert_eq!(span.name, "aws.lambda.url"); assert_eq!( span.service, @@ -339,14 +345,19 @@ mod tests { ]); assert_eq!( - event.resolve_service_name(&specific_service_mapping, "domain-name", "lambda_url"), + event.resolve_service_name( + &specific_service_mapping, + "domain-name", + "lambda_url", + true + ), "specific-service" ); let generic_service_mapping = HashMap::from([("lambda_url".to_string(), "generic-service".to_string())]); assert_eq!( - event.resolve_service_name(&generic_service_mapping, "domain-name", "lambda_url"), + event.resolve_service_name(&generic_service_mapping, "domain-name", "lambda_url", true), "generic-service" ); } diff --git a/bottlecap/src/lifecycle/invocation/triggers/mod.rs b/bottlecap/src/lifecycle/invocation/triggers/mod.rs index 6c01bb50e..02d424d84 100644 --- a/bottlecap/src/lifecycle/invocation/triggers/mod.rs +++ b/bottlecap/src/lifecycle/invocation/triggers/mod.rs @@ -96,6 +96,19 @@ pub fn parameterize_api_resource(resource: String) -> String { result.join("/") } +#[must_use] +pub fn get_default_service_name( + instance_name: &str, + fallback: &str, + aws_service_representation_enabled: bool, +) -> String { + if !aws_service_representation_enabled { + return fallback.to_string(); + } + + instance_name.to_string() +} + pub trait Trigger: ServiceNameResolver { fn new(payload: Value) -> Option where @@ -103,7 +116,12 @@ pub trait Trigger: ServiceNameResolver { fn is_match(payload: &Value) -> bool where Self: Sized; - fn enrich_span(&self, span: &mut Span, service_mapping: &HashMap); + fn enrich_span( + &self, + span: &mut Span, + service_mapping: &HashMap, + aws_service_representation_enabled: bool, + ); fn get_tags(&self) -> HashMap; fn get_arn(&self, region: &str) -> String; fn get_carrier(&self) -> HashMap; @@ -115,26 +133,18 @@ pub trait Trigger: ServiceNameResolver { service_mapping: &HashMap, instance_name: &str, fallback: &str, + aws_service_representation_enabled: bool, ) -> String { - // If DD_TRACE_AWS_SERVICE_REPRESENTATION_ENABLED is explicitly set to "false", - // always use the fallback value (typically "aws.lambda"). - if std::env::var("DD_TRACE_AWS_SERVICE_REPRESENTATION_ENABLED") - .map(|v| v.eq_ignore_ascii_case("false")) - .unwrap_or(false) - { - return fallback.to_string(); - } - service_mapping .get(&self.get_specific_identifier()) .or_else(|| service_mapping.get(self.get_generic_identifier())) .cloned() .unwrap_or_else(|| { - if instance_name.is_empty() { - fallback.to_string() - } else { - instance_name.to_string() - } + get_default_service_name( + instance_name, + fallback, + aws_service_representation_enabled, + ) }) } } diff --git a/bottlecap/src/lifecycle/invocation/triggers/msk_event.rs b/bottlecap/src/lifecycle/invocation/triggers/msk_event.rs index cb453f3cd..4b4ca3449 100644 --- a/bottlecap/src/lifecycle/invocation/triggers/msk_event.rs +++ b/bottlecap/src/lifecycle/invocation/triggers/msk_event.rs @@ -59,12 +59,21 @@ impl Trigger for MSKEvent { } #[allow(clippy::cast_possible_truncation)] - fn enrich_span(&self, span: &mut Span, service_mapping: &HashMap) { + fn enrich_span( + &self, + span: &mut Span, + service_mapping: &HashMap, + aws_service_representation_enabled: bool, + ) { debug!("Enriching an Inferred Span for an MSK event"); span.name = String::from("aws.msk"); - span.service = - self.resolve_service_name(service_mapping, &self.get_specific_identifier(), "msk"); + span.service = self.resolve_service_name( + service_mapping, + &self.get_specific_identifier(), + "msk", + aws_service_representation_enabled, + ); span.r#type = String::from("web"); let first_value = self.records.values().find_map(|arr| arr.first()); @@ -170,7 +179,7 @@ mod tests { let event = MSKEvent::new(payload).expect("Failed to deserialize MSKEvent"); let mut span = Span::default(); let service_mapping = HashMap::new(); - event.enrich_span(&mut span, &service_mapping); + event.enrich_span(&mut span, &service_mapping, true); assert_eq!(span.name, "aws.msk"); assert_eq!(span.service, "demo-cluster"); @@ -247,7 +256,8 @@ mod tests { event.resolve_service_name( &specific_service_mapping, &event.get_specific_identifier(), - "msk" + "msk", + true ), "specific-service" ); @@ -258,7 +268,8 @@ mod tests { event.resolve_service_name( &generic_service_mapping, &event.get_specific_identifier(), - "msk" + "msk", + true ), "generic-service" ); diff --git a/bottlecap/src/lifecycle/invocation/triggers/s3_event.rs b/bottlecap/src/lifecycle/invocation/triggers/s3_event.rs index e1fc07388..000a9b6d4 100644 --- a/bottlecap/src/lifecycle/invocation/triggers/s3_event.rs +++ b/bottlecap/src/lifecycle/invocation/triggers/s3_event.rs @@ -77,7 +77,12 @@ impl Trigger for S3Record { } #[allow(clippy::cast_possible_truncation)] - fn enrich_span(&self, span: &mut Span, service_mapping: &HashMap) { + fn enrich_span( + &self, + span: &mut Span, + service_mapping: &HashMap, + aws_service_representation_enabled: bool, + ) { debug!("Enriching an InferredSpan span with S3 event"); let bucket_name = self.get_specific_identifier(); let start_time = self @@ -85,10 +90,15 @@ impl Trigger for S3Record { .timestamp_nanos_opt() .unwrap_or((self.event_time.timestamp_millis() as f64 * MS_TO_NS) as i64); - let service_name = self.resolve_service_name(service_mapping, &bucket_name, "s3"); + let service_name = self.resolve_service_name( + service_mapping, + &bucket_name, + "s3", + aws_service_representation_enabled, + ); span.name = String::from("aws.s3"); - span.service = service_name.to_string(); + span.service = service_name; span.resource.clone_from(&bucket_name); span.r#type = String::from("web"); span.start = start_time; @@ -211,30 +221,12 @@ mod tests { let event = S3Record::new(payload).expect("Failed to deserialize S3Record"); let mut span = Span::default(); let service_mapping = HashMap::new(); - event.enrich_span(&mut span, &service_mapping); + event.enrich_span(&mut span, &service_mapping, true); assert_eq!(span.name, "aws.s3"); assert_eq!(span.service, "example-bucket"); assert_eq!(span.resource, "example-bucket"); assert_eq!(span.r#type, "web"); - - assert_eq!( - span.meta, - HashMap::from([ - ("operation_name".to_string(), "aws.s3".to_string()), - ("event_name".to_string(), "ObjectCreated:Put".to_string()), - ("bucketname".to_string(), "example-bucket".to_string()), - ( - "bucket_arn".to_string(), - "arn:aws:s3:::example-bucket".to_string() - ), - ("object_key".to_string(), "test/key".to_string()), - ("object_size".to_string(), "1024".to_string()), - ( - "object_etag".to_string(), - "0123456789abcdef0123456789abcdef".to_string() - ) - ]) - ); + assert_eq!(span.start, 1_673_049_600_000_000_000); } #[test] @@ -284,25 +276,23 @@ mod tests { ("lambda_s3".to_string(), "generic-service".to_string()), ]); - assert_eq!( - event.resolve_service_name( - &specific_service_mapping, - &event.get_specific_identifier(), - "s3" - ), - "specific-service" + let service = event.resolve_service_name( + &specific_service_mapping, + &event.get_specific_identifier(), + "s3", + true, ); + assert_eq!(service, "specific-service"); let generic_service_mapping = HashMap::from([("lambda_s3".to_string(), "generic-service".to_string())]); - assert_eq!( - event.resolve_service_name( - &generic_service_mapping, - &event.get_specific_identifier(), - "s3" - ), - "generic-service" + let service = event.resolve_service_name( + &generic_service_mapping, + &event.get_specific_identifier(), + "s3", + true, ); + assert_eq!(service, "generic-service"); } #[test] diff --git a/bottlecap/src/lifecycle/invocation/triggers/sns_event.rs b/bottlecap/src/lifecycle/invocation/triggers/sns_event.rs index d4df4b17a..2632e45a2 100644 --- a/bottlecap/src/lifecycle/invocation/triggers/sns_event.rs +++ b/bottlecap/src/lifecycle/invocation/triggers/sns_event.rs @@ -82,7 +82,12 @@ impl Trigger for SnsRecord { } #[allow(clippy::cast_possible_truncation)] - fn enrich_span(&self, span: &mut Span, service_mapping: &HashMap) { + fn enrich_span( + &self, + span: &mut Span, + service_mapping: &HashMap, + aws_service_representation_enabled: bool, + ) { debug!("Enriching an Inferred Span for an SNS Event"); let resource_name = self.get_specific_identifier(); @@ -92,8 +97,12 @@ impl Trigger for SnsRecord { .timestamp_nanos_opt() .unwrap_or((self.sns.timestamp.timestamp_millis() as f64 * MS_TO_NS) as i64); - let service_name = - self.resolve_service_name(service_mapping, &self.get_specific_identifier(), "sns"); + let service_name = self.resolve_service_name( + service_mapping, + &self.get_specific_identifier(), + "sns", + aws_service_representation_enabled, + ); span.name = "aws.sns".to_string(); span.service = service_name.to_string(); @@ -235,7 +244,7 @@ mod tests { let event = SnsRecord::new(payload).expect("Failed to deserialize SnsRecord"); let mut span = Span::default(); let service_mapping = HashMap::new(); - event.enrich_span(&mut span, &service_mapping); + event.enrich_span(&mut span, &service_mapping, true); assert_eq!(span.name, "aws.sns"); assert_eq!(span.service, "serverlessTracingTopicPy"); assert_eq!(span.resource, "serverlessTracingTopicPy"); @@ -372,7 +381,8 @@ mod tests { event.resolve_service_name( &specific_service_mapping, &event.get_specific_identifier(), - "sns" + "sns", + true ), "specific-service" ); @@ -383,7 +393,8 @@ mod tests { event.resolve_service_name( &generic_service_mapping, &event.get_specific_identifier(), - "sns" + "sns", + true ), "generic-service" ); diff --git a/bottlecap/src/lifecycle/invocation/triggers/sqs_event.rs b/bottlecap/src/lifecycle/invocation/triggers/sqs_event.rs index 62f6913c7..3b405313f 100644 --- a/bottlecap/src/lifecycle/invocation/triggers/sqs_event.rs +++ b/bottlecap/src/lifecycle/invocation/triggers/sqs_event.rs @@ -99,7 +99,12 @@ impl Trigger for SqsRecord { } #[allow(clippy::cast_possible_truncation)] - fn enrich_span(&self, span: &mut Span, service_mapping: &HashMap) { + fn enrich_span( + &self, + span: &mut Span, + service_mapping: &HashMap, + aws_service_representation_enabled: bool, + ) { debug!("Enriching an Inferred Span for an SQS Event"); let resource = self.get_specific_identifier(); let start_time = (self @@ -109,8 +114,12 @@ impl Trigger for SqsRecord { .unwrap_or_default() as f64 * MS_TO_NS) as i64; - let service_name = - self.resolve_service_name(service_mapping, &self.get_specific_identifier(), "sqs"); + let service_name = self.resolve_service_name( + service_mapping, + &self.get_specific_identifier(), + "sqs", + aws_service_representation_enabled, + ); span.name = "aws.sqs".to_string(); span.service = service_name.to_string(); @@ -327,7 +336,7 @@ mod tests { let event = SqsRecord::new(payload).expect("Failed to deserialize SqsRecord"); let mut span = Span::default(); let service_mapping = HashMap::new(); - event.enrich_span(&mut span, &service_mapping); + event.enrich_span(&mut span, &service_mapping, true); assert_eq!(span.name, "aws.sqs"); assert_eq!(span.service, "MyQueue"); assert_eq!(span.resource, "MyQueue"); @@ -509,7 +518,8 @@ mod tests { event.resolve_service_name( &specific_service_mapping, &event.get_specific_identifier(), - "sqs" + "sqs", + true ), "specific-service" ); @@ -520,7 +530,8 @@ mod tests { event.resolve_service_name( &generic_service_mapping, &event.get_specific_identifier(), - "sqs" + "sqs", + true ), "generic-service" ); diff --git a/bottlecap/src/lifecycle/invocation/triggers/step_function_event.rs b/bottlecap/src/lifecycle/invocation/triggers/step_function_event.rs index a15913064..1f937905e 100644 --- a/bottlecap/src/lifecycle/invocation/triggers/step_function_event.rs +++ b/bottlecap/src/lifecycle/invocation/triggers/step_function_event.rs @@ -116,6 +116,7 @@ impl Trigger for StepFunctionEvent { &self, _span: &mut datadog_trace_protobuf::pb::Span, _service_mapping: &HashMap, + _aws_service_representation_enabled: bool, ) { } diff --git a/bottlecap/tests/service_naming_test.rs b/bottlecap/tests/service_naming_test.rs deleted file mode 100644 index b67556506..000000000 --- a/bottlecap/tests/service_naming_test.rs +++ /dev/null @@ -1,151 +0,0 @@ -use std::collections::HashMap; - -use bottlecap::lifecycle::invocation::triggers::{ - Trigger, dynamodb_event::DynamoDbRecord, kinesis_event::KinesisRecord, - lambda_function_url_event::LambdaFunctionUrlEvent, msk_event::MSKEvent, s3_event::S3Record, - sns_event::SnsRecord, sqs_event::SqsRecord, -}; - -use bottlecap::lifecycle::invocation::triggers::ServiceNameResolver; - -/// Small helper for integration tests: loads a payload JSON file from -/// `bottlecap/tests/payloads/` and returns its content as `String`. -fn read_json_file(file_name: &str) -> String { - use std::fs; - use std::path::PathBuf; - - // `CARGO_MANIFEST_DIR` points at the `bottlecap` crate root when this - // integration test is compiled. - let mut path = PathBuf::from(env!("CARGO_MANIFEST_DIR")); - path.push("tests/payloads"); - path.push(file_name); - fs::read_to_string(path).expect("Failed to read test payload file") -} - -#[test] -#[serial] -fn test_dynamodb_service_name_instance_fallback() { - unsafe { std::env::remove_var("DD_TRACE_AWS_SERVICE_REPRESENTATION_ENABLED") }; - let json = read_json_file("dynamodb_event.json"); - let payload = serde_json::from_str(&json).expect("Failed to deserialize"); - let event = DynamoDbRecord::new(payload).expect("deserialize DynamoDbRecord"); - let table_name = event.get_specific_identifier(); - let service = event.resolve_service_name(&HashMap::new(), &table_name, "dynamodb"); - assert_eq!(service, "ExampleTableWithStream"); -} - -#[test] -#[serial] -fn test_s3_service_name_instance_fallback() { - unsafe { std::env::remove_var("DD_TRACE_AWS_SERVICE_REPRESENTATION_ENABLED") }; - let json = read_json_file("s3_event.json"); - let payload = serde_json::from_str(&json).expect("Failed to deserialize"); - let event = S3Record::new(payload).expect("deserialize S3Record"); - let bucket_name = event.get_specific_identifier(); - let service = event.resolve_service_name(&HashMap::new(), &bucket_name, "s3"); - assert_eq!(service, "example-bucket"); -} - -#[test] -#[serial] -fn test_sqs_service_name_instance_fallback() { - unsafe { std::env::remove_var("DD_TRACE_AWS_SERVICE_REPRESENTATION_ENABLED") }; - let json = read_json_file("sqs_event.json"); - let payload = serde_json::from_str(&json).expect("Failed to deserialize"); - let event = SqsRecord::new(payload).expect("deserialize SqsRecord"); - let queue_name = event.get_specific_identifier(); - let service = event.resolve_service_name(&HashMap::new(), &queue_name, "sqs"); - assert_eq!(service, "MyQueue"); -} - -#[test] -#[serial] -fn test_kinesis_service_name_instance_fallback() { - unsafe { std::env::remove_var("DD_TRACE_AWS_SERVICE_REPRESENTATION_ENABLED") }; - let json = read_json_file("kinesis_event.json"); - let payload = serde_json::from_str(&json).expect("Failed to deserialize"); - let event = KinesisRecord::new(payload).expect("deserialize KinesisRecord"); - let stream_name = event.get_specific_identifier(); - let service = event.resolve_service_name(&HashMap::new(), &stream_name, "kinesis"); - assert_eq!(service, "kinesisStream"); -} - -#[test] -#[serial] -fn test_msk_service_name_instance_fallback() { - unsafe { std::env::remove_var("DD_TRACE_AWS_SERVICE_REPRESENTATION_ENABLED") }; - let json = read_json_file("msk_event.json"); - let payload = serde_json::from_str(&json).expect("Failed to deserialize"); - let event = MSKEvent::new(payload).expect("deserialize MSKEvent"); - let cluster_name = event.get_specific_identifier(); - let service = event.resolve_service_name(&HashMap::new(), &cluster_name, "msk"); - assert_eq!(service, "demo-cluster"); -} - -#[test] -#[serial] -fn test_sns_service_name_instance_fallback() { - unsafe { std::env::remove_var("DD_TRACE_AWS_SERVICE_REPRESENTATION_ENABLED") }; - let json = read_json_file("sns_event.json"); - let payload = serde_json::from_str(&json).expect("Failed to deserialize"); - let event = SnsRecord::new(payload).expect("deserialize SnsRecord"); - let topic_name = event.get_specific_identifier(); - let service = event.resolve_service_name(&HashMap::new(), &topic_name, "sns"); - assert_eq!(service, "serverlessTracingTopicPy"); -} - -use serial_test::serial; -use std::env; - -fn with_env_var(f: F) { - // Helper to set env var to "false" and restore original value after running closure - const VAR: &str = "DD_TRACE_AWS_SERVICE_REPRESENTATION_ENABLED"; - let original = env::var(VAR).ok(); - unsafe { env::set_var(VAR, "false") }; - f(); - if let Some(val) = original { - unsafe { env::set_var(VAR, val) }; - } else { - unsafe { env::remove_var(VAR) }; - } -} - -#[test] -#[serial] -fn test_dynamodb_service_name_env_var_false_uses_fallback() { - with_env_var(|| { - let json = read_json_file("dynamodb_event.json"); - let payload = serde_json::from_str(&json).expect("Failed to deserialize"); - let event = DynamoDbRecord::new(payload).expect("deserialize DynamoDbRecord"); - let table_name = event.get_specific_identifier(); - let service = event.resolve_service_name(&HashMap::new(), &table_name, "dynamodb"); - assert_eq!(service, "dynamodb"); - }); -} - -#[test] -#[serial] -fn test_s3_service_name_env_var_false_uses_fallback() { - with_env_var(|| { - let json = read_json_file("s3_event.json"); - let payload = serde_json::from_str(&json).expect("Failed to deserialize"); - let event = S3Record::new(payload).expect("deserialize S3Record"); - let bucket_name = event.get_specific_identifier(); - let service = event.resolve_service_name(&HashMap::new(), &bucket_name, "s3"); - assert_eq!(service, "s3"); - }); -} - -#[test] -#[serial] -fn test_lambda_url_service_name_env_var_false_uses_fallback() { - with_env_var(|| { - let json = read_json_file("lambda_function_url_event.json"); - let payload = serde_json::from_str(&json).expect("Failed to deserialize"); - let event = - LambdaFunctionUrlEvent::new(payload).expect("deserialize LambdaFunctionUrlEvent"); - let domain = event.get_specific_identifier(); - let service = event.resolve_service_name(&HashMap::new(), &domain, "lambda_url"); - assert_eq!(service, "lambda_url"); - }); -} From abaddf5411c22ebaf41277d7bfa8af12e22abc4d Mon Sep 17 00:00:00 2001 From: Zarir Hamza Date: Wed, 6 Aug 2025 18:07:51 -0400 Subject: [PATCH 13/15] fix cold start --- bottlecap/src/lifecycle/invocation/processor.rs | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/bottlecap/src/lifecycle/invocation/processor.rs b/bottlecap/src/lifecycle/invocation/processor.rs index abf6b9012..7ec0c91cf 100644 --- a/bottlecap/src/lifecycle/invocation/processor.rs +++ b/bottlecap/src/lifecycle/invocation/processor.rs @@ -204,8 +204,6 @@ impl Processor { self.dynamic_tags .insert(String::from("cold_start"), cold_start.to_string()); - self.dynamic_tags - .insert(String::from("span.kind"), "server".to_string()); if proactive_initialization { self.dynamic_tags.insert( @@ -250,6 +248,9 @@ impl Processor { ); cold_start_span.span_id = generate_span_id(); cold_start_span.start = start_time; + cold_start_span + .meta + .insert("span.kind".to_string(), "server".to_string()); context.cold_start_span = Some(cold_start_span); } @@ -410,6 +411,11 @@ impl Processor { .meta .extend(self.dynamic_tags.clone()); + context + .invocation_span + .meta + .insert("span.kind".to_string(), "server".to_string()); + if let Some(trigger_tags) = self.inferrer.get_trigger_tags() { context.invocation_span.meta.extend(trigger_tags); } From 2af203ed957baa94ea4600c4d5bf36d81308625d Mon Sep 17 00:00:00 2001 From: Zarir Hamza Date: Wed, 6 Aug 2025 18:41:01 -0400 Subject: [PATCH 14/15] update tests per service --- .../invocation/triggers/alb_event.rs | 77 +++++++++++++++++- .../triggers/api_gateway_http_event.rs | 79 +++++++++++++++++-- .../triggers/api_gateway_rest_event.rs | 79 +++++++++++++++++-- .../triggers/api_gateway_websocket_event.rs | 78 ++++++++++++++++-- .../invocation/triggers/dynamodb_event.rs | 72 ++++++++++++++++- .../invocation/triggers/event_bridge_event.rs | 74 ++++++++++++++++- .../invocation/triggers/kinesis_event.rs | 69 +++++++++++++++- .../triggers/lambda_function_url_event.rs | 75 +++++++++++++++++- .../invocation/triggers/msk_event.rs | 69 +++++++++++++++- .../lifecycle/invocation/triggers/s3_event.rs | 61 +++++++++++++- .../invocation/triggers/sns_event.rs | 72 ++++++++++++++++- .../invocation/triggers/sqs_event.rs | 69 +++++++++++++++- .../triggers/step_function_event.rs | 72 +++++++++++++++++ 13 files changed, 893 insertions(+), 53 deletions(-) diff --git a/bottlecap/src/lifecycle/invocation/triggers/alb_event.rs b/bottlecap/src/lifecycle/invocation/triggers/alb_event.rs index f9832d351..81ffe6c32 100644 --- a/bottlecap/src/lifecycle/invocation/triggers/alb_event.rs +++ b/bottlecap/src/lifecycle/invocation/triggers/alb_event.rs @@ -216,12 +216,12 @@ mod tests { } #[test] - fn test_resolve_service_name() { + fn test_resolve_service_name_with_representation_enabled() { let json = read_json_file("application_load_balancer.json"); let payload = serde_json::from_str(&json).expect("Failed to deserialize into Value"); let event = ALBEvent::new(payload).expect("Failed to deserialize ALBEvent"); - // Priority is given to the specific key + // Test 1: Specific mapping takes priority let specific_service_mapping = HashMap::from([ ( "nhulston-alb-test".to_string(), @@ -238,11 +238,12 @@ mod tests { &specific_service_mapping, &event.request_context.elb.target_group_arn, "lambda_application_load_balancer", - true + true // aws_service_representation_enabled ), "specific-service" ); + // Test 2: Generic mapping is used when specific not found let generic_service_mapping = HashMap::from([( "lambda_application_load_balancer".to_string(), "generic-service".to_string(), @@ -252,10 +253,78 @@ mod tests { &generic_service_mapping, &event.request_context.elb.target_group_arn, "lambda_application_load_balancer", - true + true // aws_service_representation_enabled ), "generic-service" ); + + // Test 3: When no mapping exists, uses instance name + let empty_mapping = HashMap::new(); + assert_eq!( + event.resolve_service_name( + &empty_mapping, + &event.request_context.elb.target_group_arn, + "lambda_application_load_balancer", + true // aws_service_representation_enabled + ), + event.request_context.elb.target_group_arn // instance name + ); + } + + #[test] + fn test_resolve_service_name_with_representation_disabled() { + let json = read_json_file("application_load_balancer.json"); + let payload = serde_json::from_str(&json).expect("Failed to deserialize into Value"); + let event = ALBEvent::new(payload).expect("Failed to deserialize ALBEvent"); + + // Test 1: With specific mapping - still respects mapping + let specific_service_mapping = HashMap::from([ + ( + "nhulston-alb-test".to_string(), + "specific-service".to_string(), + ), + ( + "lambda_application_load_balancer".to_string(), + "generic-service".to_string(), + ), + ]); + + assert_eq!( + event.resolve_service_name( + &specific_service_mapping, + &event.request_context.elb.target_group_arn, + "lambda_application_load_balancer", + false // aws_service_representation_enabled = false + ), + "specific-service" + ); + + // Test 2: With generic mapping - still respects mapping + let generic_service_mapping = HashMap::from([( + "lambda_application_load_balancer".to_string(), + "generic-service".to_string(), + )]); + assert_eq!( + event.resolve_service_name( + &generic_service_mapping, + &event.request_context.elb.target_group_arn, + "lambda_application_load_balancer", + false // aws_service_representation_enabled = false + ), + "generic-service" + ); + + // Test 3: When no mapping exists, uses fallback value + let empty_mapping = HashMap::new(); + assert_eq!( + event.resolve_service_name( + &empty_mapping, + &event.request_context.elb.target_group_arn, + "lambda_application_load_balancer", + false // aws_service_representation_enabled = false + ), + "lambda_application_load_balancer" // fallback value + ); } #[test] 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 0f5f22a62..792e6881b 100644 --- a/bottlecap/src/lifecycle/invocation/triggers/api_gateway_http_event.rs +++ b/bottlecap/src/lifecycle/invocation/triggers/api_gateway_http_event.rs @@ -412,13 +412,13 @@ mod tests { } #[test] - fn test_resolve_service_name() { + fn test_resolve_service_name_with_representation_enabled() { let json = read_json_file("api_gateway_http_event.json"); let payload = serde_json::from_str(&json).expect("Failed to deserialize into Value"); let event = APIGatewayHttpEvent::new(payload).expect("Failed to deserialize APIGatewayHttpEvent"); - // Priority is given to the specific key + // Test 1: Specific mapping takes priority let specific_service_mapping = HashMap::from([ ("x02yirxc7a".to_string(), "specific-service".to_string()), ( @@ -431,12 +431,13 @@ mod tests { event.resolve_service_name( &specific_service_mapping, &event.request_context.domain_name, - "api_gateway_http", - true + &event.request_context.domain_name, + true // aws_service_representation_enabled ), "specific-service" ); + // Test 2: Generic mapping is used when specific not found let generic_service_mapping = HashMap::from([( "lambda_api_gateway".to_string(), "generic-service".to_string(), @@ -445,10 +446,76 @@ mod tests { event.resolve_service_name( &generic_service_mapping, &event.request_context.domain_name, - "api_gateway_http", - true + &event.request_context.domain_name, + true // aws_service_representation_enabled ), "generic-service" ); + + // Test 3: When no mapping exists, uses instance name (domain_name) + let empty_mapping = HashMap::new(); + assert_eq!( + event.resolve_service_name( + &empty_mapping, + &event.request_context.domain_name, + &event.request_context.domain_name, + true // aws_service_representation_enabled + ), + event.request_context.domain_name // instance name + ); + } + + #[test] + fn test_resolve_service_name_with_representation_disabled() { + let json = read_json_file("api_gateway_http_event.json"); + let payload = serde_json::from_str(&json).expect("Failed to deserialize into Value"); + let event = + APIGatewayHttpEvent::new(payload).expect("Failed to deserialize APIGatewayHttpEvent"); + + // Test 1: With specific mapping - still respects mapping + let specific_service_mapping = HashMap::from([ + ("x02yirxc7a".to_string(), "specific-service".to_string()), + ( + "lambda_api_gateway".to_string(), + "generic-service".to_string(), + ), + ]); + + assert_eq!( + event.resolve_service_name( + &specific_service_mapping, + &event.request_context.domain_name, + &event.request_context.domain_name, + false // aws_service_representation_enabled = false + ), + "specific-service" + ); + + // Test 2: With generic mapping - still respects mapping + let generic_service_mapping = HashMap::from([( + "lambda_api_gateway".to_string(), + "generic-service".to_string(), + )]); + assert_eq!( + event.resolve_service_name( + &generic_service_mapping, + &event.request_context.domain_name, + &event.request_context.domain_name, + false // aws_service_representation_enabled = false + ), + "generic-service" + ); + + // Test 3: When no mapping exists, uses fallback value (domain_name) + let empty_mapping = HashMap::new(); + assert_eq!( + event.resolve_service_name( + &empty_mapping, + &event.request_context.domain_name, + &event.request_context.domain_name, + false // aws_service_representation_enabled = false + ), + event.request_context.domain_name // fallback value + ); } } 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 34406689f..52f2eb8c9 100644 --- a/bottlecap/src/lifecycle/invocation/triggers/api_gateway_rest_event.rs +++ b/bottlecap/src/lifecycle/invocation/triggers/api_gateway_rest_event.rs @@ -428,13 +428,13 @@ mod tests { } #[test] - fn test_resolve_service_name() { + fn test_resolve_service_name_with_representation_enabled() { 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"); - // Priority is given to the specific key + // Test 1: Specific mapping takes priority let specific_service_mapping = HashMap::from([ ("id".to_string(), "specific-service".to_string()), ( @@ -447,12 +447,13 @@ mod tests { event.resolve_service_name( &specific_service_mapping, &event.request_context.domain_name, - "api_gateway_rest", - true + &event.request_context.domain_name, + true // aws_service_representation_enabled ), "specific-service" ); + // Test 2: Generic mapping is used when specific not found let generic_service_mapping = HashMap::from([( "lambda_api_gateway".to_string(), "generic-service".to_string(), @@ -461,10 +462,76 @@ mod tests { event.resolve_service_name( &generic_service_mapping, &event.request_context.domain_name, - "api_gateway_rest", - true + &event.request_context.domain_name, + true // aws_service_representation_enabled ), "generic-service" ); + + // Test 3: When no mapping exists, uses instance name (domain_name) + let empty_mapping = HashMap::new(); + assert_eq!( + event.resolve_service_name( + &empty_mapping, + &event.request_context.domain_name, + &event.request_context.domain_name, + true // aws_service_representation_enabled + ), + event.request_context.domain_name // instance name + ); + } + + #[test] + fn test_resolve_service_name_with_representation_disabled() { + 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"); + + // Test 1: With specific mapping - still respects mapping + let specific_service_mapping = HashMap::from([ + ("id".to_string(), "specific-service".to_string()), + ( + "lambda_api_gateway".to_string(), + "generic-service".to_string(), + ), + ]); + + assert_eq!( + event.resolve_service_name( + &specific_service_mapping, + &event.request_context.domain_name, + &event.request_context.domain_name, + false // aws_service_representation_enabled = false + ), + "specific-service" + ); + + // Test 2: With generic mapping - still respects mapping + let generic_service_mapping = HashMap::from([( + "lambda_api_gateway".to_string(), + "generic-service".to_string(), + )]); + assert_eq!( + event.resolve_service_name( + &generic_service_mapping, + &event.request_context.domain_name, + &event.request_context.domain_name, + false // aws_service_representation_enabled = false + ), + "generic-service" + ); + + // Test 3: When no mapping exists, uses fallback value (domain_name) + let empty_mapping = HashMap::new(); + assert_eq!( + event.resolve_service_name( + &empty_mapping, + &event.request_context.domain_name, + &event.request_context.domain_name, + false // aws_service_representation_enabled = false + ), + event.request_context.domain_name // fallback value + ); } } diff --git a/bottlecap/src/lifecycle/invocation/triggers/api_gateway_websocket_event.rs b/bottlecap/src/lifecycle/invocation/triggers/api_gateway_websocket_event.rs index 9c5f8952f..53b97c91b 100644 --- a/bottlecap/src/lifecycle/invocation/triggers/api_gateway_websocket_event.rs +++ b/bottlecap/src/lifecycle/invocation/triggers/api_gateway_websocket_event.rs @@ -353,13 +353,13 @@ mod tests { } #[test] - fn test_resolve_service_name() { + fn test_resolve_service_name_with_representation_enabled() { let json = read_json_file("api_gateway_websocket_connect_event.json"); let payload = serde_json::from_str(&json).expect("Failed to deserialize into Value"); let event = APIGatewayWebSocketEvent::new(payload) .expect("Failed to deserialize APIGatewayWebSocketEvent"); - // Priority is given to the specific key + // Test 1: Specific mapping takes priority let specific_service_mapping = HashMap::from([ ("85fj5nw29d".to_string(), "specific-service".to_string()), ( @@ -373,11 +373,12 @@ mod tests { &specific_service_mapping, &event.request_context.domain_name, &event.request_context.domain_name, - true + true // aws_service_representation_enabled ), "specific-service" ); + // Test 2: Generic mapping is used when specific not found let generic_service_mapping = HashMap::from([( "lambda_api_gateway".to_string(), "generic-service".to_string(), @@ -387,10 +388,77 @@ mod tests { event.resolve_service_name( &generic_service_mapping, &event.request_context.domain_name, - "api_gateway_websocket", - true + &event.request_context.domain_name, + true // aws_service_representation_enabled ), "generic-service" ); + + // Test 3: When no mapping exists, uses instance name (domain_name) + let empty_mapping = HashMap::new(); + assert_eq!( + event.resolve_service_name( + &empty_mapping, + &event.request_context.domain_name, + &event.request_context.domain_name, + true // aws_service_representation_enabled + ), + event.request_context.domain_name // instance name + ); + } + + #[test] + fn test_resolve_service_name_with_representation_disabled() { + let json = read_json_file("api_gateway_websocket_connect_event.json"); + let payload = serde_json::from_str(&json).expect("Failed to deserialize into Value"); + let event = APIGatewayWebSocketEvent::new(payload) + .expect("Failed to deserialize APIGatewayWebSocketEvent"); + + // Test 1: With specific mapping - still respects mapping + let specific_service_mapping = HashMap::from([ + ("85fj5nw29d".to_string(), "specific-service".to_string()), + ( + "lambda_api_gateway".to_string(), + "generic-service".to_string(), + ), + ]); + + assert_eq!( + event.resolve_service_name( + &specific_service_mapping, + &event.request_context.domain_name, + &event.request_context.domain_name, + false // aws_service_representation_enabled = false + ), + "specific-service" + ); + + // Test 2: With generic mapping - still respects mapping + let generic_service_mapping = HashMap::from([( + "lambda_api_gateway".to_string(), + "generic-service".to_string(), + )]); + + assert_eq!( + event.resolve_service_name( + &generic_service_mapping, + &event.request_context.domain_name, + &event.request_context.domain_name, + false // aws_service_representation_enabled = false + ), + "generic-service" + ); + + // Test 3: When no mapping exists, uses fallback value (domain_name) + let empty_mapping = HashMap::new(); + assert_eq!( + event.resolve_service_name( + &empty_mapping, + &event.request_context.domain_name, + &event.request_context.domain_name, + false // aws_service_representation_enabled = false + ), + event.request_context.domain_name // fallback value + ); } } diff --git a/bottlecap/src/lifecycle/invocation/triggers/dynamodb_event.rs b/bottlecap/src/lifecycle/invocation/triggers/dynamodb_event.rs index 6a9ccf597..38b3f4dfc 100644 --- a/bottlecap/src/lifecycle/invocation/triggers/dynamodb_event.rs +++ b/bottlecap/src/lifecycle/invocation/triggers/dynamodb_event.rs @@ -351,12 +351,12 @@ mod tests { } #[test] - fn test_resolve_service_name() { + fn test_resolve_service_name_with_representation_enabled() { let json = read_json_file("dynamodb_event.json"); let payload = serde_json::from_str(&json).expect("Failed to deserialize into Value"); let event = DynamoDbRecord::new(payload).expect("Failed to deserialize DynamoDbRecord"); - // Priority is given to the specific key + // Test 1: Specific mapping takes priority let specific_service_mapping = HashMap::from([ ( "ExampleTableWithStream".to_string(), @@ -370,11 +370,12 @@ mod tests { &specific_service_mapping, &event.get_specific_identifier(), "dynamodb", - true + true // aws_service_representation_enabled ), "specific-service" ); + // Test 2: Generic mapping is used when specific not found let generic_service_mapping = HashMap::from([("lambda_dynamodb".to_string(), "generic-service".to_string())]); assert_eq!( @@ -382,10 +383,73 @@ mod tests { &generic_service_mapping, &event.get_specific_identifier(), "dynamodb", - true + true // aws_service_representation_enabled ), "generic-service" ); + + // Test 3: When no mapping exists, uses instance name (table name) + let empty_mapping = HashMap::new(); + assert_eq!( + event.resolve_service_name( + &empty_mapping, + &event.get_specific_identifier(), + "dynamodb", + true // aws_service_representation_enabled + ), + event.get_specific_identifier() // instance name + ); + } + + #[test] + fn test_resolve_service_name_with_representation_disabled() { + let json = read_json_file("dynamodb_event.json"); + let payload = serde_json::from_str(&json).expect("Failed to deserialize into Value"); + let event = DynamoDbRecord::new(payload).expect("Failed to deserialize DynamoDbRecord"); + + // Test 1: With specific mapping - still respects mapping + let specific_service_mapping = HashMap::from([ + ( + "ExampleTableWithStream".to_string(), + "specific-service".to_string(), + ), + ("lambda_dynamodb".to_string(), "generic-service".to_string()), + ]); + + assert_eq!( + event.resolve_service_name( + &specific_service_mapping, + &event.get_specific_identifier(), + "dynamodb", + false // aws_service_representation_enabled = false + ), + "specific-service" + ); + + // Test 2: With generic mapping - still respects mapping + let generic_service_mapping = + HashMap::from([("lambda_dynamodb".to_string(), "generic-service".to_string())]); + assert_eq!( + event.resolve_service_name( + &generic_service_mapping, + &event.get_specific_identifier(), + "dynamodb", + false // aws_service_representation_enabled = false + ), + "generic-service" + ); + + // Test 3: When no mapping exists, uses fallback value + let empty_mapping = HashMap::new(); + assert_eq!( + event.resolve_service_name( + &empty_mapping, + &event.get_specific_identifier(), + "dynamodb", + false // aws_service_representation_enabled = false + ), + "dynamodb" // fallback value + ); } #[test] diff --git a/bottlecap/src/lifecycle/invocation/triggers/event_bridge_event.rs b/bottlecap/src/lifecycle/invocation/triggers/event_bridge_event.rs index b795a9bb0..f4754cef5 100644 --- a/bottlecap/src/lifecycle/invocation/triggers/event_bridge_event.rs +++ b/bottlecap/src/lifecycle/invocation/triggers/event_bridge_event.rs @@ -266,12 +266,12 @@ mod tests { } #[test] - fn test_resolve_service_name() { + fn test_resolve_service_name_with_representation_enabled() { let json = read_json_file("eventbridge_event.json"); let payload = serde_json::from_str(&json).expect("Failed to deserialize into Value"); let event = EventBridgeEvent::new(payload).expect("Failed to deserialize EventBridgeEvent"); - // Priority is given to the specific key + // Test 1: Specific mapping takes priority let specific_service_mapping = HashMap::from([ ("testBus".to_string(), "specific-service".to_string()), ( @@ -285,11 +285,12 @@ mod tests { &specific_service_mapping, &event.get_specific_identifier(), "eventbridge", - true + true // aws_service_representation_enabled ), "specific-service" ); + // Test 2: Generic mapping is used when specific not found let generic_service_mapping = HashMap::from([( "lambda_eventbridge".to_string(), "generic-service".to_string(), @@ -299,9 +300,74 @@ mod tests { &generic_service_mapping, &event.get_specific_identifier(), "eventbridge", - true + true // aws_service_representation_enabled ), "generic-service" ); + + // Test 3: When no mapping exists, uses instance name + let empty_mapping = HashMap::new(); + assert_eq!( + event.resolve_service_name( + &empty_mapping, + &event.get_specific_identifier(), + "eventbridge", + true // aws_service_representation_enabled + ), + event.get_specific_identifier() // instance name + ); + } + + #[test] + fn test_resolve_service_name_with_representation_disabled() { + let json = read_json_file("eventbridge_event.json"); + let payload = serde_json::from_str(&json).expect("Failed to deserialize into Value"); + let event = EventBridgeEvent::new(payload).expect("Failed to deserialize EventBridgeEvent"); + + // Test 1: With specific mapping - still respects mapping + let specific_service_mapping = HashMap::from([ + ("testBus".to_string(), "specific-service".to_string()), + ( + "lambda_eventbridge".to_string(), + "generic-service".to_string(), + ), + ]); + + assert_eq!( + event.resolve_service_name( + &specific_service_mapping, + &event.get_specific_identifier(), + "eventbridge", + false // aws_service_representation_enabled = false + ), + "specific-service" + ); + + // Test 2: With generic mapping - still respects mapping + let generic_service_mapping = HashMap::from([( + "lambda_eventbridge".to_string(), + "generic-service".to_string(), + )]); + assert_eq!( + event.resolve_service_name( + &generic_service_mapping, + &event.get_specific_identifier(), + "eventbridge", + false // aws_service_representation_enabled = false + ), + "generic-service" + ); + + // Test 3: When no mapping exists, uses fallback value + let empty_mapping = HashMap::new(); + assert_eq!( + event.resolve_service_name( + &empty_mapping, + &event.get_specific_identifier(), + "eventbridge", + false // aws_service_representation_enabled = false + ), + "eventbridge" // fallback value + ); } } diff --git a/bottlecap/src/lifecycle/invocation/triggers/kinesis_event.rs b/bottlecap/src/lifecycle/invocation/triggers/kinesis_event.rs index a6b263b3e..6ccf24fcf 100644 --- a/bottlecap/src/lifecycle/invocation/triggers/kinesis_event.rs +++ b/bottlecap/src/lifecycle/invocation/triggers/kinesis_event.rs @@ -277,12 +277,12 @@ mod tests { } #[test] - fn test_resolve_service_name() { + fn test_resolve_service_name_with_representation_enabled() { let json = read_json_file("kinesis_event.json"); let payload = serde_json::from_str(&json).expect("Failed to deserialize into Value"); let event = KinesisRecord::new(payload).expect("Failed to deserialize KinesisRecord"); - // Priority is given to the specific key + // Test 1: Specific mapping takes priority let specific_service_mapping = HashMap::from([ ("kinesisStream".to_string(), "specific-service".to_string()), ("lambda_kinesis".to_string(), "generic-service".to_string()), @@ -293,11 +293,12 @@ mod tests { &specific_service_mapping, &event.get_specific_identifier(), "kinesis", - true + true // aws_service_representation_enabled ), "specific-service" ); + // Test 2: Generic mapping is used when specific not found let generic_service_mapping = HashMap::from([("lambda_kinesis".to_string(), "generic-service".to_string())]); assert_eq!( @@ -305,9 +306,69 @@ mod tests { &generic_service_mapping, &event.get_specific_identifier(), "kinesis", - true + true // aws_service_representation_enabled ), "generic-service" ); + + // Test 3: When no mapping exists, uses instance name (stream name) + let empty_mapping = HashMap::new(); + assert_eq!( + event.resolve_service_name( + &empty_mapping, + &event.get_specific_identifier(), + "kinesis", + true // aws_service_representation_enabled + ), + event.get_specific_identifier() // instance name + ); + } + + #[test] + fn test_resolve_service_name_with_representation_disabled() { + let json = read_json_file("kinesis_event.json"); + let payload = serde_json::from_str(&json).expect("Failed to deserialize into Value"); + let event = KinesisRecord::new(payload).expect("Failed to deserialize KinesisRecord"); + + // Test 1: With specific mapping - still respects mapping + let specific_service_mapping = HashMap::from([ + ("kinesisStream".to_string(), "specific-service".to_string()), + ("lambda_kinesis".to_string(), "generic-service".to_string()), + ]); + + assert_eq!( + event.resolve_service_name( + &specific_service_mapping, + &event.get_specific_identifier(), + "kinesis", + false // aws_service_representation_enabled = false + ), + "specific-service" + ); + + // Test 2: With generic mapping - still respects mapping + let generic_service_mapping = + HashMap::from([("lambda_kinesis".to_string(), "generic-service".to_string())]); + assert_eq!( + event.resolve_service_name( + &generic_service_mapping, + &event.get_specific_identifier(), + "kinesis", + false // aws_service_representation_enabled = false + ), + "generic-service" + ); + + // Test 3: When no mapping exists, uses fallback value + let empty_mapping = HashMap::new(); + assert_eq!( + event.resolve_service_name( + &empty_mapping, + &event.get_specific_identifier(), + "kinesis", + false // aws_service_representation_enabled = false + ), + "kinesis" // fallback value + ); } } diff --git a/bottlecap/src/lifecycle/invocation/triggers/lambda_function_url_event.rs b/bottlecap/src/lifecycle/invocation/triggers/lambda_function_url_event.rs index 930845efb..7159244df 100644 --- a/bottlecap/src/lifecycle/invocation/triggers/lambda_function_url_event.rs +++ b/bottlecap/src/lifecycle/invocation/triggers/lambda_function_url_event.rs @@ -332,13 +332,13 @@ mod tests { } #[test] - fn test_resolve_service_name() { + fn test_resolve_service_name_with_representation_enabled() { let json = read_json_file("lambda_function_url_event.json"); let payload = serde_json::from_str(&json).expect("Failed to deserialize into Value"); let event = LambdaFunctionUrlEvent::new(payload) .expect("Failed to deserialize LambdaFunctionUrlEvent"); - // Priority is given to the specific key + // Test 1: Specific mapping takes priority let specific_service_mapping = HashMap::from([ ("a8hyhsshac".to_string(), "specific-service".to_string()), ("lambda_url".to_string(), "generic-service".to_string()), @@ -349,16 +349,83 @@ mod tests { &specific_service_mapping, "domain-name", "lambda_url", - true + true // aws_service_representation_enabled ), "specific-service" ); + // Test 2: Generic mapping is used when specific not found let generic_service_mapping = HashMap::from([("lambda_url".to_string(), "generic-service".to_string())]); assert_eq!( - event.resolve_service_name(&generic_service_mapping, "domain-name", "lambda_url", true), + event.resolve_service_name( + &generic_service_mapping, + "domain-name", + "lambda_url", + true // aws_service_representation_enabled + ), "generic-service" ); + + // Test 3: When no mapping exists, uses instance name + let empty_mapping = HashMap::new(); + assert_eq!( + event.resolve_service_name( + &empty_mapping, + "domain-name", + "lambda_url", + true // aws_service_representation_enabled + ), + "domain-name" // instance name + ); + } + + #[test] + fn test_resolve_service_name_with_representation_disabled() { + let json = read_json_file("lambda_function_url_event.json"); + let payload = serde_json::from_str(&json).expect("Failed to deserialize into Value"); + let event = LambdaFunctionUrlEvent::new(payload) + .expect("Failed to deserialize LambdaFunctionUrlEvent"); + + // Test 1: With specific mapping - still respects mapping + let specific_service_mapping = HashMap::from([ + ("a8hyhsshac".to_string(), "specific-service".to_string()), + ("lambda_url".to_string(), "generic-service".to_string()), + ]); + + assert_eq!( + event.resolve_service_name( + &specific_service_mapping, + "domain-name", + "lambda_url", + false // aws_service_representation_enabled = false + ), + "specific-service" + ); + + // Test 2: With generic mapping - still respects mapping + let generic_service_mapping = + HashMap::from([("lambda_url".to_string(), "generic-service".to_string())]); + assert_eq!( + event.resolve_service_name( + &generic_service_mapping, + "domain-name", + "lambda_url", + false // aws_service_representation_enabled = false + ), + "generic-service" + ); + + // Test 3: When no mapping exists, uses fallback value + let empty_mapping = HashMap::new(); + assert_eq!( + event.resolve_service_name( + &empty_mapping, + "domain-name", + "lambda_url", + false // aws_service_representation_enabled = false + ), + "lambda_url" // fallback value + ); } } diff --git a/bottlecap/src/lifecycle/invocation/triggers/msk_event.rs b/bottlecap/src/lifecycle/invocation/triggers/msk_event.rs index 4b4ca3449..6523387ee 100644 --- a/bottlecap/src/lifecycle/invocation/triggers/msk_event.rs +++ b/bottlecap/src/lifecycle/invocation/triggers/msk_event.rs @@ -241,12 +241,12 @@ mod tests { } #[test] - fn test_resolve_service_name() { + fn test_resolve_service_name_with_representation_enabled() { let json = read_json_file("msk_event.json"); let payload = serde_json::from_str(&json).expect("Failed to deserialize into Value"); let event = MSKEvent::new(payload).expect("Failed to deserialize MSKEvent"); - // Priority is given to the specific key + // Test 1: Specific mapping takes priority let specific_service_mapping = HashMap::from([ ("demo-cluster".to_string(), "specific-service".to_string()), ("lambda_msk".to_string(), "generic-service".to_string()), @@ -257,11 +257,12 @@ mod tests { &specific_service_mapping, &event.get_specific_identifier(), "msk", - true + true // aws_service_representation_enabled ), "specific-service" ); + // Test 2: Generic mapping is used when specific not found let generic_service_mapping = HashMap::from([("lambda_msk".to_string(), "generic-service".to_string())]); assert_eq!( @@ -269,9 +270,69 @@ mod tests { &generic_service_mapping, &event.get_specific_identifier(), "msk", - true + true // aws_service_representation_enabled ), "generic-service" ); + + // Test 3: When no mapping exists, uses instance name (cluster name) + let empty_mapping = HashMap::new(); + assert_eq!( + event.resolve_service_name( + &empty_mapping, + &event.get_specific_identifier(), + "msk", + true // aws_service_representation_enabled + ), + event.get_specific_identifier() // instance name + ); + } + + #[test] + fn test_resolve_service_name_with_representation_disabled() { + let json = read_json_file("msk_event.json"); + let payload = serde_json::from_str(&json).expect("Failed to deserialize into Value"); + let event = MSKEvent::new(payload).expect("Failed to deserialize MSKEvent"); + + // Test 1: With specific mapping - still respects mapping + let specific_service_mapping = HashMap::from([ + ("demo-cluster".to_string(), "specific-service".to_string()), + ("lambda_msk".to_string(), "generic-service".to_string()), + ]); + + assert_eq!( + event.resolve_service_name( + &specific_service_mapping, + &event.get_specific_identifier(), + "msk", + false // aws_service_representation_enabled = false + ), + "specific-service" + ); + + // Test 2: With generic mapping - still respects mapping + let generic_service_mapping = + HashMap::from([("lambda_msk".to_string(), "generic-service".to_string())]); + assert_eq!( + event.resolve_service_name( + &generic_service_mapping, + &event.get_specific_identifier(), + "msk", + false // aws_service_representation_enabled = false + ), + "generic-service" + ); + + // Test 3: When no mapping exists, uses fallback value + let empty_mapping = HashMap::new(); + assert_eq!( + event.resolve_service_name( + &empty_mapping, + &event.get_specific_identifier(), + "msk", + false // aws_service_representation_enabled = false + ), + "msk" // fallback value + ); } } diff --git a/bottlecap/src/lifecycle/invocation/triggers/s3_event.rs b/bottlecap/src/lifecycle/invocation/triggers/s3_event.rs index 000a9b6d4..6e2237d7a 100644 --- a/bottlecap/src/lifecycle/invocation/triggers/s3_event.rs +++ b/bottlecap/src/lifecycle/invocation/triggers/s3_event.rs @@ -265,12 +265,12 @@ mod tests { } #[test] - fn test_resolve_service_name() { + fn test_resolve_service_name_with_representation_enabled() { let json = read_json_file("s3_event.json"); let payload = serde_json::from_str(&json).expect("Failed to deserialize into Value"); let event = S3Record::new(payload).expect("Failed to deserialize S3Record"); - // Priority is given to the specific key + // Test 1: Specific mapping takes priority let specific_service_mapping = HashMap::from([ ("example-bucket".to_string(), "specific-service".to_string()), ("lambda_s3".to_string(), "generic-service".to_string()), @@ -280,19 +280,72 @@ mod tests { &specific_service_mapping, &event.get_specific_identifier(), "s3", - true, + true, // aws_service_representation_enabled ); assert_eq!(service, "specific-service"); + // Test 2: Generic mapping is used when specific not found let generic_service_mapping = HashMap::from([("lambda_s3".to_string(), "generic-service".to_string())]); let service = event.resolve_service_name( &generic_service_mapping, &event.get_specific_identifier(), "s3", - true, + true, // aws_service_representation_enabled ); assert_eq!(service, "generic-service"); + + // Test 3: When no mapping exists, uses instance name (bucket name) + let empty_mapping = HashMap::new(); + let service = event.resolve_service_name( + &empty_mapping, + &event.get_specific_identifier(), + "s3", + true, // aws_service_representation_enabled + ); + assert_eq!(service, event.get_specific_identifier()); // instance name + } + + #[test] + fn test_resolve_service_name_with_representation_disabled() { + let json = read_json_file("s3_event.json"); + let payload = serde_json::from_str(&json).expect("Failed to deserialize into Value"); + let event = S3Record::new(payload).expect("Failed to deserialize S3Record"); + + // Test 1: With specific mapping - still respects mapping + let specific_service_mapping = HashMap::from([ + ("example-bucket".to_string(), "specific-service".to_string()), + ("lambda_s3".to_string(), "generic-service".to_string()), + ]); + + let service = event.resolve_service_name( + &specific_service_mapping, + &event.get_specific_identifier(), + "s3", + false, // aws_service_representation_enabled = false + ); + assert_eq!(service, "specific-service"); + + // Test 2: With generic mapping - still respects mapping + let generic_service_mapping = + HashMap::from([("lambda_s3".to_string(), "generic-service".to_string())]); + let service = event.resolve_service_name( + &generic_service_mapping, + &event.get_specific_identifier(), + "s3", + false, // aws_service_representation_enabled = false + ); + assert_eq!(service, "generic-service"); + + // Test 3: When no mapping exists, uses fallback value + let empty_mapping = HashMap::new(); + let service = event.resolve_service_name( + &empty_mapping, + &event.get_specific_identifier(), + "s3", + false, // aws_service_representation_enabled = false + ); + assert_eq!(service, "s3"); // fallback value } #[test] diff --git a/bottlecap/src/lifecycle/invocation/triggers/sns_event.rs b/bottlecap/src/lifecycle/invocation/triggers/sns_event.rs index 2632e45a2..95df01083 100644 --- a/bottlecap/src/lifecycle/invocation/triggers/sns_event.rs +++ b/bottlecap/src/lifecycle/invocation/triggers/sns_event.rs @@ -363,12 +363,12 @@ mod tests { } #[test] - fn test_resolve_service_name() { + fn test_resolve_service_name_with_representation_enabled() { 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"); - // Priority is given to the specific key + // Test 1: Specific mapping takes priority let specific_service_mapping = HashMap::from([ ( "serverlessTracingTopicPy".to_string(), @@ -382,11 +382,12 @@ mod tests { &specific_service_mapping, &event.get_specific_identifier(), "sns", - true + true // aws_service_representation_enabled ), "specific-service" ); + // Test 2: Generic mapping is used when specific not found let generic_service_mapping = HashMap::from([("lambda_sns".to_string(), "generic-service".to_string())]); assert_eq!( @@ -394,9 +395,72 @@ mod tests { &generic_service_mapping, &event.get_specific_identifier(), "sns", - true + true // aws_service_representation_enabled ), "generic-service" ); + + // Test 3: When no mapping exists, uses instance name (topic name) + let empty_mapping = HashMap::new(); + assert_eq!( + event.resolve_service_name( + &empty_mapping, + &event.get_specific_identifier(), + "sns", + true // aws_service_representation_enabled + ), + event.get_specific_identifier() // instance name + ); + } + + #[test] + fn test_resolve_service_name_with_representation_disabled() { + 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"); + + // Test 1: With specific mapping - still respects mapping + let specific_service_mapping = HashMap::from([ + ( + "serverlessTracingTopicPy".to_string(), + "specific-service".to_string(), + ), + ("lambda_sns".to_string(), "generic-service".to_string()), + ]); + + assert_eq!( + event.resolve_service_name( + &specific_service_mapping, + &event.get_specific_identifier(), + "sns", + false // aws_service_representation_enabled = false + ), + "specific-service" + ); + + // Test 2: With generic mapping - still respects mapping + let generic_service_mapping = + HashMap::from([("lambda_sns".to_string(), "generic-service".to_string())]); + assert_eq!( + event.resolve_service_name( + &generic_service_mapping, + &event.get_specific_identifier(), + "sns", + false // aws_service_representation_enabled = false + ), + "generic-service" + ); + + // Test 3: When no mapping exists, uses fallback value + let empty_mapping = HashMap::new(); + assert_eq!( + event.resolve_service_name( + &empty_mapping, + &event.get_specific_identifier(), + "sns", + false // aws_service_representation_enabled = false + ), + "sns" // fallback value + ); } } diff --git a/bottlecap/src/lifecycle/invocation/triggers/sqs_event.rs b/bottlecap/src/lifecycle/invocation/triggers/sqs_event.rs index 3b405313f..834e9c143 100644 --- a/bottlecap/src/lifecycle/invocation/triggers/sqs_event.rs +++ b/bottlecap/src/lifecycle/invocation/triggers/sqs_event.rs @@ -503,12 +503,12 @@ mod tests { } #[test] - fn test_resolve_service_name() { + fn test_resolve_service_name_with_representation_enabled() { 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"); - // Priority is given to the specific key + // Test 1: Specific mapping takes priority let specific_service_mapping = HashMap::from([ ("MyQueue".to_string(), "specific-service".to_string()), ("lambda_sqs".to_string(), "generic-service".to_string()), @@ -519,11 +519,12 @@ mod tests { &specific_service_mapping, &event.get_specific_identifier(), "sqs", - true + true // aws_service_representation_enabled ), "specific-service" ); + // Test 2: Generic mapping is used when specific not found let generic_service_mapping = HashMap::from([("lambda_sqs".to_string(), "generic-service".to_string())]); assert_eq!( @@ -531,10 +532,70 @@ mod tests { &generic_service_mapping, &event.get_specific_identifier(), "sqs", - true + true // aws_service_representation_enabled ), "generic-service" ); + + // Test 3: When no mapping exists, uses instance name (queue name) + let empty_mapping = HashMap::new(); + assert_eq!( + event.resolve_service_name( + &empty_mapping, + &event.get_specific_identifier(), + "sqs", + true // aws_service_representation_enabled + ), + event.get_specific_identifier() // instance name + ); + } + + #[test] + fn test_resolve_service_name_with_representation_disabled() { + 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"); + + // Test 1: With specific mapping - still respects mapping + let specific_service_mapping = HashMap::from([ + ("MyQueue".to_string(), "specific-service".to_string()), + ("lambda_sqs".to_string(), "generic-service".to_string()), + ]); + + assert_eq!( + event.resolve_service_name( + &specific_service_mapping, + &event.get_specific_identifier(), + "sqs", + false // aws_service_representation_enabled = false + ), + "specific-service" + ); + + // Test 2: With generic mapping - still respects mapping + let generic_service_mapping = + HashMap::from([("lambda_sqs".to_string(), "generic-service".to_string())]); + assert_eq!( + event.resolve_service_name( + &generic_service_mapping, + &event.get_specific_identifier(), + "sqs", + false // aws_service_representation_enabled = false + ), + "generic-service" + ); + + // Test 3: When no mapping exists, uses fallback value + let empty_mapping = HashMap::new(); + assert_eq!( + event.resolve_service_name( + &empty_mapping, + &event.get_specific_identifier(), + "sqs", + false // aws_service_representation_enabled = false + ), + "sqs" // fallback value + ); } #[test] diff --git a/bottlecap/src/lifecycle/invocation/triggers/step_function_event.rs b/bottlecap/src/lifecycle/invocation/triggers/step_function_event.rs index 1f937905e..aaf5cfe2b 100644 --- a/bottlecap/src/lifecycle/invocation/triggers/step_function_event.rs +++ b/bottlecap/src/lifecycle/invocation/triggers/step_function_event.rs @@ -638,4 +638,76 @@ mod tests { assert_eq!(hex_tid, "1914fe7789eb32be"); } + + #[test] + fn test_resolve_service_name_with_representation_enabled() { + let json = read_json_file("step_function_event.json"); + let payload = serde_json::from_str(&json).expect("Failed to deserialize into Value"); + let event = + StepFunctionEvent::new(payload).expect("Failed to deserialize StepFunctionEvent"); + + // Test 1: Generic mapping is used for Step Functions + let generic_service_mapping = HashMap::from([( + "lambda_stepfunction".to_string(), + "generic-service".to_string(), + )]); + + assert_eq!( + event.resolve_service_name( + &generic_service_mapping, + "stepfunction", + "stepfunction", + true // aws_service_representation_enabled + ), + "generic-service" + ); + + // Test 2: When no mapping exists, uses instance name + let empty_mapping = HashMap::new(); + assert_eq!( + event.resolve_service_name( + &empty_mapping, + "stepfunction", + "stepfunction", + true // aws_service_representation_enabled + ), + "stepfunction" // instance name + ); + } + + #[test] + fn test_resolve_service_name_with_representation_disabled() { + let json = read_json_file("step_function_event.json"); + let payload = serde_json::from_str(&json).expect("Failed to deserialize into Value"); + let event = + StepFunctionEvent::new(payload).expect("Failed to deserialize StepFunctionEvent"); + + // Test 1: With generic mapping - still respects mapping + let generic_service_mapping = HashMap::from([( + "lambda_stepfunction".to_string(), + "generic-service".to_string(), + )]); + + assert_eq!( + event.resolve_service_name( + &generic_service_mapping, + "stepfunction", + "stepfunction", + false // aws_service_representation_enabled = false + ), + "generic-service" + ); + + // Test 2: When no mapping exists, uses fallback value + let empty_mapping = HashMap::new(); + assert_eq!( + event.resolve_service_name( + &empty_mapping, + "stepfunction", + "stepfunction", + false // aws_service_representation_enabled = false + ), + "stepfunction" // fallback value + ); + } } From 9731e9b0afd6fd9235dda16f4ddc1bff0d7786c1 Mon Sep 17 00:00:00 2001 From: Zarir Hamza Date: Thu, 7 Aug 2025 15:10:23 -0400 Subject: [PATCH 15/15] move span tag to generation function --- bottlecap/src/lifecycle/invocation/mod.rs | 10 ++++++++-- bottlecap/src/lifecycle/invocation/processor.rs | 9 --------- 2 files changed, 8 insertions(+), 11 deletions(-) diff --git a/bottlecap/src/lifecycle/invocation/mod.rs b/bottlecap/src/lifecycle/invocation/mod.rs index 70cca8efd..34d264b5f 100644 --- a/bottlecap/src/lifecycle/invocation/mod.rs +++ b/bottlecap/src/lifecycle/invocation/mod.rs @@ -32,13 +32,19 @@ pub fn base64_to_string(base64_string: &str) -> Result { } fn create_empty_span(name: String, resource: &str, service: &str) -> Span { - Span { + let mut span = Span { name, resource: resource.to_string(), service: service.to_string(), r#type: String::from("serverless"), ..Default::default() - } + }; + + // Add span.kind to the span to enable other server based features for serverless + span.meta + .insert("span.kind".to_string(), "server".to_string()); + + span } #[must_use] diff --git a/bottlecap/src/lifecycle/invocation/processor.rs b/bottlecap/src/lifecycle/invocation/processor.rs index 7ec0c91cf..88a30d343 100644 --- a/bottlecap/src/lifecycle/invocation/processor.rs +++ b/bottlecap/src/lifecycle/invocation/processor.rs @@ -248,10 +248,6 @@ impl Processor { ); cold_start_span.span_id = generate_span_id(); cold_start_span.start = start_time; - cold_start_span - .meta - .insert("span.kind".to_string(), "server".to_string()); - context.cold_start_span = Some(cold_start_span); } @@ -411,11 +407,6 @@ impl Processor { .meta .extend(self.dynamic_tags.clone()); - context - .invocation_span - .meta - .insert("span.kind".to_string(), "server".to_string()); - if let Some(trigger_tags) = self.inferrer.get_trigger_tags() { context.invocation_span.meta.extend(trigger_tags); }