Skip to content

Commit 2e08f74

Browse files
authored
feat(metrics): use async local storage for metrics (#4663) (#4694)
1 parent b26cd2c commit 2e08f74

File tree

11 files changed

+1340
-155
lines changed

11 files changed

+1340
-155
lines changed

package-lock.json

Lines changed: 11 additions & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

packages/metrics/package.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -88,7 +88,8 @@
8888
"url": "https://github.com/aws-powertools/powertools-lambda-typescript/issues"
8989
},
9090
"dependencies": {
91-
"@aws-lambda-powertools/commons": "2.28.1"
91+
"@aws-lambda-powertools/commons": "2.28.1",
92+
"@aws/lambda-invoke-store": "0.1.1"
9293
},
9394
"keywords": [
9495
"aws",
Lines changed: 104 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,104 @@
1+
import { InvokeStore } from '@aws/lambda-invoke-store';
2+
import type { Dimensions } from './types/Metrics.js';
3+
4+
/**
5+
* Manages storage of metrics dimensions with automatic context detection.
6+
*
7+
* This class abstracts the storage mechanism for metrics, automatically
8+
* choosing between AsyncLocalStorage (when in async context) and a fallback
9+
* object (when outside async context). The decision is made at runtime on
10+
* every method call to support Lambda's transition to async contexts.
11+
*/
12+
class DimensionsStore {
13+
readonly #dimensionsKey = Symbol('powertools.metrics.dimensions');
14+
readonly #dimensionSetsKey = Symbol('powertools.metrics.dimensionSets');
15+
16+
#fallbackDimensions: Dimensions = {};
17+
#fallbackDimensionSets: Dimensions[] = [];
18+
#defaultDimensions: Dimensions = {};
19+
20+
#getDimensions(): Dimensions {
21+
if (InvokeStore.getContext() === undefined) {
22+
return this.#fallbackDimensions;
23+
}
24+
25+
let stored = InvokeStore.get(this.#dimensionsKey) as Dimensions | undefined;
26+
if (stored == null) {
27+
stored = {};
28+
InvokeStore.set(this.#dimensionsKey, stored);
29+
}
30+
return stored;
31+
}
32+
33+
#getDimensionSets(): Dimensions[] {
34+
if (InvokeStore.getContext() === undefined) {
35+
return this.#fallbackDimensionSets;
36+
}
37+
38+
let stored = InvokeStore.get(this.#dimensionSetsKey) as
39+
| Dimensions[]
40+
| undefined;
41+
if (stored == null) {
42+
stored = [];
43+
InvokeStore.set(this.#dimensionSetsKey, stored);
44+
}
45+
return stored;
46+
}
47+
48+
public addDimension(name: string, value: string): string {
49+
this.#getDimensions()[name] = value;
50+
return value;
51+
}
52+
53+
public addDimensionSet(dimensionSet: Dimensions): Dimensions {
54+
this.#getDimensionSets().push({ ...dimensionSet });
55+
return dimensionSet;
56+
}
57+
58+
public getDimensions(): Dimensions {
59+
return { ...this.#getDimensions() };
60+
}
61+
62+
public getDimensionSets(): Dimensions[] {
63+
return this.#getDimensionSets().map((set) => ({ ...set }));
64+
}
65+
66+
public clearRequestDimensions(): void {
67+
if (InvokeStore.getContext() === undefined) {
68+
this.#fallbackDimensions = {};
69+
this.#fallbackDimensionSets = [];
70+
return;
71+
}
72+
73+
InvokeStore.set(this.#dimensionsKey, {});
74+
InvokeStore.set(this.#dimensionSetsKey, []);
75+
}
76+
77+
public clearDefaultDimensions(): void {
78+
this.#defaultDimensions = {};
79+
}
80+
81+
public getDimensionCount(): number {
82+
const dimensions = this.#getDimensions();
83+
const dimensionSets = this.#getDimensionSets();
84+
const dimensionSetsCount = dimensionSets.reduce(
85+
(total, dimensionSet) => total + Object.keys(dimensionSet).length,
86+
0
87+
);
88+
return (
89+
Object.keys(dimensions).length +
90+
Object.keys(this.#defaultDimensions).length +
91+
dimensionSetsCount
92+
);
93+
}
94+
95+
public setDefaultDimensions(dimensions: Dimensions): void {
96+
this.#defaultDimensions = { ...dimensions };
97+
}
98+
99+
public getDefaultDimensions(): Dimensions {
100+
return { ...this.#defaultDimensions };
101+
}
102+
}
103+
104+
export { DimensionsStore };
Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
import { InvokeStore } from '@aws/lambda-invoke-store';
2+
3+
/**
4+
* Manages storage of metrics #metadata with automatic context detection.
5+
*
6+
* This class abstracts the storage mechanism for metrics, automatically
7+
* choosing between AsyncLocalStorage (when in async context) and a fallback
8+
* object (when outside async context). The decision is made at runtime on
9+
* every method call to support Lambda's transition to async contexts.
10+
*/
11+
class MetadataStore {
12+
readonly #metadataKey = Symbol('powertools.metrics.metadata');
13+
14+
#fallbackStorage: Record<string, string> = {};
15+
16+
#getStorage(): Record<string, string> {
17+
if (InvokeStore.getContext() === undefined) {
18+
return this.#fallbackStorage;
19+
}
20+
21+
let stored = InvokeStore.get(this.#metadataKey) as
22+
| Record<string, string>
23+
| undefined;
24+
if (stored == null) {
25+
stored = {};
26+
InvokeStore.set(this.#metadataKey, stored);
27+
}
28+
return stored;
29+
}
30+
31+
public set(key: string, value: string): string {
32+
this.#getStorage()[key] = value;
33+
return value;
34+
}
35+
36+
public getAll(): Record<string, string> {
37+
return { ...this.#getStorage() };
38+
}
39+
40+
public clear(): void {
41+
if (InvokeStore.getContext() === undefined) {
42+
this.#fallbackStorage = {};
43+
return;
44+
}
45+
46+
InvokeStore.set(this.#metadataKey, {});
47+
}
48+
}
49+
50+
export { MetadataStore };

0 commit comments

Comments
 (0)