@@ -63,13 +63,13 @@ public abstract class BaseEmfExporterTest<T> {
6363
6464 abstract LogEventEmitter <T > createEmitter ();
6565
66- abstract MetricExporter createExporter ( );
66+ abstract MetricExporter buildExporter ( boolean shouldAddAppSignals );
6767
6868 @ BeforeEach
6969 void setup () {
7070 capturedLogEvents = new ArrayList <>();
7171 mockEmitter = createEmitter ();
72- exporter = createExporter ( );
72+ exporter = buildExporter ( false );
7373 metricData = mock (MetricData .class );
7474
7575 doAnswer (
@@ -264,6 +264,52 @@ void testExponentialHistogramMetricProcessing() {
264264 }
265265 }
266266
267+ @ ParameterizedTest
268+ @ MethodSource ("applicationSignalsDimensionsProvider" )
269+ void testApplicationSignalsDimensions (
270+ Map <String , String > resourceAttrs , String expectedService , String expectedEnvironment ) {
271+ MetricExporter exporterWithAppSignals = buildExporter (true );
272+
273+ Resource resource = Resource .empty ();
274+ for (Map .Entry <String , String > entry : resourceAttrs .entrySet ()) {
275+ resource = resource .toBuilder ().put (entry .getKey (), entry .getValue ()).build ();
276+ }
277+
278+ GaugeData gaugeData = mock (GaugeData .class );
279+ DoublePointData pointData = mock (DoublePointData .class );
280+ when (pointData .getValue ()).thenReturn (10.0 );
281+ when (pointData .getAttributes ()).thenReturn (Attributes .empty ());
282+ when (pointData .getEpochNanos ()).thenReturn (timestampCounter += 1_000_000 );
283+
284+ when (metricData .getName ()).thenReturn ("test.metric" );
285+ when (metricData .getUnit ()).thenReturn ("1" );
286+ when (metricData .getData ()).thenReturn (gaugeData );
287+ when (metricData .getResource ()).thenReturn (resource );
288+ when (gaugeData .getPoints ()).thenReturn (Collections .singletonList (pointData ));
289+
290+ CompletableResultCode result =
291+ exporterWithAppSignals .export (Collections .singletonList (metricData ));
292+
293+ assertTrue (result .isSuccess ());
294+ assertEquals (1 , capturedLogEvents .size ());
295+
296+ Map <String , Object > emfLog =
297+ validateEmfStructure (capturedLogEvents .get (0 ), "test.metric" ).orElseThrow ();
298+
299+ assertEquals (expectedService , emfLog .get ("Service" ));
300+ assertEquals (expectedEnvironment , emfLog .get ("Environment" ));
301+
302+ Map <String , Object > awsMetadata = (Map <String , Object >) emfLog .get ("_aws" );
303+ List <Map <String , Object >> cloudWatchMetrics =
304+ (List <Map <String , Object >>) awsMetadata .get ("CloudWatchMetrics" );
305+ Map <String , Object > metricGroup = cloudWatchMetrics .get (0 );
306+ List <List <String >> dimensions = (List <List <String >>) metricGroup .get ("Dimensions" );
307+
308+ List <String > dimensionNames = dimensions .get (0 );
309+ assertTrue (dimensionNames .contains ("Service" ));
310+ assertTrue (dimensionNames .contains ("Environment" ));
311+ }
312+
267313 @ ParameterizedTest
268314 @ MethodSource ("metricsGroupingProvider" )
269315 void testGroupByAttributesAndTimestamp (List <MetricData > metrics , int expectedLogCount ) {
@@ -338,6 +384,57 @@ private List<Number> generateRandomNumbers(int count) {
338384 return values ;
339385 }
340386
387+ static List <Arguments > applicationSignalsDimensionsProvider () {
388+ return Arrays .asList (
389+ // Both service.name and deployment.environment.name provided
390+ Arguments .of (
391+ Map .of ("service.name" , "test-service" , "deployment.environment.name" , "prod" ),
392+ "test-service" ,
393+ "prod" ),
394+ // Only service.name provided, environment defaults to generic:default
395+ Arguments .of (Map .of ("service.name" , "test-service" ), "test-service" , "generic:default" ),
396+ // No service.name, defaults to UnknownService
397+ Arguments .of (Map .of ("deployment.environment.name" , "staging" ), "UnknownService" , "staging" ),
398+ // Empty service.name, defaults to UnknownService
399+ Arguments .of (
400+ Map .of ("service.name" , "" , "deployment.environment.name" , "dev" ),
401+ "UnknownService" ,
402+ "dev" ),
403+ // No attributes, both default
404+ Arguments .of (Map .of (), "UnknownService" , "generic:default" ),
405+ // cloud.platform=aws_ec2, environment defaults to ec2:default
406+ Arguments .of (
407+ Map .of ("service.name" , "ec2-service" , "cloud.platform" , "aws_ec2" ),
408+ "ec2-service" ,
409+ "ec2:default" ),
410+ // cloud.platform=aws_ecs, environment defaults to ecs:default
411+ Arguments .of (
412+ Map .of ("service.name" , "ecs-service" , "cloud.platform" , "aws_ecs" ),
413+ "ecs-service" ,
414+ "ecs:default" ),
415+ // cloud.platform=aws_eks, environment defaults to eks:default
416+ Arguments .of (
417+ Map .of ("service.name" , "eks-service" , "cloud.platform" , "aws_eks" ),
418+ "eks-service" ,
419+ "eks:default" ),
420+ // cloud.platform=aws_lambda, environment defaults to lambda:default
421+ Arguments .of (
422+ Map .of ("service.name" , "lambda-service" , "cloud.platform" , "aws_lambda" ),
423+ "lambda-service" ,
424+ "lambda:default" ),
425+ // deployment.environment.name takes precedence over cloud.platform
426+ Arguments .of (
427+ Map .of (
428+ "service.name" ,
429+ "override-service" ,
430+ "deployment.environment.name" ,
431+ "custom-env" ,
432+ "cloud.platform" ,
433+ "aws_ec2" ),
434+ "override-service" ,
435+ "custom-env" ));
436+ }
437+
341438 static List <Arguments > metricsGroupingProvider () {
342439 Attributes attrs1 = Attributes .builder ().put ("env" , "prod" ).build ();
343440 Attributes attrs2 = Attributes .builder ().put ("env" , "dev" ).build ();
0 commit comments