Skip to content

Commit 4c1bcc3

Browse files
authored
improv(metrics): emit warning when default dimensions are overwritten (#4287)
1 parent a383256 commit 4c1bcc3

File tree

2 files changed

+120
-52
lines changed

2 files changed

+120
-52
lines changed

packages/metrics/src/Metrics.ts

Lines changed: 61 additions & 51 deletions
Original file line numberDiff line numberDiff line change
@@ -240,8 +240,10 @@ class Metrics extends Utility implements MetricsInterface {
240240
super();
241241

242242
this.dimensions = {};
243-
this.setOptions(options);
243+
this.setEnvConfig();
244+
this.setConsole();
244245
this.#logger = options.logger || this.console;
246+
this.setOptions(options);
245247
}
246248

247249
/**
@@ -293,38 +295,16 @@ class Metrics extends Utility implements MetricsInterface {
293295
* @param dimensions - An object with key-value pairs of dimensions
294296
*/
295297
public addDimensions(dimensions: Dimensions): void {
296-
const newDimensionSet: Dimensions = {};
297-
for (const [key, value] of Object.entries(dimensions)) {
298-
if (
299-
isStringUndefinedNullEmpty(key) ||
300-
isStringUndefinedNullEmpty(value)
301-
) {
302-
this.#logger.warn(
303-
`The dimension ${key} doesn't meet the requirements and won't be added. Ensure the dimension name and value are non empty strings`
304-
);
305-
continue;
306-
}
307-
if (
308-
Object.hasOwn(this.dimensions, key) ||
309-
Object.hasOwn(this.defaultDimensions, key) ||
310-
Object.hasOwn(newDimensionSet, key)
311-
) {
312-
this.#logger.warn(
313-
`Dimension "${key}" has already been added. The previous value will be overwritten.`
314-
);
315-
}
316-
newDimensionSet[key] = value;
317-
}
318-
298+
const newDimensions = this.#sanitizeDimensions(dimensions);
319299
const currentCount = this.getCurrentDimensionsCount();
320-
const newSetCount = Object.keys(newDimensionSet).length;
300+
const newSetCount = Object.keys(newDimensions).length;
321301
if (currentCount + newSetCount >= MAX_DIMENSION_COUNT) {
322302
throw new RangeError(
323303
`The number of metric dimensions must be lower than ${MAX_DIMENSION_COUNT}`
324304
);
325305
}
326306

327-
this.dimensionSets.push(newDimensionSet);
307+
this.dimensionSets.push(newDimensions);
328308
}
329309

