Skip to content

Commit a933a6a

Browse files
uttam282005sdangol
andauthored
fix(metrics): emit warning when default dimensions are overwritten (#4222)
Co-authored-by: Swopnil Dangol <[email protected]>
1 parent d17818e commit a933a6a

File tree

4 files changed

+144
-8
lines changed

4 files changed

+144
-8
lines changed

packages/metrics/src/Metrics.ts

Lines changed: 35 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,9 @@
11
import { Console } from 'node:console';
22
import {
33
isIntegerNumber,
4+
isNullOrUndefined,
45
isNumber,
6+
isRecord,
57
isString,
68
isStringUndefinedNullEmpty,
79
Utility,
@@ -240,8 +242,10 @@ class Metrics extends Utility implements MetricsInterface {
240242
super();
241243

242244
this.dimensions = {};
243-
this.setOptions(options);
245+
this.setEnvConfig();
246+
this.setConsole();
244247
this.#logger = options.logger || this.console;
248+
this.setOptions(options);
245249
}
246250

247251
/**
@@ -824,13 +828,41 @@ class Metrics extends Utility implements MetricsInterface {
824828
* @param dimensions - The dimensions to be added to the default dimensions object
825829
*/
826830
public setDefaultDimensions(dimensions: Dimensions | undefined): void {
831+
if (isNullOrUndefined(dimensions) || !isRecord(dimensions)) {
832+
return;
833+
}
834+
835+
const cleanedDimensions: Dimensions = {};
836+
837+
for (const [key, value] of Object.entries(dimensions)) {
838+
if (
839+
isStringUndefinedNullEmpty(key) ||
840+
isStringUndefinedNullEmpty(value)
841+
) {
842+
this.#logger.warn(
843+
`The dimension ${key} doesn't meet the requirements and won't be added. Ensure the dimension name and value are non empty strings`
844+
);
845+
continue;
846+
}
847+
848+
if (Object.hasOwn(this.defaultDimensions, key)) {
849+
this.#logger.warn(
850+
`Dimension "${key}" has already been added. The previous value will be overwritten.`
851+
);
852+
}
853+
854+
cleanedDimensions[key] = value;
855+
}
856+
827857
const targetDimensions = {
828858
...this.defaultDimensions,
829-
...dimensions,
859+
...cleanedDimensions,
830860
};
831-
if (MAX_DIMENSION_COUNT <= Object.keys(targetDimensions).length) {
861+
862+
if (Object.keys(targetDimensions).length >= MAX_DIMENSION_COUNT) {
832863
throw new Error('Max dimension count hit');
833864
}
865+
834866
this.defaultDimensions = targetDimensions;
835867
}
836868

@@ -1058,8 +1090,6 @@ class Metrics extends Utility implements MetricsInterface {
10581090
functionName,
10591091
} = options;
10601092

1061-
this.setEnvConfig();
1062-
this.setConsole();
10631093
this.setCustomConfigService(customConfigService);
10641094
this.setDisabled();
10651095
this.setNamespace(namespace);

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

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -48,7 +48,9 @@ describe('Setting custom timestamp', () => {
4848

4949
it('logs a warning when the provided timestamp is too far in the past', () => {
5050
// Prepare
51-
const metrics = new Metrics({ singleMetric: true });
51+
const metrics = new Metrics({
52+
singleMetric: true,
53+
});
5254

5355
// Act
5456
metrics.setTimestamp(Date.now() - EMF_MAX_TIMESTAMP_PAST_AGE - 1000);
@@ -63,7 +65,9 @@ describe('Setting custom timestamp', () => {
6365

6466
it('logs a warning when the provided timestamp is too far in the future', () => {
6567
// Prepare
66-
const metrics = new Metrics({ singleMetric: true });
68+
const metrics = new Metrics({
69+
singleMetric: true,
70+
});
6771

6872
// Act
6973
metrics.setTimestamp(Date.now() + EMF_MAX_TIMESTAMP_FUTURE_AGE + 1000);

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

Lines changed: 100 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -552,4 +552,104 @@ describe('Working with dimensions', () => {
552552
})
553553
);
554554
});
555+
556+
it('warns when setDefaultDimensions overwrites existing dimensions', () => {
557+
// Prepare
558+
const metrics = new Metrics({
559+
namespace: DEFAULT_NAMESPACE,
560+
defaultDimensions: { environment: 'prod' },
561+
});
562+
563+
// Act
564+
metrics.setDefaultDimensions({ region: 'us-east-1' });
565+
metrics.setDefaultDimensions({
566+
environment: 'staging', // overwrites default dimension
567+
});
568+
569+
// Assess
570+
expect(console.warn).toHaveBeenCalledOnce();
571+
expect(console.warn).toHaveBeenCalledWith(
572+
'Dimension "environment" has already been added. The previous value will be overwritten.'
573+
);
574+
});
575+
576+
it('returns immediately if dimensions is undefined', () => {
577+
// Prepare
578+
const metrics = new Metrics({
579+
singleMetric: true,
580+
namespace: DEFAULT_NAMESPACE,
581+
});
582+
583+
// Act
584+
metrics.addMetric('myMetric', MetricUnit.Count, 1);
585+
586+
// Assert
587+
expect(console.warn).not.toHaveBeenCalled();
588+
589+
expect(console.log).toHaveEmittedEMFWith(
590+
expect.objectContaining({
591+
service: 'hello-world',
592+
})
593+
);
594+
});
595+
596+
it.each([
597+
{ value: undefined, name: 'valid-name' },
598+
{ value: null, name: 'valid-name' },
599+
{ value: '', name: 'valid-name' },
600+
{ value: 'valid-value', name: '' },
601+
])(
602+
'skips invalid default dimension values in setDefaultDimensions ($name)',
603+
({ value, name }) => {
604+
// Arrange
605+
const metrics = new Metrics({
606+
singleMetric: true,
607+
namespace: DEFAULT_NAMESPACE,
608+
});
609+
610+
// Act
611+
metrics.setDefaultDimensions({
612+
validDimension: 'valid',
613+
[name as string]: value as string,
614+
});
615+
616+
metrics.addMetric('test', MetricUnit.Count, 1);
617+
metrics.publishStoredMetrics();
618+
619+
// Assert
620+
expect(console.warn).toHaveBeenCalledWith(
621+
`The dimension ${name} doesn't meet the requirements and won't be added. Ensure the dimension name and value are non empty strings`
622+
);
623+
624+
expect(console.log).toHaveEmittedEMFWith(
625+
expect.objectContaining({ validDimension: 'valid' })
626+
);
627+
628+
expect(console.log).toHaveEmittedEMFWith(
629+
expect.not.objectContaining({ [name]: value })
630+
);
631+
}
632+
);
633+
it('returns immediately without logging if dimensions is not a plain object', () => {
634+
// Prepare
635+
const metrics = new Metrics({
636+
singleMetric: true,
637+
namespace: DEFAULT_NAMESPACE,
638+
});
639+
640+
// Act
641+
// @ts-expect-error – simulate runtime misuse
642+
metrics.setDefaultDimensions('not-an-object');
643+
644+
// Assert
645+
expect(console.warn).not.toHaveBeenCalled();
646+
647+
metrics.addMetric('someMetric', MetricUnit.Count, 1);
648+
649+
expect(console.log).toHaveEmittedEMFWith(
650+
expect.objectContaining({
651+
service: 'hello-world',
652+
})
653+
);
654+
});
555655
});

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

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -65,7 +65,9 @@ describe('Initialize Metrics', () => {
6565

6666
it('uses the default namespace when none is provided', () => {
6767
// Prepare
68-
const metrics = new Metrics({ singleMetric: true });
68+
const metrics = new Metrics({
69+
singleMetric: true,
70+
});
6971

7072
// Act
7173
metrics.addMetric('test', MetricUnit.Count, 1);

0 commit comments

Comments
 (0)