UNPKG

@aws-lambda-powertools/logger

Version:
132 lines (131 loc) 4.63 kB
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 };