330310
/**
@@ -432,12 +412,6 @@ class Metrics extends Utility implements MetricsInterface {
432412
public captureColdStartMetric(functionName?: string): void {
433413
if (!this.getColdStart()) return;
434414
const singleMetric = this.singleMetric();
435-
436-
if (this.defaultDimensions.service) {
437-
singleMetric.setDefaultDimensions({
438-
service: this.defaultDimensions.service,
439-
});
440-
}
441415
const value = this.functionName?.trim() ?? functionName?.trim();
442416
if (value && value.length > 0) {
443417
singleMetric.addDimension('function_name', value);
@@ -821,15 +795,20 @@ class Metrics extends Utility implements MetricsInterface {
821795
*
822796
* @param dimensions - The dimensions to be added to the default dimensions object
823797
*/
824-
public setDefaultDimensions(dimensions: Dimensions | undefined): void {
825-
const targetDimensions = {
798+
public setDefaultDimensions(dimensions: Dimensions): void {
799+
const newDimensions = this.#sanitizeDimensions(dimensions);
800+
const currentCount = Object.keys(this.defaultDimensions).length;
801+
const newSetCount = Object.keys(newDimensions).length;
802+
if (currentCount + newSetCount >= MAX_DIMENSION_COUNT) {
803+
throw new RangeError(
804+
`The number of metric dimensions must be lower than ${MAX_DIMENSION_COUNT}`
805+
);
806+
}
807+
808+
this.defaultDimensions = {
826809
...this.defaultDimensions,
827-
...dimensions,
810+
...newDimensions,
828811
};
829-
if (MAX_DIMENSION_COUNT <= Object.keys(targetDimensions).length) {
830-
throw new Error('Max dimension count hit');
831-
}
832-
this.defaultDimensions = targetDimensions;
833812
}
834813

835814
/**
@@ -886,7 +865,6 @@ class Metrics extends Utility implements MetricsInterface {
886865
public singleMetric(): Metrics {
887866
return new Metrics({
888867
namespace: this.namespace,
889-
serviceName: this.dimensions.service,
890868
defaultDimensions: this.defaultDimensions,
891869
singleMetric: true,
892870
logger: this.#logger,
@@ -1056,33 +1034,33 @@ class Metrics extends Utility implements MetricsInterface {
10561034
functionName,
10571035
} = options;
10581036

1059-
this.setEnvConfig();
1060-
this.setConsole();
10611037
this.setCustomConfigService(customConfigService);
10621038
this.setDisabled();
10631039
this.setNamespace(namespace);
1064-
this.setService(serviceName);
1065-
this.setDefaultDimensions(defaultDimensions);
1040+
const resolvedServiceName = this.#resolveServiceName(serviceName);
1041+
this.setDefaultDimensions(
1042+
defaultDimensions
1043+
? { service: resolvedServiceName, ...defaultDimensions }
1044+
: { service: resolvedServiceName }
1045+
);
10661046
this.setFunctionNameForColdStartMetric(functionName);
10671047
this.isSingleMetric = singleMetric || false;
10681048

10691049
return this;
10701050
}
10711051

10721052
/**
1073-
* Set the service to be used.
1053+
* Set the service dimension that will be included in the metrics.
10741054
*
10751055
* @param service - The service to be used
10761056
*/
1077-
private setService(service: string | undefined): void {
1078-
const targetService =
1057+
#resolveServiceName(service?: string): string {
1058+
return (
10791059
service ||
10801060
this.getCustomConfigService()?.getServiceName() ||
10811061
this.#envConfig.serviceName ||
1082-
this.defaultServiceName;
1083-
if (targetService.length > 0) {
1084-
this.setDefaultDimensions({ service: targetService });
1085-
}
1062+
this.defaultServiceName
1063+
);
10861064
}
10871065

10881066
/**
@@ -1189,6 +1167,38 @@ class Metrics extends Utility implements MetricsInterface {
11891167
**/
11901168
return 0;
11911169
}
1170+
1171+
/**
1172+
* Sanitizes the dimensions by removing invalid entries and skipping duplicates.
1173+
*
1174+
* @param dimensions - The dimensions to sanitize.
1175+
*/
1176+
#sanitizeDimensions(dimensions: Dimensions): Dimensions {
1177+
const newDimensions: Dimensions = {};
1178+
for (const [key, value] of Object.entries(dimensions)) {
1179+
if (
1180+
isStringUndefinedNullEmpty(key) ||
1181+
isStringUndefinedNullEmpty(value)
1182+
) {
1183+
this.#logger.warn(
1184+
`The dimension ${key} doesn't meet the requirements and won't be added. Ensure the dimension name and value are non empty strings`
1185+
);
1186+
continue;
1187+
}
1188+
if (
1189+
Object.hasOwn(this.dimensions, key) ||
1190+
Object.hasOwn(this.defaultDimensions, key) ||
1191+
Object.hasOwn(newDimensions, key)
1192+
) {
1193+
this.#logger.warn(
1194+
`Dimension "${key}" has already been added. The previous value will be overwritten.`
1195+
);
1196+
}
1197+
newDimensions[key] = value;
1198+
}
1199+
1200+
return newDimensions;
1201+
}
11921202
}
11931203

11941204
export { Metrics };

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

Lines changed: 59 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -358,7 +358,7 @@ describe('Working with dimensions', () => {
358358

359359
// Assess
360360
expect(() => metrics.setDefaultDimensions({ extra: 'test' })).toThrowError(
361-
'Max dimension count hit'
361+
'The number of metric dimensions must be lower than 29'
362362
);
363363
});
364364

@@ -552,4 +552,62 @@ 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.each([
577+
{ value: undefined, name: 'valid-name' },
578+
{ value: null, name: 'valid-name' },
579+
{ value: '', name: 'valid-name' },
580+
{ value: 'valid-value', name: '' },
581+
])(
582+
'skips invalid default dimension values in setDefaultDimensions ($name)',
583+
({ value, name }) => {
584+
// Arrange
585+
const metrics = new Metrics({
586+
singleMetric: true,
587+
namespace: DEFAULT_NAMESPACE,
588+
});
589+
590+
// Act
591+
metrics.setDefaultDimensions({
592+
validDimension: 'valid',
593+
[name as string]: value as string,
594+
});
595+
596+
metrics.addMetric('test', MetricUnit.Count, 1);
597+
metrics.publishStoredMetrics();
598+
599+
// Assess
600+
expect(console.warn).toHaveBeenCalledWith(
601+
`The dimension ${name} doesn't meet the requirements and won't be added. Ensure the dimension name and value are non empty strings`
602+
);
603+
604+
expect(console.log).toHaveEmittedEMFWith(
605+
expect.objectContaining({ validDimension: 'valid' })
606+
);
607+
608+
expect(console.log).toHaveEmittedEMFWith(
609+
expect.not.objectContaining({ [name]: value })
610+
);
611+
}
612+
);
555613
});

0 commit comments

Comments
 (0)