@aws-lambda-powertools/logger
Version:
The logging package for the Powertools for AWS Lambda (TypeScript) library
132 lines (131 loc) • 4.63 kB
JavaScript
import '@aws/lambda-invoke-store';
import { shouldUseInvokeStore } from '@aws-lambda-powertools/commons/utils/env';
import merge from 'lodash.merge';
/**
* Manages storage of log attributes with automatic context detection.
*
* This class abstracts the storage mechanism for log attributes, automatically
* choosing between AsyncLocalStorage (when in async context) and a fallback
* object (when outside async context). The decision is made at runtime on
* every method call to support Lambda's transition to async contexts.
*/
class LogAttributesStore {
#temporaryAttributesKey = Symbol('powertools.logger.temporaryAttributes');
#keysKey = Symbol('powertools.logger.keys');
#fallbackTemporaryAttributes = {};
#fallbackKeys = new Map();
#persistentAttributes = {};
#getTemporaryAttributes() {
if (!shouldUseInvokeStore()) {
return this.#fallbackTemporaryAttributes;
}
if (globalThis.awslambda?.InvokeStore === undefined) {
throw new Error('InvokeStore is not available');
}
const store = globalThis.awslambda.InvokeStore;
let stored = store.get(this.#temporaryAttributesKey);
if (stored == null) {
stored = {};
store.set(this.#temporaryAttributesKey, stored);
}
return stored;
}
#getKeys() {
if (!shouldUseInvokeStore()) {
return this.#fallbackKeys;
}
if (globalThis.awslambda?.InvokeStore === undefined) {
throw new Error('InvokeStore is not available');
}
const store = globalThis.awslambda.InvokeStore;
let stored = store.get(this.#keysKey);
if (stored == null) {
stored = new Map();
store.set(this.#keysKey, stored);
}
return stored;
}
appendTemporaryKeys(attributes) {
const tempAttrs = this.#getTemporaryAttributes();
merge(tempAttrs, attributes);
const keysMap = this.#getKeys();
for (const key of Object.keys(attributes)) {
keysMap.set(key, 'temp');
}
}
removeTemporaryKeys(keys) {
const tempAttrs = this.#getTemporaryAttributes();
const keysMap = this.#getKeys();
for (const key of keys) {
tempAttrs[key] = undefined;
if (this.#persistentAttributes[key]) {
keysMap.set(key, 'persistent');
}
else {
keysMap.delete(key);
}
}
}
getTemporaryAttributes() {
return { ...this.#getTemporaryAttributes() };
}
clearTemporaryAttributes() {
const tempAttrs = this.#getTemporaryAttributes();
const keysMap = this.#getKeys();
for (const key of Object.keys(tempAttrs)) {
if (this.#persistentAttributes[key]) {
keysMap.set(key, 'persistent');
}
else {
keysMap.delete(key);
}
}
if (!shouldUseInvokeStore()) {
this.#fallbackTemporaryAttributes = {};
return;
}
globalThis.awslambda.InvokeStore?.set(this.#temporaryAttributesKey, {});
}
setPersistentAttributes(attributes) {
const keysMap = this.#getKeys();
this.#persistentAttributes = { ...attributes };
for (const key of Object.keys(attributes)) {
keysMap.set(key, 'persistent');
}
}
getPersistentAttributes() {
return { ...this.#persistentAttributes };
}
getAllAttributes() {
const result = {};
const tempAttrs = this.#getTemporaryAttributes();
const keysMap = this.#getKeys();
// First add all persistent attributes
for (const [key, value] of Object.entries(this.#persistentAttributes)) {
if (value !== undefined) {
result[key] = value;
}
}
// Then override with temporary attributes based on keysMap
for (const [key, type] of keysMap.entries()) {
if (type === 'temp' && tempAttrs[key] !== undefined) {
result[key] = tempAttrs[key];
}
}
return result;
}
removePersistentKeys(keys) {
const keysMap = this.#getKeys();
const tempAttrs = this.#getTemporaryAttributes();
for (const key of keys) {
this.#persistentAttributes[key] = undefined;
if (tempAttrs[key]) {
keysMap.set(key, 'temp');
}
else {
keysMap.delete(key);
}
}
}
}
export { LogAttributesStore };