Skip to content

Commit 8a07c48

Browse files
author
Aaron Michael Lamb
committed
fix: prevent putDimensions from storing duplicate dimensions
Conditions previously checked for any dimension set which did not match before adding the input dimension set. This would allow different-sized dimension sets from being mistaken as non-duplicate and being added (see unit test for example). New conditions check for any dimension set which does match before skipping adding the input dimension set to the collection. [TESTING] Unit test updated to address edge case; multiple dimension sets of variable size are added and checked.
1 parent 2206817 commit 8a07c48

File tree

2 files changed

+39
-53
lines changed

2 files changed

+39
-53
lines changed

src/logger/MetricsContext.ts

Lines changed: 17 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -58,22 +58,22 @@ export class MetricsContext {
5858
dimensions?: Array<Record<string, string>>,
5959
defaultDimensions?: Record<string, string>,
6060
shouldUseDefaultDimensions?: boolean,
61-
timestamp?: Date | number
61+
timestamp?: Date | number,
6262
) {
63-
this.namespace = namespace || Configuration.namespace
63+
this.namespace = namespace || Configuration.namespace;
6464
this.properties = properties || {};
6565
this.dimensions = dimensions || [];
6666
this.timestamp = timestamp;
6767
this.meta.Timestamp = MetricsContext.resolveMetaTimestamp(timestamp);
6868
this.defaultDimensions = defaultDimensions || {};
6969
if (shouldUseDefaultDimensions != undefined) {
70-
this.shouldUseDefaultDimensions = shouldUseDefaultDimensions
70+
this.shouldUseDefaultDimensions = shouldUseDefaultDimensions;
7171
}
7272
}
7373

