@vitkuz/aws-logger 1.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/.env.example ADDED
@@ -0,0 +1,3 @@
1
+ AWS_REGION=your_aws_region_here
2
+ AWS_ACCESS_KEY_ID=your_aws_access_key_id_here
3
+ AWS_SECRET_ACCESS_KEY=your_aws_secret_access_key_here
package/.gitkeep ADDED
File without changes
@@ -0,0 +1,4 @@
1
+ dist
2
+ node_modules
3
+ package-lock.json
4
+ *.json
package/.prettierrc ADDED
@@ -0,0 +1,8 @@
1
+ {
2
+ "semi": true,
3
+ "singleQuote": true,
4
+ "tabWidth": 4,
5
+ "trailingComma": "all",
6
+ "printWidth": 100,
7
+ "arrowParens": "always"
8
+ }
@@ -0,0 +1,89 @@
1
+ interface Logger {
2
+ debug(message: string, context?: Record<string, any>): void;
3
+ info(message: string, context?: Record<string, any>): void;
4
+ warn(message: string, context?: Record<string, any>): void;
5
+ error(message: string, error?: Error | string, context?: Record<string, any>): void;
6
+ /**
7
+ * Creates a child logger with bound context.
8
+ * Any logs emitted by the child will include the parent's context plus the new context.
9
+ */
10
+ child(context: Record<string, any>): Logger;
11
+ }
12
+
13
+ type RedactionStrategy = 'mask' | 'remove' | 'hash' | string | ((value: any) => any);
14
+ interface RedactionConfig {
15
+ /**
16
+ * Array of keys to redact.
17
+ * Case-insensitive matching is recommended/implemented.
18
+ */
19
+ keys: string[];
20
+ /**
21
+ * Map of specific key to strategy.
22
+ * If a key is in 'keys' but not here, it uses the default strategy ('mask').
23
+ */
24
+ strategies?: Record<string, RedactionStrategy>;
25
+ /**
26
+ * Default strategy for keys found in the list but not in the strategy map.
27
+ * Default: 'mask'
28
+ */
29
+ defaultStrategy?: RedactionStrategy;
30
+ }
31
+ declare const COMMON_REDACTION_KEYS: string[];
32
+ /**
33
+ * Recursively walks the object and redacts fields matching the config.
34
+ */
35
+ declare const recursiveRedact: (target: any, config: RedactionConfig, cache?: Set<any>) => any;
36
+ declare const createRedactor: (config: RedactionConfig) => (info: any) => any;
37
+
38
+ /**
39
+ * Runs a callback within a logger context.
40
+ * The logger provided becomes the "current" logger for the duration of the callback.
41
+ */
42
+ declare const runWithLogger: <T>(logger: Logger, callback: () => T) => T;
43
+ /**
44
+ * Gets the current logger from the async context.
45
+ * Throws an error if called outside of a runWithLogger context,
46
+ * unless a fallback is provided (though usually we want to enforce context).
47
+ *
48
+ * To make it easier to use, we can return undefined or a default logger if needed,
49
+ * but for this specific feature request "access by all down the stream", strictness is good.
50
+ */
51
+ declare const getLogger: () => Logger | undefined;
52
+ /**
53
+ * Updates the current context's logger.
54
+ * This effectively "extends" the logger for the remainder of the current async execution
55
+ * and any further downstream calls sharing this context.
56
+ */
57
+ declare const updateLoggerContext: (newLogger: Logger) => void;
58
+
59
+ interface CreateLoggerOptions {
60
+ /**
61
+ * Default context to include in all logs
62
+ */
63
+ defaultContext?: Record<string, any>;
64
+ /**
65
+ * Log level (default: process.env.LOG_LEVEL || 'info')
66
+ */
67
+ level?: string;
68
+ /**
69
+ * Configuration for data redaction
70
+ */
71
+ redaction?: RedactionConfig;
72
+ }
73
+ declare const createLogger: (options?: CreateLoggerOptions) => Logger;
74
+
75
+ type Handler<TEvent = any, TResult = any> = (event: TEvent, context: any, callback?: any) => Promise<TResult> | void;
76
+ /**
77
+ * Higher-order function to wrap a Lambda handler with logger context.
78
+ * Automatically extracts 'awsRequestId' and 'functionName' from the Lambda context
79
+ * and initializes a logger for the request scope.
80
+ *
81
+ * @param handler The original Lambda handler
82
+ * @param options Logger options (level, redaction, etc.)
83
+ */
84
+ declare const withLogger: <TEvent = any, TResult = any>(handler: Handler<TEvent, TResult>, options?: CreateLoggerOptions) => Handler<TEvent, TResult>;
85
+
86
+ declare const DEFAULT_LOG_LEVEL = "info";
87
+ declare const REQUEST_ID_KEY = "x-request-id";
88
+
89
+ export { COMMON_REDACTION_KEYS, type CreateLoggerOptions, DEFAULT_LOG_LEVEL, type Logger, REQUEST_ID_KEY, type RedactionConfig, type RedactionStrategy, createLogger, createRedactor, getLogger, recursiveRedact, runWithLogger, updateLoggerContext, withLogger };
@@ -0,0 +1,89 @@
1
+ interface Logger {
2
+ debug(message: string, context?: Record<string, any>): void;
3
+ info(message: string, context?: Record<string, any>): void;
4
+ warn(message: string, context?: Record<string, any>): void;
5
+ error(message: string, error?: Error | string, context?: Record<string, any>): void;
6
+ /**
7
+ * Creates a child logger with bound context.
8
+ * Any logs emitted by the child will include the parent's context plus the new context.
9
+ */
10
+ child(context: Record<string, any>): Logger;
11
+ }
12
+
13
+ type RedactionStrategy = 'mask' | 'remove' | 'hash' | string | ((value: any) => any);
14
+ interface RedactionConfig {
15
+ /**
16
+ * Array of keys to redact.
17
+ * Case-insensitive matching is recommended/implemented.
18
+ */
19
+ keys: string[];
20
+ /**
21
+ * Map of specific key to strategy.
22
+ * If a key is in 'keys' but not here, it uses the default strategy ('mask').
23
+ */
24
+ strategies?: Record<string, RedactionStrategy>;
25
+ /**
26
+ * Default strategy for keys found in the list but not in the strategy map.
27
+ * Default: 'mask'
28
+ */
29
+ defaultStrategy?: RedactionStrategy;
30
+ }
31
+ declare const COMMON_REDACTION_KEYS: string[];
32
+ /**
33
+ * Recursively walks the object and redacts fields matching the config.
34
+ */
35
+ declare const recursiveRedact: (target: any, config: RedactionConfig, cache?: Set<any>) => any;
36
+ declare const createRedactor: (config: RedactionConfig) => (info: any) => any;
37
+
38
+ /**
39
+ * Runs a callback within a logger context.
40
+ * The logger provided becomes the "current" logger for the duration of the callback.
41
+ */
42
+ declare const runWithLogger: <T>(logger: Logger, callback: () => T) => T;
43
+ /**
44
+ * Gets the current logger from the async context.
45
+ * Throws an error if called outside of a runWithLogger context,
46
+ * unless a fallback is provided (though usually we want to enforce context).
47
+ *
48
+ * To make it easier to use, we can return undefined or a default logger if needed,
49
+ * but for this specific feature request "access by all down the stream", strictness is good.
50
+ */
51
+ declare const getLogger: () => Logger | undefined;
52
+ /**
53
+ * Updates the current context's logger.
54
+ * This effectively "extends" the logger for the remainder of the current async execution
55
+ * and any further downstream calls sharing this context.
56
+ */
57
+ declare const updateLoggerContext: (newLogger: Logger) => void;
58
+
59
+ interface CreateLoggerOptions {
60
+ /**
61
+ * Default context to include in all logs
62
+ */
63
+ defaultContext?: Record<string, any>;
64
+ /**
65
+ * Log level (default: process.env.LOG_LEVEL || 'info')
66
+ */
67
+ level?: string;
68
+ /**
69
+ * Configuration for data redaction
70
+ */
71
+ redaction?: RedactionConfig;
72
+ }
73
+ declare const createLogger: (options?: CreateLoggerOptions) => Logger;
74
+
75
+ type Handler<TEvent = any, TResult = any> = (event: TEvent, context: any, callback?: any) => Promise<TResult> | void;
76
+ /**
77
+ * Higher-order function to wrap a Lambda handler with logger context.
78
+ * Automatically extracts 'awsRequestId' and 'functionName' from the Lambda context
79
+ * and initializes a logger for the request scope.
80
+ *
81
+ * @param handler The original Lambda handler
82
+ * @param options Logger options (level, redaction, etc.)
83
+ */
84
+ declare const withLogger: <TEvent = any, TResult = any>(handler: Handler<TEvent, TResult>, options?: CreateLoggerOptions) => Handler<TEvent, TResult>;
85
+
86
+ declare const DEFAULT_LOG_LEVEL = "info";
87
+ declare const REQUEST_ID_KEY = "x-request-id";
88
+
89
+ export { COMMON_REDACTION_KEYS, type CreateLoggerOptions, DEFAULT_LOG_LEVEL, type Logger, REQUEST_ID_KEY, type RedactionConfig, type RedactionStrategy, createLogger, createRedactor, getLogger, recursiveRedact, runWithLogger, updateLoggerContext, withLogger };
package/dist/index.js ADDED
@@ -0,0 +1,262 @@
1
+ "use strict";
2
+ var __create = Object.create;
3
+ var __defProp = Object.defineProperty;
4
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
5
+ var __getOwnPropNames = Object.getOwnPropertyNames;
6
+ var __getProtoOf = Object.getPrototypeOf;
7
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
8
+ var __export = (target, all) => {
9
+ for (var name in all)
10
+ __defProp(target, name, { get: all[name], enumerable: true });
11
+ };
12
+ var __copyProps = (to, from, except, desc) => {
13
+ if (from && typeof from === "object" || typeof from === "function") {
14
+ for (let key of __getOwnPropNames(from))
15
+ if (!__hasOwnProp.call(to, key) && key !== except)
16
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
17
+ }
18
+ return to;
19
+ };
20
+ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
21
+ // If the importer is in node compatibility mode or this is not an ESM
22
+ // file that has been converted to a CommonJS file using a Babel-
23
+ // compatible transform (i.e. "__esModule" has not been set), then set
24
+ // "default" to the CommonJS "module.exports" for node compatibility.
25
+ isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
26
+ mod
27
+ ));
28
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
29
+
30
+ // src/index.ts
31
+ var index_exports = {};
32
+ __export(index_exports, {
33
+ COMMON_REDACTION_KEYS: () => COMMON_REDACTION_KEYS,
34
+ DEFAULT_LOG_LEVEL: () => DEFAULT_LOG_LEVEL,
35
+ REQUEST_ID_KEY: () => REQUEST_ID_KEY,
36
+ createLogger: () => createLogger,
37
+ createRedactor: () => createRedactor,
38
+ getLogger: () => getLogger,
39
+ recursiveRedact: () => recursiveRedact,
40
+ runWithLogger: () => runWithLogger,
41
+ updateLoggerContext: () => updateLoggerContext,
42
+ withLogger: () => withLogger
43
+ });
44
+ module.exports = __toCommonJS(index_exports);
45
+
46
+ // src/redactor.ts
47
+ var import_crypto = __toESM(require("crypto"));
48
+ var COMMON_REDACTION_KEYS = [
49
+ "password",
50
+ "passwd",
51
+ "secret",
52
+ "token",
53
+ "access_token",
54
+ "access-token",
55
+ "auth",
56
+ "auth-token",
57
+ "authorization",
58
+ "apikey",
59
+ "api_key",
60
+ "api-key",
61
+ "card",
62
+ "cvv",
63
+ "ssn",
64
+ "pin"
65
+ ];
66
+ var applyStrategy = (value, strategy) => {
67
+ if (typeof strategy === "function") {
68
+ return strategy(value);
69
+ }
70
+ if (typeof strategy === "string" && strategy.startsWith("mask-last-")) {
71
+ const parts = strategy.split("-");
72
+ const lastN = parseInt(parts[2], 10);
73
+ if (typeof value === "string" && !isNaN(lastN)) {
74
+ if (value.length <= lastN) {
75
+ return value;
76
+ return value;
77
+ }
78
+ const maskLen = value.length - lastN;
79
+ return "*".repeat(maskLen) + value.slice(-lastN);
80
+ }
81
+ return "*****";
82
+ }
83
+ switch (strategy) {
84
+ case "remove":
85
+ return void 0;
86
+ case "hash":
87
+ if (typeof value === "string" || typeof value === "number") {
88
+ return import_crypto.default.createHash("sha256").update(String(value)).digest("hex");
89
+ }
90
+ return "[HASH_FAILED_TYPE]";
91
+ case "mask":
92
+ default:
93
+ return "*****";
94
+ }
95
+ };
96
+ var recursiveRedact = (target, config, cache = /* @__PURE__ */ new Set()) => {
97
+ if (target === null || typeof target !== "object") {
98
+ return target;
99
+ }
100
+ if (cache.has(target)) {
101
+ return "[Circular]";
102
+ }
103
+ cache.add(target);
104
+ if (Array.isArray(target)) {
105
+ return target.map((item) => recursiveRedact(item, config, cache));
106
+ }
107
+ const redactedObj = {};
108
+ const keysToRedact = new Set(config.keys.map((k) => k.toLowerCase()));
109
+ for (const [key, value] of Object.entries(target)) {
110
+ const lowerKey = key.toLowerCase();
111
+ if (keysToRedact.has(lowerKey)) {
112
+ let strategy = config.defaultStrategy || "mask";
113
+ if (config.strategies && config.strategies[key]) {
114
+ strategy = config.strategies[key];
115
+ } else if (config.strategies) {
116
+ const strategyKey = Object.keys(config.strategies).find(
117
+ (k) => k.toLowerCase() === lowerKey
118
+ );
119
+ if (strategyKey) {
120
+ strategy = config.strategies[strategyKey];
121
+ }
122
+ }
123
+ const result = applyStrategy(value, strategy);
124
+ if (result !== void 0) {
125
+ redactedObj[key] = result;
126
+ }
127
+ } else {
128
+ redactedObj[key] = recursiveRedact(value, config, cache);
129
+ }
130
+ }
131
+ return redactedObj;
132
+ };
133
+ var createRedactor = (config) => {
134
+ return (info) => {
135
+ return recursiveRedact(info, config);
136
+ };
137
+ };
138
+
139
+ // src/context.ts
140
+ var import_async_hooks = require("async_hooks");
141
+ var asyncLocalStorage = new import_async_hooks.AsyncLocalStorage();
142
+ var runWithLogger = (logger, callback) => {
143
+ return asyncLocalStorage.run({ logger }, callback);
144
+ };
145
+ var getLogger = () => {
146
+ const store = asyncLocalStorage.getStore();
147
+ return store?.logger;
148
+ };
149
+ var updateLoggerContext = (newLogger) => {
150
+ const store = asyncLocalStorage.getStore();
151
+ if (store) {
152
+ store.logger = newLogger;
153
+ } else {
154
+ console.warn(
155
+ "AntigravityLogger: updateLoggerContext called outside of an active context. Logic ignored."
156
+ );
157
+ }
158
+ };
159
+
160
+ // src/logger.ts
161
+ var import_winston = __toESM(require("winston"));
162
+
163
+ // src/constants.ts
164
+ var DEFAULT_LOG_LEVEL = "info";
165
+ var REQUEST_ID_KEY = "x-request-id";
166
+
167
+ // src/logger.ts
168
+ var formatContext = (context) => {
169
+ return context || {};
170
+ };
171
+ var createLoggerWrapper = (winstonLogger) => {
172
+ return {
173
+ debug: (message, context) => {
174
+ winstonLogger.debug(message, formatContext(context));
175
+ },
176
+ info: (message, context) => {
177
+ winstonLogger.info(message, formatContext(context));
178
+ },
179
+ warn: (message, context) => {
180
+ winstonLogger.warn(message, formatContext(context));
181
+ },
182
+ error: (message, error, context) => {
183
+ const meta = formatContext(context);
184
+ if (error instanceof Error) {
185
+ const { message: message2, name, stack, ...rest } = error;
186
+ meta.error = {
187
+ ...rest,
188
+ message: message2,
189
+ name,
190
+ stack
191
+ };
192
+ } else if (error) {
193
+ meta.error = error;
194
+ }
195
+ winstonLogger.error(message, meta);
196
+ },
197
+ child: (context) => {
198
+ return createLoggerWrapper(winstonLogger.child(context));
199
+ }
200
+ };
201
+ };
202
+ var createLogger = (options = {}) => {
203
+ const level = options.level || process.env.LOG_LEVEL || DEFAULT_LOG_LEVEL;
204
+ const redactor = options.redaction ? createRedactor(options.redaction) : null;
205
+ const redactionFormat = import_winston.default.format((info) => {
206
+ if (redactor) {
207
+ return redactor(info);
208
+ }
209
+ return info;
210
+ });
211
+ const format = import_winston.default.format.combine(
212
+ import_winston.default.format.timestamp(),
213
+ redactionFormat(),
214
+ // Apply redaction before JSON
215
+ import_winston.default.format.json()
216
+ );
217
+ const winstonLogger = import_winston.default.createLogger({
218
+ level,
219
+ format,
220
+ defaultMeta: options.defaultContext,
221
+ transports: [new import_winston.default.transports.Console()]
222
+ });
223
+ return createLoggerWrapper(winstonLogger);
224
+ };
225
+
226
+ // src/wrapper.ts
227
+ var withLogger = (handler, options = {}) => {
228
+ return async (event, context, callback) => {
229
+ const rootLogger = createLogger(options);
230
+ const requestContext = {};
231
+ if (context) {
232
+ if (context.awsRequestId) requestContext.requestId = context.awsRequestId;
233
+ if (context.functionName) requestContext.functionName = context.functionName;
234
+ }
235
+ const scopedLogger = rootLogger.child(requestContext);
236
+ scopedLogger.debug("Lambda Event", { event });
237
+ scopedLogger.debug("Lambda Context", { context });
238
+ return runWithLogger(scopedLogger, async () => {
239
+ try {
240
+ const result = await handler(event, context, callback);
241
+ return result;
242
+ } catch (error) {
243
+ scopedLogger.error("Unhandled Lambda Exception", error);
244
+ throw error;
245
+ }
246
+ });
247
+ };
248
+ };
249
+ // Annotate the CommonJS export names for ESM import in node:
250
+ 0 && (module.exports = {
251
+ COMMON_REDACTION_KEYS,
252
+ DEFAULT_LOG_LEVEL,
253
+ REQUEST_ID_KEY,
254
+ createLogger,
255
+ createRedactor,
256
+ getLogger,
257
+ recursiveRedact,
258
+ runWithLogger,
259
+ updateLoggerContext,
260
+ withLogger
261
+ });
262
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/index.ts","../src/redactor.ts","../src/context.ts","../src/logger.ts","../src/constants.ts","../src/wrapper.ts"],"sourcesContent":["export * from './types';\nexport * from './redactor';\nexport * from './context';\nexport * from './logger';\nexport * from './wrapper';\nexport * from './constants';\n","import crypto from 'crypto';\n\nexport type RedactionStrategy = 'mask' | 'remove' | 'hash' | string | ((value: any) => any);\n\nexport interface RedactionConfig {\n /**\n * Array of keys to redact.\n * Case-insensitive matching is recommended/implemented.\n */\n keys: string[];\n /**\n * Map of specific key to strategy.\n * If a key is in 'keys' but not here, it uses the default strategy ('mask').\n */\n strategies?: Record<string, RedactionStrategy>;\n /**\n * Default strategy for keys found in the list but not in the strategy map.\n * Default: 'mask'\n */\n defaultStrategy?: RedactionStrategy;\n}\n\nexport const COMMON_REDACTION_KEYS = [\n 'password',\n 'passwd',\n 'secret',\n 'token',\n 'access_token',\n 'access-token',\n 'auth',\n 'auth-token',\n 'authorization',\n 'apikey',\n 'api_key',\n 'api-key',\n 'card',\n 'cvv',\n 'ssn',\n 'pin',\n];\n\n/**\n * Applies the redaction strategy to a single value.\n */\nconst applyStrategy = (value: any, strategy: RedactionStrategy): any => {\n if (typeof strategy === 'function') {\n return strategy(value);\n }\n\n if (typeof strategy === 'string' && strategy.startsWith('mask-last-')) {\n const parts = strategy.split('-');\n const lastN = parseInt(parts[2], 10);\n\n if (typeof value === 'string' && !isNaN(lastN)) {\n if (value.length <= lastN) {\n return value; // Or mask entirely? Usually if shorter, we show it or mask all. Let's return as is or mask all?\n // Security wise: showing short secrets fully is bad. Masking all is safer.\n // BUT, if user asked for last 4 and length is 3, showing 3 is effectively \"last 4\".\n // Let's mask all if length <= lastN to be safe? Or just return.\n // Actually, standard is usually: if len <= N, show all (it IS the last N).\n // But for API keys, usually we want to see SOMETHING masked.\n // Let's preserve standard behavior: visible suffix.\n return value;\n }\n const maskLen = value.length - lastN;\n return '*'.repeat(maskLen) + value.slice(-lastN);\n }\n return '*****'; // Fallback for non-strings\n }\n\n switch (strategy) {\n case 'remove':\n return undefined;\n case 'hash':\n if (typeof value === 'string' || typeof value === 'number') {\n return crypto.createHash('sha256').update(String(value)).digest('hex');\n }\n return '[HASH_FAILED_TYPE]';\n case 'mask':\n default:\n return '*****';\n }\n};\n\n/**\n * Recursively walks the object and redacts fields matching the config.\n */\nexport const recursiveRedact = (\n target: any,\n config: RedactionConfig,\n cache = new Set<any>(),\n): any => {\n if (target === null || typeof target !== 'object') {\n return target;\n }\n\n // Handle circular references\n if (cache.has(target)) {\n return '[Circular]';\n }\n cache.add(target);\n\n // Handle Arrays\n if (Array.isArray(target)) {\n return target.map((item) => recursiveRedact(item, config, cache));\n }\n\n // Handle Objects\n const redactedObj: Record<string, any> = {};\n const keysToRedact = new Set(config.keys.map((k) => k.toLowerCase()));\n\n for (const [key, value] of Object.entries(target)) {\n const lowerKey = key.toLowerCase();\n\n if (keysToRedact.has(lowerKey)) {\n // Determine strategy\n let strategy = config.defaultStrategy || 'mask';\n if (config.strategies && config.strategies[key]) {\n // Try exact match first\n strategy = config.strategies[key];\n } else if (config.strategies) {\n // Try case-insensitive lookup in strategies if needed,\n // but for simplicity let's rely on specific keys matching or default.\n // Actually, user might put 'Password': 'hash' and we matched 'password'.\n // Let's iterate strategies keys to find case-insensitive match if strictly needed.\n // For performance/simplicity, strict case matching in strategies map is safer,\n // but generic 'keys' list is case-insensitive.\n\n // Let's look for a key in strategies that matches lowerKey\n const strategyKey = Object.keys(config.strategies).find(\n (k) => k.toLowerCase() === lowerKey,\n );\n if (strategyKey) {\n strategy = config.strategies[strategyKey];\n }\n }\n\n const result = applyStrategy(value, strategy);\n if (result !== undefined) {\n redactedObj[key] = result;\n }\n // If undefined (remove strategy), we just don't add the key.\n } else {\n // Recurse\n redactedObj[key] = recursiveRedact(value, config, cache);\n }\n }\n\n return redactedObj;\n};\n\nexport const createRedactor = (config: RedactionConfig) => {\n return (info: any) => {\n return recursiveRedact(info, config);\n };\n};\n","import { AsyncLocalStorage } from 'async_hooks';\nimport { Logger } from './types';\n\n// We store a reference to the logger so we can update it in-place for the current context\ninterface LoggerStore {\n logger: Logger;\n}\n\nconst asyncLocalStorage = new AsyncLocalStorage<LoggerStore>();\n\n/**\n * Runs a callback within a logger context.\n * The logger provided becomes the \"current\" logger for the duration of the callback.\n */\nexport const runWithLogger = <T>(logger: Logger, callback: () => T): T => {\n return asyncLocalStorage.run({ logger }, callback);\n};\n\n/**\n * Gets the current logger from the async context.\n * Throws an error if called outside of a runWithLogger context,\n * unless a fallback is provided (though usually we want to enforce context).\n *\n * To make it easier to use, we can return undefined or a default logger if needed,\n * but for this specific feature request \"access by all down the stream\", strictness is good.\n */\nexport const getLogger = (): Logger | undefined => {\n const store = asyncLocalStorage.getStore();\n return store?.logger;\n};\n\n/**\n * Updates the current context's logger.\n * This effectively \"extends\" the logger for the remainder of the current async execution\n * and any further downstream calls sharing this context.\n */\nexport const updateLoggerContext = (newLogger: Logger): void => {\n const store = asyncLocalStorage.getStore();\n if (store) {\n store.logger = newLogger;\n } else {\n // If we are not in a context, we can't update it.\n // We could throw, or warn. For now, let's warn.\n console.warn(\n 'AntigravityLogger: updateLoggerContext called outside of an active context. Logic ignored.',\n );\n }\n};\n","import winston from 'winston';\nimport { Logger } from './types';\nimport { DEFAULT_LOG_LEVEL } from './constants';\nimport { RedactionConfig, createRedactor } from './redactor';\n\nexport interface CreateLoggerOptions {\n /**\n * Default context to include in all logs\n */\n defaultContext?: Record<string, any>;\n /**\n * Log level (default: process.env.LOG_LEVEL || 'info')\n */\n level?: string;\n /**\n * Configuration for data redaction\n */\n redaction?: RedactionConfig;\n}\n\nconst formatContext = (context?: Record<string, any>): Record<string, any> => {\n return context || {};\n};\n\nconst createLoggerWrapper = (winstonLogger: winston.Logger): Logger => {\n return {\n debug: (message: string, context?: Record<string, any>): void => {\n winstonLogger.debug(message, formatContext(context));\n },\n\n info: (message: string, context?: Record<string, any>): void => {\n winstonLogger.info(message, formatContext(context));\n },\n\n warn: (message: string, context?: Record<string, any>): void => {\n winstonLogger.warn(message, formatContext(context));\n },\n\n error: (message: string, error?: Error | string, context?: Record<string, any>): void => {\n const meta = formatContext(context);\n if (error instanceof Error) {\n // Winston handles error objects well if passed as meta or splat\n // But for explicit structure, let's attach it\n // Spread error first to capture custom props, then overwrite standard ones to ensure presence\n const { message, name, stack, ...rest } = error;\n meta.error = {\n ...rest,\n message,\n name,\n stack,\n };\n } else if (error) {\n meta.error = error;\n }\n winstonLogger.error(message, meta);\n },\n\n child: (context: Record<string, any>): Logger => {\n // Winston's child() returns a new logger instance with the metadata bound\n return createLoggerWrapper(winstonLogger.child(context));\n },\n };\n};\n\nexport const createLogger = (options: CreateLoggerOptions = {}): Logger => {\n const level = options.level || process.env.LOG_LEVEL || DEFAULT_LOG_LEVEL;\n\n // Create redactor if config is present\n const redactor = options.redaction ? createRedactor(options.redaction) : null;\n\n // Custom format that applies redaction\n const redactionFormat = winston.format((info) => {\n if (redactor) {\n return redactor(info);\n }\n return info;\n });\n\n const format = winston.format.combine(\n winston.format.timestamp(),\n redactionFormat(), // Apply redaction before JSON\n winston.format.json(),\n );\n\n const winstonLogger = winston.createLogger({\n level,\n format,\n defaultMeta: options.defaultContext,\n transports: [new winston.transports.Console()],\n });\n\n return createLoggerWrapper(winstonLogger);\n};\n","export const DEFAULT_LOG_LEVEL = 'info';\nexport const REQUEST_ID_KEY = 'x-request-id';\n","import { CreateLoggerOptions, createLogger } from './logger';\nimport { runWithLogger } from './context';\n\n// Generic Handler type compatible with AWS Lambda\n// We use 'any' to avoid strict dependency on @types/aws-lambda for this generic wrapper\n// but in practice it wraps (event, context, callback?) => Promise<any> | any\ntype Handler<TEvent = any, TResult = any> = (\n event: TEvent,\n context: any,\n callback?: any,\n) => Promise<TResult> | void;\n\n/**\n * Higher-order function to wrap a Lambda handler with logger context.\n * Automatically extracts 'awsRequestId' and 'functionName' from the Lambda context\n * and initializes a logger for the request scope.\n *\n * @param handler The original Lambda handler\n * @param options Logger options (level, redaction, etc.)\n */\nexport const withLogger = <TEvent = any, TResult = any>(\n handler: Handler<TEvent, TResult>,\n options: CreateLoggerOptions = {},\n): Handler<TEvent, TResult> => {\n return async (event: TEvent, context: any, callback?: any) => {\n // 1. Initialize Logger\n // Create a root logger if custom options are provided, or use default behavior.\n // We create a FRESH logger instance for each request to ensure context isolation if needed?\n // Actually, creating a logger instance is cheap?\n // Winston createLogger is relatively heavy.\n // Optimization: We could reuse a global logger and just child() it.\n // But options might change? Usually options are static.\n // Let's assume options are static per lambda wrapper usage.\n\n // HOWEVER, to support dynamic options per request might be overkill.\n // Let's create one global logger instance for this wrapper instantiation if possible?\n // But wait, the standard `createLogger` we implemented creates a NEW winston instance every time.\n // We should probably cache it?\n // For now, let's keep it simple: createLogger per request.\n // If performance becomes an issue, we can refactor `createLogger` to reuse the winston instance if options match.\n\n const rootLogger = createLogger(options);\n\n // 2. Extract Context\n const requestContext: Record<string, any> = {};\n if (context) {\n if (context.awsRequestId) requestContext.requestId = context.awsRequestId;\n if (context.functionName) requestContext.functionName = context.functionName;\n }\n\n // 3. Create Child Logger with Request Context\n const scopedLogger = rootLogger.child(requestContext);\n\n // 3a. Log Event and Context\n scopedLogger.debug('Lambda Event', { event });\n scopedLogger.debug('Lambda Context', { context });\n\n // 4. Run Handler in Context\n return runWithLogger(scopedLogger, async () => {\n // We use 'await' to ensure the context stays active during the handler execution\n // If the handler accepts a callback, we might need special handling?\n // Most modern lambdas use async/await.\n // If legacy callback style is used, AsyncLocalStorage context *should* still propagate\n // if the callback is invoked asynchronously.\n // But we wrap the result in Promise usually.\n\n // Supporting both async and callback style:\n try {\n // If it returns a promise, await it\n const result = await handler(event, context, callback);\n return result as TResult;\n } catch (error) {\n // We could log unhandled errors here too?\n // Standard lambda practice is to let the error propagate so Lambda runtime sees it (and retries etc)\n // BUT we should log it first because once it leaves here, we might lose the logger context behavior.\n scopedLogger.error('Unhandled Lambda Exception', error as Error);\n throw error;\n }\n });\n };\n};\n"],"mappings":"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","names":["crypto","message","winston"]}