idea-aws 4.3.2 → 4.3.4

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/dist/index.d.ts CHANGED
@@ -11,4 +11,4 @@ export * from './src/sns';
11
11
  export * from './src/ssm';
12
12
  export * from './src/translate';
13
13
  export * from './src/attachments';
14
- export * from './src/logger';
14
+ export * from './src/lambdaLogger';
package/dist/index.js CHANGED
@@ -27,4 +27,4 @@ __exportStar(require("./src/sns"), exports);
27
27
  __exportStar(require("./src/ssm"), exports);
28
28
  __exportStar(require("./src/translate"), exports);
29
29
  __exportStar(require("./src/attachments"), exports);
30
- __exportStar(require("./src/logger"), exports);
30
+ __exportStar(require("./src/lambdaLogger"), exports);
@@ -1,10 +1,12 @@
1
1
  import * as DDB from '@aws-sdk/lib-dynamodb';
2
2
  import * as DDBUtils from '@aws-sdk/util-dynamodb';
3
+ import { LambdaLogger } from './lambdaLogger';
3
4
  /**
4
5
  * A wrapper for AWS DynamoDB.
5
6
  */
6
7
  export declare class DynamoDB {
7
8
  protected dynamo: DDB.DynamoDBDocument;
9
+ protected logger: LambdaLogger;
8
10
  constructor();
9
11
  /**
10
12
  * Convert a JSON object from DynamoDB format to simple JSON.
@@ -29,11 +29,13 @@ const client_dynamodb_1 = require("@aws-sdk/client-dynamodb");
29
29
  const DDBUtils = __importStar(require("@aws-sdk/util-dynamodb"));
30
30
  const nanoid_1 = require("nanoid");
31
31
  const NanoID = (0, nanoid_1.customAlphabet)('0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz', 25);
32
+ const lambdaLogger_1 = require("./lambdaLogger");
32
33
  /**
33
34
  * A wrapper for AWS DynamoDB.
34
35
  */
35
36
  class DynamoDB {
36
37
  constructor() {
38
+ this.logger = new lambdaLogger_1.LambdaLogger();
37
39
  this.dynamo = DDB.DynamoDBDocument.from(new client_dynamodb_1.DynamoDB(), {
38
40
  marshallOptions: { convertEmptyValues: true, removeUndefinedValues: true, convertClassInstanceToMap: true }
39
41
  });
@@ -84,7 +86,7 @@ class DynamoDB {
84
86
  * @param key the key of the counter
85
87
  */
86
88
  async getAtomicCounterByKey(key) {
87
- console.debug(`Get atomic counter for ${key}`);
89
+ this.logger.trace(`Get atomic counter for ${key}`);
88
90
  const { Attributes } = await this.update({
89
91
  TableName: 'idea_atomicCounters',
90
92
  Key: { key },
@@ -101,7 +103,7 @@ class DynamoDB {
101
103
  * @param params the params to apply to DynamoDB's function
102
104
  */
103
105
  async get(params) {
104
- console.debug(`Get ${params.TableName}`);
106
+ this.logger.trace(`Get ${params.TableName}`);
105
107
  const { Item } = await this.dynamo.get(params);
106
108
  if (!Item)
107
109
  throw new Error('Not found');
@@ -112,7 +114,7 @@ class DynamoDB {
112
114
  * @param params the params to apply to DynamoDB's function
113
115
  */
114
116
  async put(params) {
115
- console.debug(`Put ${params.TableName}`);
117
+ this.logger.trace(`Put ${params.TableName}`);
116
118
  return await this.dynamo.put(params);
117
119
  }
118
120
  /**
@@ -120,7 +122,7 @@ class DynamoDB {
120
122
  * @param params the params to apply to DynamoDB's function
121
123
  */
122
124
  async update(params) {
123
- console.debug(`Update ${params.TableName}`);
125
+ this.logger.trace(`Update ${params.TableName}`);
124
126
  return await this.dynamo.update(params);
125
127
  }
126
128
  /**
@@ -128,7 +130,7 @@ class DynamoDB {
128
130
  * @param params the params to apply to DynamoDB's function
129
131
  */
130
132
  async delete(params) {
131
- console.debug(`Delete ${params.TableName}`);
133
+ this.logger.trace(`Delete ${params.TableName}`);
132
134
  return await this.dynamo.delete(params);
133
135
  }
134
136
  /**
@@ -139,7 +141,7 @@ class DynamoDB {
139
141
  */
140
142
  async batchGet(table, keys, ignoreErr) {
141
143
  if (!keys.length) {
142
- console.debug(`Batch get ${table}: no elements to get`);
144
+ this.logger.trace(`Batch get ${table}: no elements to get`);
143
145
  return [];
144
146
  }
145
147
  return await this.batchGetHelper(table, keys, [], Boolean(ignoreErr));
@@ -150,7 +152,7 @@ class DynamoDB {
150
152
  [table]: { Keys: keys.slice(currentChunk, currentChunk + chunkSize) }
151
153
  }
152
154
  };
153
- console.debug(`Batch get ${table}: ${currentChunk} of ${keys.length}`);
155
+ this.logger.trace(`Batch get ${table}: ${currentChunk} of ${keys.length}`);
154
156
  let result;
155
157
  try {
156
158
  result = await this.dynamo.batchGet(batch);
@@ -177,7 +179,7 @@ class DynamoDB {
177
179
  */
178
180
  async batchPut(table, items) {
179
181
  if (!items.length)
180
- return console.debug(`Batch write (put) ${table}: no elements to write`);
182
+ return this.logger.trace(`Batch write (put) ${table}: no elements to write`);
181
183
  await this.batchWriteHelper(table, items, true);
182
184
  }
183
185
  /**
@@ -189,11 +191,11 @@ class DynamoDB {
189
191
  */
190
192
  async batchDelete(table, keys) {
191
193
  if (!keys.length)
192
- return console.debug(`Batch write (delete) ${table}: no elements to write`);
194
+ return this.logger.trace(`Batch write (delete) ${table}: no elements to write`);
193
195
  await this.batchWriteHelper(table, keys, false);
194
196
  }
195
197
  async batchWriteHelper(table, itemsOrKeys, isPut, currentChunk = 0, chunkSize = 25) {
196
- console.debug(`Batch write (${isPut ? 'put' : 'delete'}) ${table}: ${currentChunk} of ${itemsOrKeys.length}`);
198
+ this.logger.trace(`Batch write (${isPut ? 'put' : 'delete'}) ${table}: ${currentChunk} of ${itemsOrKeys.length}`);
197
199
  let requests;
198
200
  if (isPut)
199
201
  requests = itemsOrKeys.slice(currentChunk, currentChunk + chunkSize).map(i => ({ PutRequest: { Item: i } }));
@@ -218,7 +220,7 @@ class DynamoDB {
218
220
  params.RequestItems = response.UnprocessedItems;
219
221
  attempts++;
220
222
  const waitSeconds = getRandomInt(attempts * 5);
221
- console.debug(`Batch write throttled: waiting ${waitSeconds} seconds to retry`);
223
+ this.logger.trace(`Batch write throttled: waiting ${waitSeconds} seconds to retry`);
222
224
  await wait(waitSeconds);
223
225
  }
224
226
  else {
@@ -231,9 +233,9 @@ class DynamoDB {
231
233
  * @param params the params to apply to DynamoDB's function
232
234
  */
233
235
  async query(params) {
234
- console.debug(`Query ${params.TableName}`);
236
+ this.logger.trace(`Query ${params.TableName}`);
235
237
  const result = await this.queryScanHelper(params, [], true);
236
- console.debug(`Results query ${params.TableName}: ${result.length ?? 0}`);
238
+ this.logger.trace(`Results query ${params.TableName}: ${result.length ?? 0}`);
237
239
  return result;
238
240
  }
239
241
  /**
@@ -241,9 +243,9 @@ class DynamoDB {
241
243
  * @param params the params to apply to DynamoDB's function
242
244
  */
243
245
  async scan(params) {
244
- console.debug(`Scan ${params.TableName}`);
246
+ this.logger.trace(`Scan ${params.TableName}`);
245
247
  const result = await this.queryScanHelper(params, [], false);
246
- console.debug(`Results scan ${params.TableName}: ${result.length ?? 0}`);
248
+ this.logger.trace(`Results scan ${params.TableName}: ${result.length ?? 0}`);
247
249
  return result;
248
250
  }
249
251
  async queryScanHelper(params, items, isQuery) {
@@ -265,9 +267,9 @@ class DynamoDB {
265
267
  * @param params the params to apply to DynamoDB's function
266
268
  */
267
269
  async queryClassic(params) {
268
- console.debug(`Query classic ${params.TableName}`);
270
+ this.logger.trace(`Query classic ${params.TableName}`);
269
271
  const result = await this.dynamo.query(params);
270
- console.debug(`Results query classic ${params.TableName}: ${result.Items.length ?? 0}`);
272
+ this.logger.trace(`Results query classic ${params.TableName}: ${result.Items.length ?? 0}`);
271
273
  return result;
272
274
  }
273
275
  /**
@@ -275,9 +277,9 @@ class DynamoDB {
275
277
  * @param params the params to apply to DynamoDB's function
276
278
  */
277
279
  async scanClassic(params) {
278
- console.debug(`Scan classic ${params.TableName}`);
280
+ this.logger.trace(`Scan classic ${params.TableName}`);
279
281
  const result = await this.dynamo.scan(params);
280
- console.debug(`Results scan classic ${params.TableName}: ${result.Items.length ?? 0}`);
282
+ this.logger.trace(`Results scan classic ${params.TableName}: ${result.Items.length ?? 0}`);
281
283
  return result;
282
284
  }
283
285
  /**
@@ -286,8 +288,8 @@ class DynamoDB {
286
288
  */
287
289
  async transactWrites(ops) {
288
290
  if (!ops.length)
289
- return console.debug('Transaction writes: no elements to write');
290
- console.debug('Transaction writes');
291
+ return this.logger.trace('Transaction writes: no elements to write');
292
+ this.logger.trace('Transaction writes');
291
293
  await this.dynamo.transactWrite({ TransactItems: ops });
292
294
  }
293
295
  }
@@ -1,12 +1,12 @@
1
1
  import 'source-map-support/register';
2
- import { Logger } from './logger';
2
+ import { LambdaLogger } from './lambdaLogger';
3
3
  /**
4
4
  * An abstract class to inherit to manage some resources with an AWS Lambda function.
5
5
  */
6
6
  export declare abstract class GenericController {
7
7
  protected event: any;
8
8
  protected callback: any;
9
- protected logger: Logger;
9
+ protected logger: LambdaLogger;
10
10
  /**
11
11
  * Initialize a new GenericController helper object.
12
12
  * @param event the event that invoked the AWS lambda function
@@ -14,11 +14,47 @@ export declare abstract class GenericController {
14
14
  */
15
15
  constructor(event: any, callback: any);
16
16
  /**
17
- * The main function, that handle the request and should terminate with an invokation of the method `done`.
17
+ * The main function (to override), that handles the request and must terminate invoking the method `done`.
18
18
  */
19
- abstract handleRequest(): void;
19
+ handleRequest(): Promise<void>;
20
20
  /**
21
21
  * Default callback for the Lambda.
22
22
  */
23
- protected done(error: Error | any, res?: any): void;
23
+ protected done(error?: Error | any, res?: any): void;
24
+ /**
25
+ * Remap an error to manage the logging and make sure no unhandled error is returned to the requester.
26
+ */
27
+ protected handleControllerError(err: Error | HandledError | any, interceptedInContext: string, replaceWithMessage: string): HandledError | UnhandledError;
28
+ /**
29
+ * Get the current log level for the current Lambda function's `logger`.
30
+ * Note: "FATAL" means that no log will be printed.
31
+ */
32
+ getLambdaLogLevel(): 'TRACE' | 'DEBUG' | 'INFO' | 'WARN' | 'ERROR' | 'FATAL';
33
+ /**
34
+ * Set the log level for the current Lambda function's `logger`.
35
+ */
36
+ setLambdaLogLevel(logLevel: 'TRACE' | 'DEBUG' | 'INFO' | 'WARN' | 'ERROR' | 'FATAL'): void;
37
+ /**
38
+ * Raise the log level of the current Lambda function's `logger` to "FATAL", hence avoiding printing any log.
39
+ */
40
+ silentLambdaLogs(): void;
41
+ }
42
+ /**
43
+ * A specific type of error in the context of the Controller, to distinguish from "unhandled" errors.
44
+ */
45
+ export declare class HandledError extends Error {
46
+ constructor(message: string);
47
+ }
48
+ /**
49
+ * An unhandled error thrown inside the controller (i.e. `!(error instanceof HandledError)`) .
50
+ */
51
+ export declare class UnhandledError extends Error {
52
+ /**
53
+ * The context where the unhandled error was intercepted.
54
+ */
55
+ unhandled: string;
56
+ /**
57
+ * The original error message before it was replaced by a public-facing message.
58
+ */
59
+ internalMessage: string;
24
60
  }
@@ -1,8 +1,8 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.GenericController = void 0;
3
+ exports.UnhandledError = exports.HandledError = exports.GenericController = void 0;
4
4
  require("source-map-support/register");
5
- const logger_1 = require("./logger");
5
+ const lambdaLogger_1 = require("./lambdaLogger");
6
6
  /**
7
7
  * An abstract class to inherit to manage some resources with an AWS Lambda function.
8
8
  */
@@ -13,19 +13,77 @@ class GenericController {
13
13
  * @param callback the callback to resolve or reject the execution
14
14
  */
15
15
  constructor(event, callback) {
16
- this.logger = new logger_1.Logger();
16
+ this.logger = new lambdaLogger_1.LambdaLogger();
17
17
  this.event = event;
18
18
  this.callback = callback;
19
19
  }
20
+ /**
21
+ * The main function (to override), that handles the request and must terminate invoking the method `done`.
22
+ */
23
+ async handleRequest() {
24
+ this.logger.info('START');
25
+ this.done();
26
+ }
20
27
  /**
21
28
  * Default callback for the Lambda.
22
29
  */
23
- done(error, res) {
24
- if (error)
25
- this.logger.error('END-FAILED', error);
30
+ done(error = null, res) {
31
+ if (error) {
32
+ if (error.unhandled)
33
+ this.logger.error('END-FAILED', error);
34
+ else
35
+ this.logger.warn('END-FAILED', error);
36
+ }
26
37
  else
27
38
  this.logger.info('END-SUCCESS');
28
39
  this.callback(error, res);
29
40
  }
41
+ /**
42
+ * Remap an error to manage the logging and make sure no unhandled error is returned to the requester.
43
+ */
44
+ handleControllerError(err, interceptedInContext, replaceWithMessage) {
45
+ if (err instanceof HandledError)
46
+ return err;
47
+ const error = err;
48
+ error.unhandled = interceptedInContext;
49
+ error.internalMessage = error.message;
50
+ error.message = replaceWithMessage;
51
+ return error;
52
+ }
53
+ /**
54
+ * Get the current log level for the current Lambda function's `logger`.
55
+ * Note: "FATAL" means that no log will be printed.
56
+ */
57
+ getLambdaLogLevel() {
58
+ return process.env.AWS_LAMBDA_LOG_LEVEL;
59
+ }
60
+ /**
61
+ * Set the log level for the current Lambda function's `logger`.
62
+ */
63
+ setLambdaLogLevel(logLevel) {
64
+ process.env.AWS_LAMBDA_LOG_LEVEL = logLevel;
65
+ }
66
+ /**
67
+ * Raise the log level of the current Lambda function's `logger` to "FATAL", hence avoiding printing any log.
68
+ */
69
+ silentLambdaLogs() {
70
+ process.env.AWS_LAMBDA_LOG_LEVEL = 'FATAL';
71
+ }
30
72
  }
31
73
  exports.GenericController = GenericController;
74
+ /**
75
+ * A specific type of error in the context of the Controller, to distinguish from "unhandled" errors.
76
+ */
77
+ class HandledError extends Error {
78
+ constructor(message) {
79
+ super(message);
80
+ Object.setPrototypeOf(this, HandledError.prototype);
81
+ }
82
+ }
83
+ exports.HandledError = HandledError;
84
+ /**
85
+ * An unhandled error thrown inside the controller (i.e. `!(error instanceof HandledError)`) .
86
+ */
87
+ class UnhandledError extends Error {
88
+ }
89
+ exports.UnhandledError = UnhandledError;
@@ -0,0 +1,13 @@
1
+ /**
2
+ * Manage structured logging in the context of a Lambda function.
3
+ * Note: the log level is controlled by each Lambda function's configuration.
4
+ */
5
+ export declare class LambdaLogger {
6
+ shouldLog: (logLevel: 'TRACE' | 'DEBUG' | 'INFO' | 'WARN' | 'ERROR' | 'FATAL') => boolean;
7
+ trace: (summary: string, content?: object) => void;
8
+ debug: (summary: string, content?: object) => void;
9
+ info: (summary: string, content?: object) => void;
10
+ warn: (summary: string, error: Error | any, content?: object) => void;
11
+ error: (summary: string, error: Error | any, content?: object) => void;
12
+ }
13
+ export declare const LOG_LEVELS_PRIORITY: Record<string, number>;
@@ -0,0 +1,43 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.LOG_LEVELS_PRIORITY = exports.LambdaLogger = void 0;
4
+ /**
5
+ * Manage structured logging in the context of a Lambda function.
6
+ * Note: the log level is controlled by each Lambda function's configuration.
7
+ */
8
+ class LambdaLogger {
9
+ constructor() {
10
+ // note: this is needed as long as the Lambda functions don't become reactive to changes to `AWS_LAMBDA_LOG_LEVEL`
11
+ this.shouldLog = (logLevel) => exports.LOG_LEVELS_PRIORITY[logLevel] >= exports.LOG_LEVELS_PRIORITY[process.env.AWS_LAMBDA_LOG_LEVEL];
12
+ this.trace = (summary, content = {}) => {
13
+ if (this.shouldLog('TRACE'))
14
+ console.trace({ summary, ...content });
15
+ };
16
+ this.debug = (summary, content = {}) => {
17
+ if (this.shouldLog('DEBUG'))
18
+ console.debug({ summary, ...content });
19
+ };
20
+ this.info = (summary, content = {}) => {
21
+ if (this.shouldLog('INFO'))
22
+ console.info({ summary, ...content });
23
+ };
24
+ this.warn = (summary, error, content = {}) => {
25
+ if (this.shouldLog('WARN'))
26
+ console.warn({ summary, ...content, error });
27
+ };
28
+ this.error = (summary, error, content = {}) => {
29
+ if (this.shouldLog('ERROR'))
30
+ console.error({ summary, ...content, error });
31
+ };
32
+ }
33
+ }
34
+ exports.LambdaLogger = LambdaLogger;
35
+ // levels here are identical to bunyan practices (https://github.com/trentm/node-bunyan#levels)
36
+ exports.LOG_LEVELS_PRIORITY = {
37
+ TRACE: 10,
38
+ DEBUG: 20,
39
+ INFO: 30,
40
+ WARN: 40,
41
+ ERROR: 50,
42
+ FATAL: 60
43
+ };
@@ -43,7 +43,6 @@ export declare abstract class ResourceController extends GenericController {
43
43
  */
44
44
  protected getQueryParamAsArray(paramName: string): string[];
45
45
  handleRequest: () => Promise<void>;
46
- private remapHandlerError;
47
46
  protected done(error?: Error | any, rawResult?: any, statusCode?: number): void;
48
47
  /**
49
48
  * To @override
@@ -225,9 +224,3 @@ export interface InternalAPIRequestParams {
225
224
  */
226
225
  body?: any;
227
226
  }
228
- /**
229
- * Explicitly define a specific type of error to use in the RC's handler, to distinguish it from the normal errors.
230
- */
231
- export declare class RCError extends Error {
232
- constructor(message: string);
233
- }
@@ -23,7 +23,7 @@ var __importStar = (this && this.__importStar) || function (mod) {
23
23
  return result;
24
24
  };
25
25
  Object.defineProperty(exports, "__esModule", { value: true });
26
- exports.RCError = exports.ResourceController = void 0;
26
+ exports.ResourceController = void 0;
27
27
  const fs_1 = require("fs");
28
28
  const Lambda = __importStar(require("@aws-sdk/client-lambda"));
29
29
  const EventBridge = __importStar(require("@aws-sdk/client-eventbridge"));
@@ -49,6 +49,7 @@ class ResourceController extends genericController_1.GenericController {
49
49
  /// REQUEST HANDLERS
50
50
  ///
51
51
  this.handleRequest = async () => {
52
+ this.logger.info('START', { event: this.getEventSummary() });
52
53
  if (this.initError)
53
54
  return;
54
55
  try {
@@ -77,7 +78,7 @@ class ResourceController extends genericController_1.GenericController {
77
78
  response = await this.headResource();
78
79
  break;
79
80
  default:
80
- this.done(new RCError('Unsupported method'));
81
+ this.done(new genericController_1.HandledError('Unsupported method'));
81
82
  }
82
83
  }
83
84
  else {
@@ -102,17 +103,17 @@ class ResourceController extends genericController_1.GenericController {
102
103
  response = await this.headResources();
103
104
  break;
104
105
  default:
105
- this.done(new RCError('Unsupported method'));
106
+ this.done(new genericController_1.HandledError('Unsupported method'));
106
107
  }
107
108
  }
108
109
  this.done(null, response);
109
110
  }
110
111
  catch (err) {
111
- this.done(this.remapHandlerError(err, 'HANDLER-ERROR', 'Operation failed'));
112
+ this.done(this.handleControllerError(err, 'HANDLER-ERROR', 'Operation failed'));
112
113
  }
113
114
  }
114
115
  catch (err) {
115
- this.done(this.remapHandlerError(err, 'AUTH-CHECK-ERROR', 'Forbidden'));
116
+ this.done(this.handleControllerError(err, 'AUTH-CHECK-ERROR', 'Forbidden'));
116
117
  }
117
118
  };
118
119
  this.event = event;
@@ -138,11 +139,10 @@ class ResourceController extends genericController_1.GenericController {
138
139
  }
139
140
  if (options.useMetrics)
140
141
  this.prepareMetrics();
141
- this.logger.info('START', { event: this.getEventSummary() });
142
142
  }
143
143
  catch (err) {
144
144
  this.initError = true;
145
- this.done(this.remapHandlerError(err, 'INIT-ERROR', 'Malformed request'));
145
+ this.done(this.handleControllerError(err, 'INIT-ERROR', 'Malformed request'));
146
146
  }
147
147
  }
148
148
  initFromEventV2(event, options) {
@@ -165,7 +165,7 @@ class ResourceController extends genericController_1.GenericController {
165
165
  this.body = (event.body ? JSON.parse(event.body) : {}) || {};
166
166
  }
167
167
  catch (error) {
168
- throw new RCError('Malformed body');
168
+ throw new genericController_1.HandledError('Malformed body');
169
169
  }
170
170
  }
171
171
  initFromEventV1(event, options) {
@@ -187,7 +187,7 @@ class ResourceController extends genericController_1.GenericController {
187
187
  this.body = (event.body ? JSON.parse(event.body) : {}) || {};
188
188
  }
189
189
  catch (error) {
190
- throw new RCError('Malformed body');
190
+ throw new genericController_1.HandledError('Malformed body');
191
191
  }
192
192
  }
193
193
  getEventSummary() {
@@ -213,22 +213,18 @@ class ResourceController extends genericController_1.GenericController {
213
213
  else
214
214
  return String(this.queryParams[paramName]).split(',');
215
215
  }
216
- remapHandlerError(err, interceptedInContext, replaceWithMessage) {
217
- if (err instanceof RCError)
218
- return err;
219
- const error = err;
220
- error.intercepted = interceptedInContext;
221
- error.internalMessage = error.message;
222
- error.message = replaceWithMessage;
223
- return error;
224
- }
225
216
  done(error, rawResult, statusCode = this.returnStatusCode ?? (error ? 400 : 200)) {
226
217
  const result = error ? { message: error.message } : rawResult ?? {};
227
- if (error)
228
- this.logger.error('END-FAILED', error, { statusCode, event: this.getEventSummary() });
229
- else
230
- this.logger.info('END-SUCCESS', { statusCode, event: this.getEventSummary() });
231
218
  this.logger.debug('END-DETAIL', { result: Array.isArray(result) ? { array: result.length } : result });
219
+ const finalLogContent = { statusCode, event: this.getEventSummary() };
220
+ if (error) {
221
+ if (error.unhandled)
222
+ this.logger.error('END-FAILED', error, finalLogContent);
223
+ else
224
+ this.logger.warn('END-FAILED', error, finalLogContent);
225
+ }
226
+ else
227
+ this.logger.info('END-SUCCESS', finalLogContent);
232
228
  if (this.logRequestsWithKey)
233
229
  this.storeLog(!error);
234
230
  if (this.metrics)
@@ -249,73 +245,73 @@ class ResourceController extends genericController_1.GenericController {
249
245
  * To @override
250
246
  */
251
247
  async getResource() {
252
- throw new RCError('Unsupported method');
248
+ throw new genericController_1.HandledError('Unsupported method');
253
249
  }
254
250
  /**
255
251
  * To @override
256
252
  */
257
253
  async postResource() {
258
- throw new RCError('Unsupported method');
254
+ throw new genericController_1.HandledError('Unsupported method');
259
255
  }
260
256
  /**
261
257
  * To @override
262
258
  */
263
259
  async putResource() {
264
- throw new RCError('Unsupported method');
260
+ throw new genericController_1.HandledError('Unsupported method');
265
261
  }
266
262
  /**
267
263
  * To @override
268
264
  */
269
265
  async deleteResource() {
270
- throw new RCError('Unsupported method');
266
+ throw new genericController_1.HandledError('Unsupported method');
271
267
  }
272
268
  /**
273
269
  * To @override
274
270
  */
275
271
  async headResource() {
276
- throw new RCError('Unsupported method');
272
+ throw new genericController_1.HandledError('Unsupported method');
277
273
  }
278
274
  /**
279
275
  * To @override
280
276
  */
281
277
  async getResources() {
282
- throw new RCError('Unsupported method');
278
+ throw new genericController_1.HandledError('Unsupported method');
283
279
  }
284
280
  /**
285
281
  * To @override
286
282
  */
287
283
  async postResources() {
288
- throw new RCError('Unsupported method');
284
+ throw new genericController_1.HandledError('Unsupported method');
289
285
  }
290
286
  /**
291
287
  * To @override
292
288
  */
293
289
  async putResources() {
294
- throw new RCError('Unsupported method');
290
+ throw new genericController_1.HandledError('Unsupported method');
295
291
  }
296
292
  /**
297
293
  * To @override
298
294
  */
299
295
  async patchResource() {
300
- throw new RCError('Unsupported method');
296
+ throw new genericController_1.HandledError('Unsupported method');
301
297
  }
302
298
  /**
303
299
  * To @override
304
300
  */
305
301
  async patchResources() {
306
- throw new RCError('Unsupported method');
302
+ throw new genericController_1.HandledError('Unsupported method');
307
303
  }
308
304
  /**
309
305
  * To @override
310
306
  */
311
307
  async deleteResources() {
312
- throw new RCError('Unsupported method');
308
+ throw new genericController_1.HandledError('Unsupported method');
313
309
  }
314
310
  /**
315
311
  * To @override
316
312
  */
317
313
  async headResources() {
318
- throw new RCError('Unsupported method');
314
+ throw new genericController_1.HandledError('Unsupported method');
319
315
  }
320
316
  ///
321
317
  /// HELPERS
@@ -537,18 +533,3 @@ class ResourceController extends genericController_1.GenericController {
537
533
  }
538
534
  }
539
535
  exports.ResourceController = ResourceController;
540
- /**
541
- * Explicitly define a specific type of error to use in the RC's handler, to distinguish it from the normal errors.
542
- */
543
- class RCError extends Error {
544
- constructor(message) {
545
- super(message);
546
- Object.setPrototypeOf(this, RCError.prototype);
547
- }
548
- }
549
- exports.RCError = RCError;
550
- /**
551
- * An unhandled error thrown inside the RC (i.e. `!(error instanceof RCError)`) .
552
- */
553
- class RCUnhandledError extends Error {
554
- }
package/dist/src/s3.d.ts CHANGED
@@ -1,11 +1,13 @@
1
1
  import * as AWSS3 from '@aws-sdk/client-s3';
2
2
  import { BodyDataTypes } from '@aws-sdk/lib-storage';
3
3
  import { SignedURL } from 'idea-toolbox';
4
+ import { LambdaLogger } from './lambdaLogger';
4
5
  /**
5
6
  * A wrapper for AWS Simple Storage Service.
6
7
  */
7
8
  export declare class S3 {
8
9
  protected s3: AWSS3.S3Client;
10
+ protected logger: LambdaLogger;
9
11
  protected DEFAULT_DOWNLOAD_BUCKET_PREFIX: string;
10
12
  protected DEFAULT_DOWNLOAD_BUCKET: string;
11
13
  protected DEFAULT_DOWNLOAD_BUCKET_SEC_TO_EXP: number;
package/dist/src/s3.js CHANGED
@@ -28,11 +28,13 @@ const AWSS3 = __importStar(require("@aws-sdk/client-s3"));
28
28
  const lib_storage_1 = require("@aws-sdk/lib-storage");
29
29
  const s3_request_presigner_1 = require("@aws-sdk/s3-request-presigner");
30
30
  const idea_toolbox_1 = require("idea-toolbox");
31
+ const lambdaLogger_1 = require("./lambdaLogger");
31
32
  /**
32
33
  * A wrapper for AWS Simple Storage Service.
33
34
  */
34
35
  class S3 {
35
36
  constructor() {
37
+ this.logger = new lambdaLogger_1.LambdaLogger();
36
38
  this.DEFAULT_DOWNLOAD_BUCKET_PREFIX = 'common';
37
39
  this.DEFAULT_DOWNLOAD_BUCKET = 'idea-downloads';
38
40
  this.DEFAULT_DOWNLOAD_BUCKET_SEC_TO_EXP = 180;
@@ -81,7 +83,7 @@ class S3 {
81
83
  * Make a copy of an object of the bucket.
82
84
  */
83
85
  async copyObject(options) {
84
- console.debug(`S3 copy object: ${options.key}`);
86
+ this.logger.trace(`S3 copy object: ${options.key}`);
85
87
  const command = new AWSS3.CopyObjectCommand({
86
88
  CopySource: options.copySource,
87
89
  Bucket: options.bucket,
@@ -93,7 +95,7 @@ class S3 {
93
95
  * Get an object from a S3 bucket.
94
96
  */
95
97
  async getObject(options) {
96
- console.debug(`S3 get object: ${options.key}`);
98
+ this.logger.trace(`S3 get object: ${options.key}`);
97
99
  const params = { Bucket: options.bucket, Key: options.key };
98
100
  if (options.filename)
99
101
  params.ResponseContentDisposition = `attachment; filename ="${(0, exports.cleanFilename)(options.filename)}"`;
@@ -127,14 +129,14 @@ class S3 {
127
129
  params.Metadata = options.metadata;
128
130
  if (options.filename)
129
131
  params.ContentDisposition = `attachment; filename ="${(0, exports.cleanFilename)(options.filename)}"`;
130
- console.debug(`S3 put object: ${options.key}`);
132
+ this.logger.trace(`S3 put object: ${options.key}`);
131
133
  return await this.s3.send(new AWSS3.PutObjectCommand(params));
132
134
  }
133
135
  /**
134
136
  * Delete an object from an S3 bucket.
135
137
  */
136
138
  async deleteObject(options) {
137
- console.debug(`S3 delete object: ${options.key}`);
139
+ this.logger.trace(`S3 delete object: ${options.key}`);
138
140
  const deleteCommand = new AWSS3.DeleteObjectCommand({ Bucket: options.bucket, Key: options.key });
139
141
  return await this.s3.send(deleteCommand);
140
142
  }
@@ -142,7 +144,7 @@ class S3 {
142
144
  * List the objects of an S3 bucket.
143
145
  */
144
146
  async listObjects(options) {
145
- console.debug(`S3 list object: ${options.prefix}`);
147
+ this.logger.trace(`S3 list object: ${options.prefix}`);
146
148
  const command = new AWSS3.ListObjectsCommand({ Bucket: options.bucket, Prefix: options.prefix });
147
149
  return await this.s3.send(command);
148
150
  }
package/dist/src/ses.d.ts CHANGED
@@ -2,11 +2,13 @@
2
2
  import * as AWSSES from '@aws-sdk/client-sesv2';
3
3
  import { SentMessageInfo as NodemailerSentMessageInfo } from 'nodemailer';
4
4
  import { Headers } from 'nodemailer/lib/mailer';
5
+ import { LambdaLogger } from './lambdaLogger';
5
6
  /**
6
7
  * A wrapper for AWS Simple Email Service.
7
8
  */
8
9
  export declare class SES {
9
10
  protected ses: AWSSES.SESv2Client;
11
+ protected logger: LambdaLogger;
10
12
  constructor(options?: {
11
13
  region?: string;
12
14
  });
package/dist/src/ses.js CHANGED
@@ -28,11 +28,13 @@ const client_ses_1 = require("@aws-sdk/client-ses");
28
28
  const AWSSES = __importStar(require("@aws-sdk/client-sesv2"));
29
29
  const nodemailer_1 = require("nodemailer");
30
30
  const dynamoDB_1 = require("./dynamoDB");
31
+ const lambdaLogger_1 = require("./lambdaLogger");
31
32
  /**
32
33
  * A wrapper for AWS Simple Email Service.
33
34
  */
34
35
  class SES {
35
36
  constructor(options = {}) {
37
+ this.logger = new lambdaLogger_1.LambdaLogger();
36
38
  this.ses = new AWSSES.SESv2Client({ region: options.region });
37
39
  }
38
40
  //
@@ -104,7 +106,7 @@ class SES {
104
106
  ses = this.ses;
105
107
  else
106
108
  ses = new AWSSES.SESv2Client({ region: sesParams.region });
107
- console.debug('SES send templated email');
109
+ this.logger.trace('SES send templated email');
108
110
  return await ses.send(command);
109
111
  }
110
112
  /**
@@ -144,7 +146,7 @@ class SES {
144
146
  ses = this.ses;
145
147
  else
146
148
  ses = new AWSSES.SESv2Client({ region: sesParams.region });
147
- console.debug('SES send email');
149
+ this.logger.trace('SES send email');
148
150
  return await ses.send(command);
149
151
  }
150
152
  async sendEmailWithNodemailer(emailData, sesParams) {
@@ -165,7 +167,7 @@ class SES {
165
167
  mailOptions.attachments = emailData.attachments;
166
168
  // note: Nodemailer doesn't support SESv2 as of August 2023
167
169
  const ses = new client_ses_1.SESClient({ region: sesParams.region });
168
- console.debug('SES send email (Nodemailer)');
170
+ this.logger.trace('SES send email (Nodemailer)');
169
171
  // note: this is a workaround to make Nodemailer works with AWS SDK 3;
170
172
  // see: https://github.com/nodemailer/nodemailer/issues/1430
171
173
  return await (0, nodemailer_1.createTransport)({ SES: { ses, aws: { SendRawEmailCommand: client_ses_1.SendRawEmailCommand } } }).sendMail(mailOptions);
package/dist/src/sns.d.ts CHANGED
@@ -1,10 +1,12 @@
1
1
  import * as AWSSNS from '@aws-sdk/client-sns';
2
2
  import { PushNotificationsPlatforms } from 'idea-toolbox';
3
+ import { LambdaLogger } from './lambdaLogger';
3
4
  /**
4
5
  * A wrapper for AWS Simple Notification Service.
5
6
  */
6
7
  export declare class SNS {
7
8
  protected client: AWSSNS.SNSClient;
9
+ protected logger: LambdaLogger;
8
10
  constructor(options?: {
9
11
  region?: string;
10
12
  });
package/dist/src/sns.js CHANGED
@@ -26,11 +26,13 @@ Object.defineProperty(exports, "__esModule", { value: true });
26
26
  exports.SNS = void 0;
27
27
  const AWSSNS = __importStar(require("@aws-sdk/client-sns"));
28
28
  const idea_toolbox_1 = require("idea-toolbox");
29
+ const lambdaLogger_1 = require("./lambdaLogger");
29
30
  /**
30
31
  * A wrapper for AWS Simple Notification Service.
31
32
  */
32
33
  class SNS {
33
34
  constructor(options = {}) {
35
+ this.logger = new lambdaLogger_1.LambdaLogger();
34
36
  this.client = new AWSSNS.SNSClient({ region: options.region });
35
37
  }
36
38
  /**
@@ -52,7 +54,7 @@ class SNS {
52
54
  default:
53
55
  throw new Error('Unsupported platform');
54
56
  }
55
- console.debug('SNS add platform endpoint');
57
+ this.logger.trace('SNS add platform endpoint');
56
58
  const command = new AWSSNS.CreatePlatformEndpointCommand({ PlatformApplicationArn: platformARN, Token: token });
57
59
  const { EndpointArn } = await this.client.send(command);
58
60
  return EndpointArn;
@@ -80,7 +82,7 @@ class SNS {
80
82
  default:
81
83
  throw new Error('Unsupported platform');
82
84
  }
83
- console.debug('SNS publish in topic');
85
+ this.logger.trace('SNS publish in topic');
84
86
  const command = new AWSSNS.PublishCommand({
85
87
  MessageStructure: 'json',
86
88
  Message: JSON.stringify(structuredMessage),
@@ -1,3 +1,4 @@
1
+ import { DynamoDBRecord } from 'aws-lambda';
1
2
  import { GenericController } from './genericController';
2
3
  /**
3
4
  * An abstract class to inherit to manage AWS DDB streams in an AWS Lambda function.
@@ -5,4 +6,6 @@ import { GenericController } from './genericController';
5
6
  export declare abstract class StreamController extends GenericController {
6
7
  records: any[];
7
8
  constructor(event: any, callback: any);
9
+ protected abstract handleRecord(record: DynamoDBRecord): Promise<void>;
10
+ handleRequest(): Promise<void>;
8
11
  }
@@ -9,7 +9,12 @@ class StreamController extends genericController_1.GenericController {
9
9
  constructor(event, callback) {
10
10
  super(event, callback);
11
11
  this.records = event.Records ?? [];
12
- this.logger.info('START STREAM', { records: this.records.length ?? 0 });
12
+ }
13
+ async handleRequest() {
14
+ this.logger.info('START', { streamOfRecords: this.records.length ?? 0 });
15
+ await Promise.all(this.records.map(record => this.handleRecord(record)))
16
+ .then(() => this.done())
17
+ .catch((err) => this.done(this.handleControllerError(err, 'STREAM-ERROR', 'Operation failed')));
13
18
  }
14
19
  }
15
20
  exports.StreamController = StreamController;
package/index.ts CHANGED
@@ -13,4 +13,4 @@ export * from './src/ssm';
13
13
  export * from './src/translate';
14
14
  export * from './src/attachments';
15
15
 
16
- export * from './src/logger';
16
+ export * from './src/lambdaLogger';
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "idea-aws",
3
- "version": "4.3.2",
3
+ "version": "4.3.4",
4
4
  "description": "AWS wrappers to use in IDEA's back-ends",
5
5
  "main": "dist/index.js",
6
6
  "types": "dist/index.d.ts",