Skip to content

Commit 6278aa8

Browse files
committed
fix: addDimensions() now creates a new dimension set (fixes #3777)
1 parent 2a60d86 commit 6278aa8

File tree

3 files changed

+164
-10
lines changed

3 files changed

+164
-10
lines changed

packages/metrics/src/Metrics.ts

Lines changed: 45 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -154,6 +154,12 @@ class Metrics extends Utility implements MetricsInterface {
154154
*/
155155
private dimensions: Dimensions = {};
156156

157+
/**
158+
* Additional dimension sets for the current metrics context
159+
* @default []
160+
*/
161+
private dimensionSets: Dimensions[] = [];
162+
157163
/**
158164
* Service for accessing environment variables
159165
*/
@@ -267,9 +273,15 @@ class Metrics extends Utility implements MetricsInterface {
267273
* @param dimensions - An object with key-value pairs of dimensions
268274
*/
269275
public addDimensions(dimensions: Dimensions): void {
270-
for (const [name, value] of Object.entries(dimensions)) {
271-
this.addDimension(name, value);
276+
if (
277+
Object.keys(dimensions).length + this.getCurrentDimensionsCount() >=
278+
MAX_DIMENSION_COUNT
279+
) {
280+
throw new RangeError(
281+
`The number of metric dimensions must be lower than ${MAX_DIMENSION_COUNT}`
282+
);
272283
}
284+
this.dimensionSets.push(dimensions);
273285
}
274286

275287
/**
@@ -447,6 +459,7 @@ class Metrics extends Utility implements MetricsInterface {
447459
*/
448460
public clearDimensions(): void {
449461
this.dimensions = {};
462+
this.dimensionSets = [];
450463
}
451464

452465
/**
@@ -692,26 +705,49 @@ class Metrics extends Utility implements MetricsInterface {
692705
{}
693706
);
694707

695-
const dimensionNames = [
696-
...new Set([
697-
...Object.keys(this.defaultDimensions),
698-
...Object.keys(this.dimensions),
699-
]),
700-
];
708+
const dimensionNames = [];
709+
710+
if (Object.keys(this.dimensions).length > 0) {
711+
dimensionNames.push([
712+
...new Set([
713+
...Object.keys(this.defaultDimensions),
714+
...Object.keys(this.dimensions),
715+
]),
716+
]);
717+
}
718+
719+
for (const dimensionSet of this.dimensionSets) {
720+
dimensionNames.push([
721+
...new Set([
722+
...Object.keys(this.defaultDimensions),
723+
...Object.keys(dimensionSet),
724+
]),
725+
]);
726+
}
727+
728+
if (
729+
dimensionNames.length === 0 &&
730+
Object.keys(this.defaultDimensions).length > 0
731+
) {
732+
dimensionNames.push([
733+
...new Set([...Object.keys(this.defaultDimensions)]),
734+
]);
735+
}
701736

702737
return {
703738
_aws: {
704739
Timestamp: this.#timestamp ?? new Date().getTime(),
705740
CloudWatchMetrics: [
706741
{
707742
Namespace: this.namespace || DEFAULT_NAMESPACE,
708-
Dimensions: [dimensionNames],
743+
Dimensions: dimensionNames as [string[]],
709744
Metrics: metricDefinitions,
710745
},
711746
],
712747
},
713748
...this.defaultDimensions,
714749
...this.dimensions,
750+
...this.dimensionSets.reduce((acc, dims) => Object.assign(acc, dims), {}),
715751
...metricValues,
716752
...this.metadata,
717753
};

packages/metrics/src/types/Metrics.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -190,8 +190,8 @@ interface MetricsInterface {
190190
* included in all metrics, use the {@link MetricsInterface.setDefaultDimensions | `setDefaultDimensions()`} method.
191191
*
192192
* @param dimensions - An object with key-value pairs of dimensions
193+
* @returns The dimensions object that was passed in
193194
*/
194-
addDimensions(dimensions: Dimensions): void;
195195
/**
196196
* A metadata key-value pair to be included with metrics.
197197
*

packages/metrics/tests/unit/dimensions.test.ts

Lines changed: 118 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,42 @@ describe('Working with dimensions', () => {
1414
vi.clearAllMocks();
1515
});
1616

17+
it('creates a new dimension set', () => {
18+
// Prepare
19+
const metrics = new Metrics({
20+
namespace: DEFAULT_NAMESPACE,
21+
});
22+
23+
// Act
24+
metrics.addDimension('environment', 'prod');
25+
26+
metrics.addDimensions({
27+
dimension1: '1',
28+
dimension2: '2',
29+
});
30+
31+
metrics.addMetric('foo', MetricUnit.Count, 1);
32+
metrics.publishStoredMetrics();
33+
34+
// Assess
35+
expect(console.log).toHaveEmittedEMFWith(
36+
expect.objectContaining({
37+
environment: 'prod',
38+
dimension1: '1',
39+
dimension2: '2',
40+
foo: 1,
41+
})
42+
);
43+
expect(console.log).toHaveEmittedMetricWith(
44+
expect.objectContaining({
45+
Dimensions: [
46+
['service', 'environment'],
47+
['service', 'dimension1', 'dimension2'],
48+
],
49+
})
50+
);
51+
});
52+
1753
it('adds default dimensions to the metric via constructor', () => {
1854
// Prepare
1955
const metrics = new Metrics({
@@ -284,6 +320,88 @@ describe('Working with dimensions', () => {
284320
);
285321
});
286322

323+
it('throws when adding dimension sets would exceed the limit', () => {
324+
// Prepare
325+
const metrics = new Metrics({
326+
singleMetric: true,
327+
defaultDimensions: {
328+
environment: 'test',
329+
},
330+
});
331+
332+
// Act & Assess
333+
let i = 1;
334+
// We start with 2 dimensions because the default dimension & service name are already added
335+
for (i = 2; i < MAX_DIMENSION_COUNT - 2; i++) {
336+
metrics.addDimension(`dimension-${i}`, 'test');
337+
}
338+
339+
// Adding a dimension set with 3 dimensions would exceed the limit
340+
expect(() =>
341+
metrics.addDimensions({
342+
'dimension-extra-1': 'test',
343+
'dimension-extra-2': 'test',
344+
'dimension-extra-3': 'test',
345+
})
346+
).toThrowError(
347+
`The number of metric dimensions must be lower than ${MAX_DIMENSION_COUNT}`
348+
);
349+
});
350+
351+
it('handles dimension overrides across multiple dimension sets', () => {
352+
// Prepare
353+
const metrics = new Metrics({
354+
namespace: DEFAULT_NAMESPACE,
355+
});
356+
357+
// Act
358+
// First add a single dimension
359+
metrics.addDimension('d', '3');
360+
361+
// First dimension set
362+
metrics.addDimensions({
363+
a: '1',
364+
b: '2',
365+
});
366+
367+
// Second dimension set with some overlapping keys
368+
metrics.addDimensions({
369+
a: '3',
370+
c: '5',
371+
d: '8',
372+
});
373+
374+
// Third dimension set with more overlapping keys
375+
metrics.addDimensions({
376+
b: '5',
377+
d: '1',
378+
});
379+
380+
metrics.addMetric('foo', MetricUnit.Count, 1);
381+
metrics.publishStoredMetrics();
382+
383+
// Assess
384+
expect(console.log).toHaveEmittedEMFWith(
385+
expect.objectContaining({
386+
a: '3', // Last value from second set
387+
b: '5', // Last value from third set
388+
c: '5', // Only value from second set
389+
d: '1', // Last value from third set (overriding both the initial d:3 and second set's d:8)
390+
foo: 1,
391+
})
392+
);
393+
expect(console.log).toHaveEmittedMetricWith(
394+
expect.objectContaining({
395+
Dimensions: [
396+
['service', 'd'],
397+
['service', 'a', 'b'],
398+
['service', 'a', 'c', 'd'],
399+
['service', 'b', 'd'],
400+
],
401+
})
402+
);
403+
});
404+
287405
it.each([
288406
{ value: undefined, name: 'undefined' },
289407
{ value: null, name: 'null' },

0 commit comments

Comments
 (0)