Skip to content

Commit 164952a

Browse files
authored
Fix EMF exporter to generate EMF metrics when dimensions are empty due to no attributes (#228)
*Description of changes:* Populate the correct `CloudWatchMetrics` in EMF when metrics has empty dimension list due to no attributes. *Testing:* ```json { "_aws": { "Timestamp": 1753213216039, "CloudWatchMetrics": [ { "Namespace": "default", "Metrics": [ { "Name": "histogram.counter", "Unit": "Milliseconds" } ] } ] }, "Version": "1", "otel.resource.service.name": "unknown_service:node", "otel.resource.telemetry.sdk.language": "nodejs", "otel.resource.telemetry.sdk.name": "opentelemetry", "otel.resource.telemetry.sdk.version": "1.30.1", "otel.resource.telemetry.auto.version": "0.6.0-dev0-aws", "otel.resource.aws.service.type": "gen_ai_agent", "histogram.counter": { "Values": [ 0.9946140065969877, 1.9892280131939772, 3.002072155884692, 3.978456026387959, 0 ], "Counts": [ 1, 1, 2, 3, 2 ], "Count": 9, "Sum": 21, "Max": 4, "Min": 0 } } ``` By submitting this pull request, I confirm that you can use, modify, copy, and redistribute this contribution, under the terms of your choice.
1 parent 62d0c23 commit 164952a

File tree

2 files changed

+51
-10
lines changed

2 files changed

+51
-10
lines changed

aws-distro-opentelemetry-node-autoinstrumentation/src/exporter/aws/metrics/aws-cloudwatch-emf-exporter.ts

Lines changed: 12 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -71,7 +71,7 @@ interface _Aws {
7171

7272
interface CloudWatchMetric {
7373
Namespace: string;
74-
Dimensions: string[][];
74+
Dimensions?: string[][];
7575
Metrics: Metric[];
7676
}
7777

@@ -449,16 +449,23 @@ export class AWSCloudWatchEMFExporter implements PushMetricExporter {
449449

450450
const dimensionNames = this.getDimensionNames(allAttributes);
451451

452+
// Add attribute values to the root of the EMF log
452453
for (const [name, value] of Object.entries(allAttributes)) {
453454
emfLog[name] = value?.toString() ?? 'undefined';
454455
}
455456

456-
if (dimensionNames && metricDefinitions.length > 0) {
457-
emfLog._aws.CloudWatchMetrics.push({
457+
// Add CloudWatch Metrics if we have metrics, include dimensions only if they exist
458+
if (metricDefinitions.length > 0) {
459+
const cloudWatchMetric: CloudWatchMetric = {
458460
Namespace: this.namespace,
459-
Dimensions: [dimensionNames],
460461
Metrics: metricDefinitions,
461-
});
462+
};
463+
464+
if (dimensionNames.length > 0) {
465+
cloudWatchMetric.Dimensions = [dimensionNames];
466+
}
467+
468+
emfLog._aws.CloudWatchMetrics.push(cloudWatchMetric);
462469
}
463470

464471
return emfLog;

aws-distro-opentelemetry-node-autoinstrumentation/test/exporter/aws/metrics/aws-cloudwatch-emf-exporter.test.ts

Lines changed: 39 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -412,7 +412,7 @@ describe('TestAwsCloudWatchEmfExporter', () => {
412412

413413
expect(result).toHaveProperty('_aws');
414414
expect(result._aws.CloudWatchMetrics[0].Namespace).toEqual('TestNamespace');
415-
expect(result._aws.CloudWatchMetrics[0].Dimensions[0][0]).toEqual('env');
415+
expect(result._aws.CloudWatchMetrics[0].Dimensions![0][0]).toEqual('env');
416416
expect(result._aws.CloudWatchMetrics[0].Metrics[0].Name).toEqual('gauge_metric');
417417
expect(result._aws.CloudWatchMetrics[0].Metrics[0].Unit).toEqual('Count');
418418
expect(result._aws.CloudWatchMetrics[0].Metrics[1].Name).toEqual('sum_metric');
@@ -672,10 +672,10 @@ describe('TestAwsCloudWatchEmfExporter', () => {
672672

673673
expect(call1Args.logEvents.length).toEqual(0);
674674
expect(call2Args.logEvents[0].message).toMatch(
675-
/^\{"_aws":\{"Timestamp":\d+,"CloudWatchMetrics":\[\{"Namespace":"TestNamespace","Dimensions":\[\["uniqueKey1"\]\],"Metrics":\[\{"Name":"descriptorName","Unit":"Milliseconds"\}\]\}\]},"Version":"1","descriptorName":3,"uniqueKey1":"uniqueValue1"\}$/
675+
/^\{"_aws":\{"Timestamp":\d+,"CloudWatchMetrics":\[\{"Namespace":"TestNamespace","Metrics":\[\{"Name":"descriptorName","Unit":"Milliseconds"\}\],"Dimensions":\[\["uniqueKey1"\]\]\}\]},"Version":"1","descriptorName":3,"uniqueKey1":"uniqueValue1"\}$/
676676
);
677677
expect(call3Args.logEvents[0].message).toMatch(
678-
/^\{"_aws":\{"Timestamp":\d+,"CloudWatchMetrics":\[\{"Namespace":"TestNamespace","Dimensions":\[\["uniqueKey2"\]\],"Metrics":\[\{"Name":"descriptorName","Unit":"Milliseconds"\}\]\}\]},"Version":"1","descriptorName":9,"uniqueKey2":"uniqueValue2"\}$/
678+
/^\{"_aws":\{"Timestamp":\d+,"CloudWatchMetrics":\[\{"Namespace":"TestNamespace","Metrics":\[\{"Name":"descriptorName","Unit":"Milliseconds"\}\],"Dimensions":\[\["uniqueKey2"\]\]\}\]},"Version":"1","descriptorName":9,"uniqueKey2":"uniqueValue2"\}$/
679679
);
680680
resolve();
681681
});
@@ -747,8 +747,42 @@ describe('TestAwsCloudWatchEmfExporter', () => {
747747
// Check CloudWatch metrics structure
748748
const cwMetrics = result._aws.CloudWatchMetrics[0];
749749
expect(cwMetrics.Namespace).toEqual('TestNamespace');
750-
expect(cwMetrics.Dimensions[0]).toContain('env');
751-
expect(cwMetrics.Dimensions[0]).toContain('service');
750+
expect(cwMetrics).toHaveProperty('Dimensions');
751+
expect(cwMetrics.Dimensions![0]).toContain('env');
752+
expect(cwMetrics.Dimensions![0]).toContain('service');
753+
expect(cwMetrics.Metrics[0].Name).toEqual('gauge_metric');
754+
});
755+
756+
it('TestCreateEmfLogWithoutDimensions', () => {
757+
/* Test EMF log creation with metrics but no dimensions. */
758+
// Create test record with empty attributes (no dimensions)
759+
const gaugeRecord: MetricRecord = {
760+
...exporter['createMetricRecord']('gauge_metric', 'Count', 'Gauge', Date.now(), {}),
761+
value: 75.0,
762+
};
763+
764+
const records = [gaugeRecord];
765+
const resource = new Resource({ 'service.name': 'test-service', 'service.version': '1.0.0' });
766+
767+
const result = exporter['createEmfLog'](records, resource, 1234567890);
768+
769+
// Verify EMF log structure
770+
expect(result).toHaveProperty('_aws');
771+
expect(result._aws).toHaveProperty('CloudWatchMetrics');
772+
expect(result._aws.Timestamp).toEqual(1234567890);
773+
expect(result.Version).toEqual('1');
774+
775+
// Check resource attributes are prefixed
776+
expect(result['otel.resource.service.name']).toEqual('test-service');
777+
expect(result['otel.resource.service.version']).toEqual('1.0.0');
778+
779+
// Check metric value
780+
expect(result.gauge_metric).toEqual(75.0);
781+
782+
// Check CloudWatch metrics structure
783+
const cwMetrics = result._aws.CloudWatchMetrics[0];
784+
expect(cwMetrics.Namespace).toEqual('TestNamespace');
785+
expect(cwMetrics).not.toHaveProperty('Dimensions');
752786
expect(cwMetrics.Metrics[0].Name).toEqual('gauge_metric');
753787
});
754788

0 commit comments

Comments
 (0)