@@ -829,4 +829,255 @@ describe('TestAWSCloudWatchEMFExporter', () => {
829829 expect ( mockSendLogEvent . calledOnce ) . toBeTruthy ( ) ;
830830 expect ( mockSendLogEvent . calledWith ( logEvent ) ) . toBeTruthy ( ) ;
831831 } ) ;
832+
833+ describe ( 'Application Signals EMF Dimensions' , ( ) => {
834+ let savedAppSignalsEnabled : string | undefined ;
835+ let savedEmfExportEnabled : string | undefined ;
836+
837+ beforeEach ( ( ) => {
838+ // Save original env vars
839+ savedAppSignalsEnabled = process . env [ 'OTEL_AWS_APPLICATION_SIGNALS_ENABLED' ] ;
840+ savedEmfExportEnabled = process . env [ 'OTEL_AWS_APPLICATION_SIGNALS_EMF_EXPORT_ENABLED' ] ;
841+ } ) ;
842+
843+ afterEach ( ( ) => {
844+ // Restore original env vars
845+ if ( savedAppSignalsEnabled === undefined ) {
846+ delete process . env [ 'OTEL_AWS_APPLICATION_SIGNALS_ENABLED' ] ;
847+ } else {
848+ process . env [ 'OTEL_AWS_APPLICATION_SIGNALS_ENABLED' ] = savedAppSignalsEnabled ;
849+ }
850+ if ( savedEmfExportEnabled === undefined ) {
851+ delete process . env [ 'OTEL_AWS_APPLICATION_SIGNALS_EMF_EXPORT_ENABLED' ] ;
852+ } else {
853+ process . env [ 'OTEL_AWS_APPLICATION_SIGNALS_EMF_EXPORT_ENABLED' ] = savedEmfExportEnabled ;
854+ }
855+ } ) ;
856+
857+ it ( 'TestDimensionsNotAddedWhenFeatureDisabled' , ( ) => {
858+ /* Test that Service/Environment dimensions are NOT added when feature is disabled. */
859+ delete process . env [ 'OTEL_AWS_APPLICATION_SIGNALS_ENABLED' ] ;
860+ delete process . env [ 'OTEL_AWS_APPLICATION_SIGNALS_EMF_EXPORT_ENABLED' ] ;
861+
862+ const gaugeRecord : MetricRecord = {
863+ ...exporter [ 'createMetricRecord' ] ( 'test_metric' , 'Count' , 'Test' , Date . now ( ) , { env : 'test' } ) ,
864+ value : 50.0 ,
865+ } ;
866+
867+ const resource = new Resource ( { 'service.name' : 'my-service' } ) ;
868+ const result = exporter [ 'createEmfLog' ] ( [ gaugeRecord ] , resource , 1234567890 ) ;
869+
870+ // Should NOT have Service or Environment dimensions
871+ expect ( result ) . not . toHaveProperty ( 'Service' ) ;
872+ expect ( result ) . not . toHaveProperty ( 'Environment' ) ;
873+ const cwMetrics = result . _aws . CloudWatchMetrics [ 0 ] ;
874+ expect ( cwMetrics . Dimensions ! [ 0 ] ) . not . toContain ( 'Service' ) ;
875+ expect ( cwMetrics . Dimensions ! [ 0 ] ) . not . toContain ( 'Environment' ) ;
876+ } ) ;
877+
878+ it ( 'TestDimensionsAddedWhenBothEnvVarsEnabled' , ( ) => {
879+ /* Test that Service/Environment dimensions ARE added when both env vars are enabled. */
880+ process . env [ 'OTEL_AWS_APPLICATION_SIGNALS_ENABLED' ] = 'true' ;
881+ process . env [ 'OTEL_AWS_APPLICATION_SIGNALS_EMF_EXPORT_ENABLED' ] = 'true' ;
882+
883+ const gaugeRecord : MetricRecord = {
884+ ...exporter [ 'createMetricRecord' ] ( 'test_metric' , 'Count' , 'Test' , Date . now ( ) , { env : 'test' } ) ,
885+ value : 50.0 ,
886+ } ;
887+
888+ const resource = new Resource ( { 'service.name' : 'my-service' , 'deployment.environment' : 'production' } ) ;
889+ const result = exporter [ 'createEmfLog' ] ( [ gaugeRecord ] , resource , 1234567890 ) ;
890+
891+ // Should have Service and Environment dimensions
892+ expect ( result ) . toHaveProperty ( 'Service' , 'my-service' ) ;
893+ expect ( result ) . toHaveProperty ( 'Environment' , 'production' ) ;
894+ const cwMetrics = result . _aws . CloudWatchMetrics [ 0 ] ;
895+ expect ( cwMetrics . Dimensions ! [ 0 ] ) . toContain ( 'Service' ) ;
896+ expect ( cwMetrics . Dimensions ! [ 0 ] ) . toContain ( 'Environment' ) ;
897+ // Service should be first, Environment second
898+ expect ( cwMetrics . Dimensions ! [ 0 ] [ 0 ] ) . toEqual ( 'Service' ) ;
899+ expect ( cwMetrics . Dimensions ! [ 0 ] [ 1 ] ) . toEqual ( 'Environment' ) ;
900+ } ) ;
901+
902+ it ( 'TestDimensionsNotAddedWhenOnlyAppSignalsEnabled' , ( ) => {
903+ /* Test that dimensions are NOT added when only APPLICATION_SIGNALS_ENABLED is set. */
904+ process . env [ 'OTEL_AWS_APPLICATION_SIGNALS_ENABLED' ] = 'true' ;
905+ delete process . env [ 'OTEL_AWS_APPLICATION_SIGNALS_EMF_EXPORT_ENABLED' ] ;
906+
907+ const gaugeRecord : MetricRecord = {
908+ ...exporter [ 'createMetricRecord' ] ( 'test_metric' , 'Count' , 'Test' , Date . now ( ) , { env : 'test' } ) ,
909+ value : 50.0 ,
910+ } ;
911+
912+ const resource = new Resource ( { 'service.name' : 'my-service' } ) ;
913+ const result = exporter [ 'createEmfLog' ] ( [ gaugeRecord ] , resource , 1234567890 ) ;
914+
915+ expect ( result ) . not . toHaveProperty ( 'Service' ) ;
916+ expect ( result ) . not . toHaveProperty ( 'Environment' ) ;
917+ } ) ;
918+
919+ it ( 'TestDimensionsNotAddedWhenOnlyEmfExportEnabled' , ( ) => {
920+ /* Test that dimensions are NOT added when only EMF_EXPORT_ENABLED is set. */
921+ delete process . env [ 'OTEL_AWS_APPLICATION_SIGNALS_ENABLED' ] ;
922+ process . env [ 'OTEL_AWS_APPLICATION_SIGNALS_EMF_EXPORT_ENABLED' ] = 'true' ;
923+
924+ const gaugeRecord : MetricRecord = {
925+ ...exporter [ 'createMetricRecord' ] ( 'test_metric' , 'Count' , 'Test' , Date . now ( ) , { env : 'test' } ) ,
926+ value : 50.0 ,
927+ } ;
928+
929+ const resource = new Resource ( { 'service.name' : 'my-service' } ) ;
930+ const result = exporter [ 'createEmfLog' ] ( [ gaugeRecord ] , resource , 1234567890 ) ;
931+
932+ expect ( result ) . not . toHaveProperty ( 'Service' ) ;
933+ expect ( result ) . not . toHaveProperty ( 'Environment' ) ;
934+ } ) ;
935+
936+ it ( 'TestServiceDimensionNotOverwrittenCaseInsensitive' , ( ) => {
937+ /* Test that user-set Service dimension (any case) is NOT overwritten. */
938+ process . env [ 'OTEL_AWS_APPLICATION_SIGNALS_ENABLED' ] = 'true' ;
939+ process . env [ 'OTEL_AWS_APPLICATION_SIGNALS_EMF_EXPORT_ENABLED' ] = 'true' ;
940+
941+ // User sets 'service' (lowercase) as an attribute
942+ const gaugeRecord : MetricRecord = {
943+ ...exporter [ 'createMetricRecord' ] ( 'test_metric' , 'Count' , 'Test' , Date . now ( ) , { service : 'user-service' } ) ,
944+ value : 50.0 ,
945+ } ;
946+
947+ const resource = new Resource ( { 'service.name' : 'resource-service' } ) ;
948+ const result = exporter [ 'createEmfLog' ] ( [ gaugeRecord ] , resource , 1234567890 ) ;
949+
950+ // Should NOT add 'Service' dimension since 'service' already exists
951+ expect ( result ) . not . toHaveProperty ( 'Service' ) ;
952+ expect ( result ) . toHaveProperty ( 'service' , 'user-service' ) ;
953+ // Environment should still be added
954+ expect ( result ) . toHaveProperty ( 'Environment' , 'lambda:default' ) ;
955+ } ) ;
956+
957+ it ( 'TestEnvironmentDimensionNotOverwrittenCaseInsensitive' , ( ) => {
958+ /* Test that user-set Environment dimension (any case) is NOT overwritten. */
959+ process . env [ 'OTEL_AWS_APPLICATION_SIGNALS_ENABLED' ] = 'true' ;
960+ process . env [ 'OTEL_AWS_APPLICATION_SIGNALS_EMF_EXPORT_ENABLED' ] = 'true' ;
961+
962+ // User sets 'ENVIRONMENT' (uppercase) as an attribute
963+ const gaugeRecord : MetricRecord = {
964+ ...exporter [ 'createMetricRecord' ] ( 'test_metric' , 'Count' , 'Test' , Date . now ( ) , { ENVIRONMENT : 'user-env' } ) ,
965+ value : 50.0 ,
966+ } ;
967+
968+ const resource = new Resource ( { 'service.name' : 'my-service' , 'deployment.environment' : 'production' } ) ;
969+ const result = exporter [ 'createEmfLog' ] ( [ gaugeRecord ] , resource , 1234567890 ) ;
970+
971+ // Should NOT add 'Environment' dimension since 'ENVIRONMENT' already exists
972+ expect ( result ) . not . toHaveProperty ( 'Environment' ) ;
973+ expect ( result ) . toHaveProperty ( 'ENVIRONMENT' , 'user-env' ) ;
974+ // Service should still be added
975+ expect ( result ) . toHaveProperty ( 'Service' , 'my-service' ) ;
976+ } ) ;
977+
978+ it ( 'TestServiceFallbackToUnknownService' , ( ) => {
979+ /* Test that Service falls back to UnknownService when resource has no service.name. */
980+ process . env [ 'OTEL_AWS_APPLICATION_SIGNALS_ENABLED' ] = 'true' ;
981+ process . env [ 'OTEL_AWS_APPLICATION_SIGNALS_EMF_EXPORT_ENABLED' ] = 'true' ;
982+
983+ const gaugeRecord : MetricRecord = {
984+ ...exporter [ 'createMetricRecord' ] ( 'test_metric' , 'Count' , 'Test' , Date . now ( ) , { env : 'test' } ) ,
985+ value : 50.0 ,
986+ } ;
987+
988+ // Resource without service.name
989+ const resource = new Resource ( { } ) ;
990+ const result = exporter [ 'createEmfLog' ] ( [ gaugeRecord ] , resource , 1234567890 ) ;
991+
992+ expect ( result ) . toHaveProperty ( 'Service' , 'UnknownService' ) ;
993+ } ) ;
994+
995+ it ( 'TestServiceFallbackWhenUnknownServicePattern' , ( ) => {
996+ /* Test that Service falls back to UnknownService when resource has OTel default service name. */
997+ process . env [ 'OTEL_AWS_APPLICATION_SIGNALS_ENABLED' ] = 'true' ;
998+ process . env [ 'OTEL_AWS_APPLICATION_SIGNALS_EMF_EXPORT_ENABLED' ] = 'true' ;
999+
1000+ const gaugeRecord : MetricRecord = {
1001+ ...exporter [ 'createMetricRecord' ] ( 'test_metric' , 'Count' , 'Test' , Date . now ( ) , { env : 'test' } ) ,
1002+ value : 50.0 ,
1003+ } ;
1004+
1005+ // Resource with OTel default service name pattern
1006+ const { defaultServiceName } = require ( '@opentelemetry/resources' ) ;
1007+ const resource = new Resource ( { 'service.name' : defaultServiceName ( ) } ) ;
1008+ const result = exporter [ 'createEmfLog' ] ( [ gaugeRecord ] , resource , 1234567890 ) ;
1009+
1010+ expect ( result ) . toHaveProperty ( 'Service' , 'UnknownService' ) ;
1011+ } ) ;
1012+
1013+ it ( 'TestEnvironmentFallbackToLambdaDefault' , ( ) => {
1014+ /* Test that Environment falls back to lambda:default when not set in resource. */
1015+ process . env [ 'OTEL_AWS_APPLICATION_SIGNALS_ENABLED' ] = 'true' ;
1016+ process . env [ 'OTEL_AWS_APPLICATION_SIGNALS_EMF_EXPORT_ENABLED' ] = 'true' ;
1017+
1018+ const gaugeRecord : MetricRecord = {
1019+ ...exporter [ 'createMetricRecord' ] ( 'test_metric' , 'Count' , 'Test' , Date . now ( ) , { env : 'test' } ) ,
1020+ value : 50.0 ,
1021+ } ;
1022+
1023+ // Resource without deployment.environment
1024+ const resource = new Resource ( { 'service.name' : 'my-service' } ) ;
1025+ const result = exporter [ 'createEmfLog' ] ( [ gaugeRecord ] , resource , 1234567890 ) ;
1026+
1027+ expect ( result ) . toHaveProperty ( 'Environment' , 'lambda:default' ) ;
1028+ } ) ;
1029+
1030+ it ( 'TestEnvironmentExtractedFromResource' , ( ) => {
1031+ /* Test that Environment is extracted from deployment.environment resource attribute. */
1032+ process . env [ 'OTEL_AWS_APPLICATION_SIGNALS_ENABLED' ] = 'true' ;
1033+ process . env [ 'OTEL_AWS_APPLICATION_SIGNALS_EMF_EXPORT_ENABLED' ] = 'true' ;
1034+
1035+ const gaugeRecord : MetricRecord = {
1036+ ...exporter [ 'createMetricRecord' ] ( 'test_metric' , 'Count' , 'Test' , Date . now ( ) , { env : 'test' } ) ,
1037+ value : 50.0 ,
1038+ } ;
1039+
1040+ const resource = new Resource ( { 'service.name' : 'my-service' , 'deployment.environment' : 'staging' } ) ;
1041+ const result = exporter [ 'createEmfLog' ] ( [ gaugeRecord ] , resource , 1234567890 ) ;
1042+
1043+ expect ( result ) . toHaveProperty ( 'Environment' , 'staging' ) ;
1044+ } ) ;
1045+
1046+ it ( 'TestDimensionOrderServiceThenEnvironment' , ( ) => {
1047+ /* Test that Service comes before Environment in dimensions array. */
1048+ process . env [ 'OTEL_AWS_APPLICATION_SIGNALS_ENABLED' ] = 'true' ;
1049+ process . env [ 'OTEL_AWS_APPLICATION_SIGNALS_EMF_EXPORT_ENABLED' ] = 'true' ;
1050+
1051+ const gaugeRecord : MetricRecord = {
1052+ ...exporter [ 'createMetricRecord' ] ( 'test_metric' , 'Count' , 'Test' , Date . now ( ) , { existing_dim : 'value' } ) ,
1053+ value : 50.0 ,
1054+ } ;
1055+
1056+ const resource = new Resource ( { 'service.name' : 'my-service' , 'deployment.environment' : 'prod' } ) ;
1057+ const result = exporter [ 'createEmfLog' ] ( [ gaugeRecord ] , resource , 1234567890 ) ;
1058+
1059+ const cwMetrics = result . _aws . CloudWatchMetrics [ 0 ] ;
1060+ // Dimensions should be: ['Service', 'Environment', 'existing_dim']
1061+ expect ( cwMetrics . Dimensions ! [ 0 ] [ 0 ] ) . toEqual ( 'Service' ) ;
1062+ expect ( cwMetrics . Dimensions ! [ 0 ] [ 1 ] ) . toEqual ( 'Environment' ) ;
1063+ expect ( cwMetrics . Dimensions ! [ 0 ] [ 2 ] ) . toEqual ( 'existing_dim' ) ;
1064+ } ) ;
1065+
1066+ it ( 'TestEnvVarsCaseInsensitive' , ( ) => {
1067+ /* Test that env var values are case-insensitive (TRUE, True, true all work). */
1068+ process . env [ 'OTEL_AWS_APPLICATION_SIGNALS_ENABLED' ] = 'TRUE' ;
1069+ process . env [ 'OTEL_AWS_APPLICATION_SIGNALS_EMF_EXPORT_ENABLED' ] = 'True' ;
1070+
1071+ const gaugeRecord : MetricRecord = {
1072+ ...exporter [ 'createMetricRecord' ] ( 'test_metric' , 'Count' , 'Test' , Date . now ( ) , { env : 'test' } ) ,
1073+ value : 50.0 ,
1074+ } ;
1075+
1076+ const resource = new Resource ( { 'service.name' : 'my-service' } ) ;
1077+ const result = exporter [ 'createEmfLog' ] ( [ gaugeRecord ] , resource , 1234567890 ) ;
1078+
1079+ expect ( result ) . toHaveProperty ( 'Service' , 'my-service' ) ;
1080+ expect ( result ) . toHaveProperty ( 'Environment' , 'lambda:default' ) ;
1081+ } ) ;
1082+ } ) ;
8321083} ) ;
0 commit comments