7474
private static resolveMetaTimestamp(timestamp?: Date | number): number {
7575
if (timestamp instanceof Date) {
76-
return timestamp.getTime()
76+
return timestamp.getTime();
7777
} else if (timestamp) {
7878
return timestamp;
7979
} else {
@@ -111,33 +111,24 @@ export class MetricsContext {
111111
* @param dimensions
112112
*/
113113
public putDimensions(incomingDimensionSet: Record<string, string>): void {
114-
if (this.dimensions.length === 0) {
115-
this.dimensions.push(incomingDimensionSet);
116-
return;
117-
}
118-
119-
for (let i = 0; i < this.dimensions.length; i++) {
120-
const existingDimensionSet = this.dimensions[i];
114+
const incomingDimensionSetKeys = Object.keys(incomingDimensionSet);
121115

122-
// check for duplicate dimensions when putting
123-
// this is an O(n^2) operation, but since we never expect to have more than
124-
// 10 dimensions, this is acceptable for almost all cases.
125-
// This makes re-using loggers much easier.
116+
// This operation is O(n^2), but acceptable given sets are capped at 10 dimensions
117+
const doesDimensionSetExist = this.dimensions.some(existingDimensionSet => {
126118
const existingDimensionSetKeys = Object.keys(existingDimensionSet);
127-
const incomingDimensionSetKeys = Object.keys(incomingDimensionSet);
128119
if (existingDimensionSetKeys.length !== incomingDimensionSetKeys.length) {
129-
this.dimensions.push(incomingDimensionSet);
130-
return;
120+
return false;
131121
}
122+
return existingDimensionSetKeys.every(existingDimensionSetKey =>
123+
incomingDimensionSetKeys.includes(existingDimensionSetKey),
124+
);
125+
});
132126

133-
for (let j = 0; j < existingDimensionSetKeys.length; j++) {
134-
if (!incomingDimensionSetKeys.includes(existingDimensionSetKeys[j])) {
135-
// we're done now because we know that the dimensions keys are not identical
136-
this.dimensions.push(incomingDimensionSet);
137-
return;
138-
}
139-
}
127+
if (doesDimensionSetExist) {
128+
return;
140129
}
130+
131+
this.dimensions.push(incomingDimensionSet);
141132
}
142133

143134
/**
@@ -196,7 +187,7 @@ export class MetricsContext {
196187
Object.assign([], this.dimensions),
197188
this.defaultDimensions,
198189
this.shouldUseDefaultDimensions,
199-
this.timestamp
190+
this.timestamp,
200191
);
201192
}
202193
}

src/logger/__tests__/MetricsContext.test.ts

Lines changed: 22 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ test('can set property', () => {
1616
expect(actualValue).toBe(expectedValue);
1717
});
1818

19-
test('putDimension adds key to dimension and sets the dimension as a property', () => {
19+
test('putDimensions adds key to dimension and sets the dimension as a property', () => {
2020
// arrange
2121
const context = MetricsContext.empty();
2222
const dimension = faker.random.word();
@@ -29,51 +29,46 @@ test('putDimension adds key to dimension and sets the dimension as a property',
2929
expect(context.getDimensions()[0]).toStrictEqual(expectedDimension);
3030
});
3131

32-
test('putDimension will not duplicate dimensions', () => {
32+
test('putDimensions accepts multiple unique dimension sets', () => {
3333
// arrange
3434
const context = MetricsContext.empty();
35-
const dimension = faker.random.word();
36-
const expectedDimension = { dimension };
35+
const expectedDimension1 = { d1: faker.random.word(), d2: faker.random.word() };
36+
const expectedDimension2 = { d2: faker.random.word(), d3: faker.random.word() };
3737

3838
// act
39-
context.putDimensions({ dimension });
40-
context.putDimensions({ dimension });
39+
context.putDimensions(expectedDimension1);
40+
context.putDimensions(expectedDimension2);
4141

4242
// assert
43-
expect(context.getDimensions().length).toBe(1);
44-
expect(context.getDimensions()[0]).toStrictEqual(expectedDimension);
43+
expect(context.getDimensions().length).toBe(2);
44+
expect(context.getDimensions()[0]).toStrictEqual(expectedDimension1);
45+
expect(context.getDimensions()[1]).toStrictEqual(expectedDimension2);
4546
});
4647

47-
test('putDimension will not duplicate dimensions, multiple in different order', () => {
48+
test('putDimensions will not duplicate dimensions', () => {
4849
// arrange
4950
const context = MetricsContext.empty();
5051
const dimension1 = faker.random.word();
5152
const dimension2 = faker.random.word();
52-
const expectedDimension = { dimension1, dimension2 };
53+
const expectedDimension1 = { dimension1 };
54+
const expectedDimension2 = { dimension1, dimension2 };
55+
const expectedDimension3 = { dimension2 };
5356

5457
// act
58+
context.putDimensions({ dimension1 });
5559
context.putDimensions({ dimension1, dimension2 });
5660
context.putDimensions({ dimension2, dimension1 });
61+
context.putDimensions({ dimension2 });
62+
context.putDimensions({ dimension1 });
63+
context.putDimensions({ dimension1, dimension2 });
64+
context.putDimensions({ dimension2, dimension1 });
65+
context.putDimensions({ dimension2 });
5766

5867
// assert
59-
expect(context.getDimensions().length).toBe(1);
60-
expect(context.getDimensions()[0]).toStrictEqual(expectedDimension);
61-
});
62-
63-
test('putDimension accepts multiple unique dimension sets', () => {
64-
// arrange
65-
const context = MetricsContext.empty();
66-
const expectedDimension1 = { d1: faker.random.word(), d2: faker.random.word() };
67-
const expectedDimension2 = { d2: faker.random.word(), d3: faker.random.word() };
68-
69-
// act
70-
context.putDimensions(expectedDimension1);
71-
context.putDimensions(expectedDimension2);
72-
73-
// assert
74-
expect(context.getDimensions().length).toBe(2);
68+
expect(context.getDimensions().length).toBe(3);
7569
expect(context.getDimensions()[0]).toStrictEqual(expectedDimension1);
7670
expect(context.getDimensions()[1]).toStrictEqual(expectedDimension2);
71+
expect(context.getDimensions()[2]).toStrictEqual(expectedDimension3);
7772
});
7873

7974
test('getDimensions returns default dimensions if custom dimensions not set', () => {
@@ -201,5 +196,5 @@ test('createCopyWithContext copies shouldUseDefaultDimensions', () => {
201196

202197
// assert
203198
expect(newContext).not.toBe(context);
204-
expect(newContext.getDimensions()).toEqual([])
199+
expect(newContext.getDimensions()).toEqual([]);
205200
});

0 commit comments

Comments
 (0)