Skip to content

Commit 4507fcc

Browse files
authored
feat(logger): use async local storage for logger (#4668)
1 parent 2e08f74 commit 4507fcc

File tree

14 files changed

+881
-209
lines changed

14 files changed

+881
-209
lines changed

package-lock.json

Lines changed: 10 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

packages/logger/package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -99,6 +99,7 @@
9999
},
100100
"dependencies": {
101101
"@aws-lambda-powertools/commons": "2.28.1",
102+
"@aws/lambda-invoke-store": "0.1.1",
102103
"lodash.merge": "^4.6.2"
103104
},
104105
"keywords": [
Lines changed: 152 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,152 @@
1+
import { InvokeStore } from '@aws/lambda-invoke-store';
2+
import type { LogAttributes } from './types/logKeys.js';
3+
4+
/**
5+
* Manages storage of log attributes with automatic context detection.
6+
*
7+
* This class abstracts the storage mechanism for log attributes, 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 LogAttributesStore {
13+
readonly #temporaryAttributesKey = Symbol(
14+
'powertools.logger.temporaryAttributes'
15+
);
16+
readonly #keysKey = Symbol('powertools.logger.keys');
17+
18+
#fallbackTemporaryAttributes: LogAttributes = {};
19+
readonly #fallbackKeys: Map<string, 'temp' | 'persistent'> = new Map();
20+
#persistentAttributes: LogAttributes = {};
21+
22+
#getTemporaryAttributes(): LogAttributes {
23+
if (InvokeStore.getContext() === undefined) {
24+
return this.#fallbackTemporaryAttributes;
25+
}
26+
27+
let stored = InvokeStore.get(this.#temporaryAttributesKey) as
28+
| LogAttributes
29+
| undefined;
30+
if (stored == null) {
31+
stored = {};
32+
InvokeStore.set(this.#temporaryAttributesKey, stored);
33+
}
34+
return stored;
35+
}
36+
37+
#getKeys(): Map<string, 'temp' | 'persistent'> {
38+
if (InvokeStore.getContext() === undefined) {
39+
return this.#fallbackKeys;
40+
}
41+
42+
let stored = InvokeStore.get(this.#keysKey) as
43+
| Map<string, 'temp' | 'persistent'>
44+
| undefined;
45+
if (stored == null) {
46+
stored = new Map();
47+
InvokeStore.set(this.#keysKey, stored);
48+
}
49+
return stored;
50+
}
51+
52+
public appendTemporaryKeys(attributes: LogAttributes): void {
53+
const tempAttrs = this.#getTemporaryAttributes();
54+
const keys = this.#getKeys();
55+
56+
for (const [key, value] of Object.entries(attributes)) {
57+
tempAttrs[key] = value;
58+
keys.set(key, 'temp');
59+
}
60+
}
61+
62+
public removeTemporaryKeys(keys: string[]): void {
63+
const tempAttrs = this.#getTemporaryAttributes();
64+
const keysMap = this.#getKeys();
65+
66+
for (const key of keys) {
67+
tempAttrs[key] = undefined;
68+
69+
if (this.#persistentAttributes[key]) {
70+
keysMap.set(key, 'persistent');
71+
} else {
72+
keysMap.delete(key);
73+
}
74+
}
75+
}
76+
77+
public getTemporaryAttributes(): LogAttributes {
78+
return { ...this.#getTemporaryAttributes() };
79+
}
80+
81+
public clearTemporaryAttributes(): void {
82+
const tempAttrs = this.#getTemporaryAttributes();
83+
const keysMap = this.#getKeys();
84+
85+
for (const key of Object.keys(tempAttrs)) {
86+
if (this.#persistentAttributes[key]) {
87+
keysMap.set(key, 'persistent');
88+
} else {
89+
keysMap.delete(key);
90+
}
91+
}
92+
93+
if (InvokeStore.getContext() === undefined) {
94+
this.#fallbackTemporaryAttributes = {};
95+
return;
96+
}
97+
98+
InvokeStore.set(this.#temporaryAttributesKey, {});
99+
}
100+
101+
public setPersistentAttributes(attributes: LogAttributes): void {
102+
const keysMap = this.#getKeys();
103+
this.#persistentAttributes = { ...attributes };
104+
105+
for (const key of Object.keys(attributes)) {
106+
keysMap.set(key, 'persistent');
107+
}
108+
}
109+
110+
public getPersistentAttributes(): LogAttributes {
111+
return { ...this.#persistentAttributes };
112+
}
113+
114+
public getAllAttributes(): LogAttributes {
115+
const result: LogAttributes = {};
116+
const tempAttrs = this.#getTemporaryAttributes();
117+
const keysMap = this.#getKeys();
118+
119+
// First add all persistent attributes
120+
for (const [key, value] of Object.entries(this.#persistentAttributes)) {
121+
if (value !== undefined) {
122+
result[key] = value;
123+
}
124+
}
125+
126+
// Then override with temporary attributes based on keysMap
127+
for (const [key, type] of keysMap.entries()) {
128+
if (type === 'temp' && tempAttrs[key] !== undefined) {
129+
result[key] = tempAttrs[key];
130+
}
131+
}
132+
133+
return result;
134+
}
135+
136+
public removePersistentKeys(keys: string[]): void {
137+
const keysMap = this.#getKeys();
138+
const tempAttrs = this.#getTemporaryAttributes();
139+
140+
for (const key of keys) {
141+
this.#persistentAttributes[key] = undefined;
142+
143+
if (tempAttrs[key]) {
144+
keysMap.set(key, 'temp');
145+
} else {
146+
keysMap.delete(key);
147+
}
148+
}
149+
}
150+
}
151+
152+
export { LogAttributesStore };

0 commit comments

Comments
 (0)