@ikonintegration/ikapi 3.0.18 → 3.1.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/README.md CHANGED
@@ -50,7 +50,13 @@ export const handler = async (event, context) => {
50
50
  sensitiveFilteringKeywords: [], //replaced default blacklist set
51
51
  },
52
52
  //Queue
53
- publisher: { region: 'SNS-REGION' }
53
+ publisher: { region: 'SNS-REGION' },
54
+ //Resources tracker
55
+ resourcesTracker: { //by default, it will auto track (lambda, ecs, ddb)
56
+ busName: 'busName',
57
+ region: 'region',
58
+ appName: 'appName',
59
+ }
54
60
  };)).handleEvent(event, context));
55
61
  }
56
62
  ```
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ikonintegration/ikapi",
3
- "version": "3.0.18",
3
+ "version": "3.1.0",
4
4
  "description": "",
5
5
  "main": "index.js",
6
6
  "module": "main.js",
@@ -17,6 +17,7 @@
17
17
  "@aws-sdk/credential-provider-node": "^3.72.0",
18
18
  "@aws-sdk/node-http-handler": "^3.58.0",
19
19
  "@aws-sdk/util-dynamodb": "^3.72.0",
20
+ "@ikonintegration/mod-resources-tracker-client": "0.0.1",
20
21
  "abind": "^1.0.5",
21
22
  "bluebird": "^3.7.2",
22
23
  "email-templates": "^8.0.8",
@@ -22,7 +22,7 @@ export default class IKResponse {
22
22
  getBody() { return this._body; }
23
23
  appendIntoBody(key, value) { this._body[key] = value; }
24
24
  appendHeader(key, value) { this._headers[key] = value; }
25
- async build(context, transaction, isBatch) {
25
+ async build(context, transaction, doNotSucceed) {
26
26
  //Stream support
27
27
  if (this._streamingOut) return;
28
28
  if (this._isStream) return this._pipe(context);
@@ -42,7 +42,7 @@ export default class IKResponse {
42
42
  //Check for transaction response proxy
43
43
  if (transaction.responseProxy) await transaction.responseProxy(b);
44
44
  //Batch does not succeed directly just on upper transaction (which will should be a batch)
45
- if (!isBatch) context.succeed(b);
45
+ if (!doNotSucceed) context.succeed(b);
46
46
  }
47
47
  _pipe(context) {
48
48
  //Check if not streaming
@@ -10,6 +10,7 @@ import IKCacheRedis from '../Cache/Redis/IKRedis';
10
10
  import IKLogger from "../Logger/IKLogger";
11
11
  import IKValidation from '../Validation/IKValidation';
12
12
  import IKPublisher from '../Publisher/IKPublisher';
13
+ import IKExecutionTracker from '../Tracker/IKExecutionTracker';
13
14
  //
14
15
  export default class IKProcess {
15
16
  constructor(config, interval) {
@@ -20,6 +21,7 @@ export default class IKProcess {
20
21
  this.validator = new IKValidation(config.validation);
21
22
  this.db = this._getDBDriver();
22
23
  this.cache = this._getCacheDriver();
24
+ if (config.resourcesTracker) this.tracker = new IKExecutionTracker(config.resourcesTracker, context, this.db);
23
25
  }
24
26
 
25
27
  //Main interface
@@ -49,6 +51,8 @@ export default class IKProcess {
49
51
  this.logger.exception(e);
50
52
  //Rollback DB
51
53
  if (this.db) await this.db.rollback();
54
+ //Tracker
55
+ if (this.tracker) await this.tracker.stopTracking();
52
56
  } return executionFailed;
53
57
  }
54
58
  /* DB drivers support */
@@ -10,6 +10,7 @@ import IKCacheRedis from '../Cache/Redis/IKRedis';
10
10
  import IKLogger from "../Logger/IKLogger";
11
11
  import IKValidation from '../Validation/IKValidation';
12
12
  import IKPublisher from '../Publisher/IKPublisher';
13
+ import IKExecutionTracker from '../Tracker/IKExecutionTracker';
13
14
  //Request
14
15
  import IKRequest from '../API/IKRequest';
15
16
  import IKResponse, { IKBadRequestResponseWithRollback } from '../API/IKResponse';
@@ -20,12 +21,11 @@ export default class IKTransaction {
20
21
  this._event = event;
21
22
  this._context = context;
22
23
  this._config = config;
23
- //queue support
24
- this._isBatch = _isBatch;
25
- this._resp = null;
26
- //Step function support
24
+ //response behaviour flags
25
+ this._syncReturn = _isBatch;
27
26
  this._retrowErrors = _retrowErrors; /* retrow internal errors */
28
- //When set, this will be called with the response context right before calling the context suceed/fail
27
+ this._resp = null;
28
+ //When set, this will be called with the response context right before calling the context suceed/fail - useful for IOing the resp for example.
29
29
  this.responseProxy = null;
30
30
  //
31
31
  this.logger = new IKLogger(config.logger, Utils.logLevel(), (context.awsRequestId ? context.awsRequestId : (event.requestContext ? event.requestContext.requestId : 'unknown')));
@@ -33,6 +33,7 @@ export default class IKTransaction {
33
33
  this.publisher = new IKPublisher(config.publisher);
34
34
  this.validator = new IKValidation(config.validation);
35
35
  this.db = this._getDBDriver();
36
+ if (config.resourcesTracker) this.tracker = new IKExecutionTracker(config.resourcesTracker, context, this.db);
36
37
  this.cache = this._getCacheDriver();
37
38
  }
38
39
 
@@ -43,7 +44,7 @@ export default class IKTransaction {
43
44
  return await this._execute(executionFunc);
44
45
  });
45
46
  });
46
- if (this._isBatch) return this._resp;
47
+ if (this._syncReturn) return this._resp;
47
48
  }
48
49
  //Executions
49
50
  async _execute(executionFunc) {
@@ -54,11 +55,11 @@ export default class IKTransaction {
54
55
  this._resp = await executionFunc(this);
55
56
  //Answer client
56
57
  if (this._resp && this._resp instanceof IKResponse) {
57
- await this._resp.build(this._context, this, this._isBatch);
58
+ await this._resp.build(this._context, this, this._syncReturn);
58
59
  executionFailed = !!(this._resp.getBody() && this._resp.getBody().rollback);
59
60
  } else {
60
61
  this._resp = this._getErrorResponse(IKGlobals.ErrorResponseInvalidServerResponse, IKGlobals.ErrorCode_APIError)
61
- await this._resp.build(this._context, this, this._isBatch);
62
+ await this._resp.build(this._context, this, this._syncReturn);
62
63
  this.logger.error("Invalid response object from main request code.");
63
64
  }
64
65
  } catch (e) { /*EXECUTION FAIL*/
@@ -69,7 +70,7 @@ export default class IKTransaction {
69
70
  //envelope exception?
70
71
  if (executionFailed) {
71
72
  this._resp = this._getErrorResponse(IKGlobals.ErrorResponseUnhandledError, IKGlobals.ErrorCode_APIError);
72
- await this._resp.build(this._context, this, this._isBatch);
73
+ await this._resp.build(this._context, this, this._syncReturn);
73
74
  }
74
75
  } return executionFailed;
75
76
  }
@@ -101,6 +102,8 @@ export default class IKTransaction {
101
102
  try {
102
103
  await safeExecution();
103
104
  await this.logger.flushLogs();
105
+ //Tracker
106
+ if (this.tracker) await this.tracker.stopTracking();
104
107
  } catch (e) {
105
108
  this.logger.error('Exception when flushing logs.');
106
109
  this.logger.exception(e);
@@ -16,6 +16,7 @@ export default class IKDB_DDB extends IKDB {
16
16
  super(config, transaction);
17
17
  this.tableName = config.tableName;
18
18
  this.region = config.region;
19
+ this.stats = { readUnits: 0, writeUnits: 0 };
19
20
  //
20
21
  // const localConsole = (transaction ? transaction.logger : console);
21
22
  // if (config && this.tableName) { localConsole.debug(`Using table: ${this.tableName} on region: ${this.region}`); }
@@ -37,8 +38,9 @@ export default class IKDB_DDB extends IKDB {
37
38
  // localConsole.debug("Starting remote database connection");
38
39
  //initialize connection
39
40
  this.connection = new DynamoDB({
40
- region: this.region,
41
+ region: this.region, maxAttempts: IKGlobals.DDBMaxAttempts,
41
42
  requestHandler: new NodeHttpHandler({
43
+ connectionTimeout: IKGlobals.DDBHttpTimeout, socketTimeout: IKGlobals.DDBHttpTimeout,
42
44
  httpsAgent: new Agent({ keepAlive: false, maxSockets: 50, rejectUnauthorized: true }),
43
45
  })
44
46
  });
@@ -47,4 +49,8 @@ export default class IKDB_DDB extends IKDB {
47
49
  DDB_CONN_HASH = sha1(this.region);
48
50
  }
49
51
  }
52
+ getTrackerStats() {
53
+ if (!(this.stats.readUnits > 0 || this.state.writeUnits > 0)) return null;
54
+ return { tableName: this.tableName, ...this.stats };
55
+ }
50
56
  }
@@ -23,6 +23,7 @@ export default class IKDBQueryBatchWrite extends IKDBBaseQuery {
23
23
  localConsole.log('Batch writting items: ', query);
24
24
  const resp = await dbManager.connection.batchWriteItem(query);
25
25
  // localConsole.debug('Raw result: ', resp);
26
+ this._incrementTrackerStats(resp, dbManager);
26
27
  return resp;
27
28
  }
28
29
 
@@ -56,4 +57,8 @@ export default class IKDBQueryBatchWrite extends IKDBBaseQuery {
56
57
  expression.RequestItems[dbTableName].push({DeleteRequest: item});
57
58
  } return expression;
58
59
  }
60
+ /* tracker support */
61
+ _incrementTrackerStats(resp, dbManager) {
62
+ if (resp && resp.ConsumedCapacity && resp.ConsumedCapacity.CapacityUnits) dbManager.stats.writeUnits += parseFloat(resp.ConsumedCapacity.CapacityUnits);
63
+ }
59
64
  }
@@ -19,6 +19,7 @@ export default class IKDBQueryUpdate extends IKDBBaseQuery {
19
19
  localConsole.log("Deleting item: ", query);
20
20
  let resp = await dbManager.connection.deleteItem(query);
21
21
  // localConsole.debug('Raw result: ', resp);
22
+ this._incrementTrackerStats(resp, dbManager);
22
23
  return resp;
23
24
  }
24
25
 
@@ -26,4 +27,8 @@ export default class IKDBQueryUpdate extends IKDBBaseQuery {
26
27
  return { ...super._rawQuery(dbTableName, isTransaction),
27
28
  ...(isTransaction ? {} : {ReturnValues: "ALL_OLD" /*request all deleted values*/ })};
28
29
  }
30
+ /* tracker support */
31
+ _incrementTrackerStats(resp, dbManager) {
32
+ if (resp && resp.ConsumedCapacity && resp.ConsumedCapacity.CapacityUnits) dbManager.stats.writeUnits += parseFloat(resp.ConsumedCapacity.CapacityUnits);
33
+ }
29
34
  }
@@ -37,6 +37,12 @@ export default class IKDBQueryGet extends IKDBBaseQuery {
37
37
  localConsole.log('Querying: ', query, appendingItems ? appendingItems.length : null);
38
38
  const resp = await dbManager.connection.query(query);
39
39
  // localConsole.debug('Raw result: ', resp);
40
+ this._incrementTrackerStats(resp, dbManager);
40
41
  return resp;
41
42
  }
43
+
44
+ /* tracker support */
45
+ _incrementTrackerStats(resp, dbManager) {
46
+ if (resp && resp.ConsumedCapacity && resp.ConsumedCapacity.CapacityUnits) dbManager.stats.readUnits += parseFloat(resp.ConsumedCapacity.CapacityUnits);
47
+ }
42
48
  }
@@ -61,6 +61,7 @@ export default class IKDBQueryPut extends IKDBBaseQuery {
61
61
  localConsole.log('Putting item: ', query);
62
62
  const resp = await dbManager.connection.putItem(query);
63
63
  // localConsole.debug('Raw result: ', resp);
64
+ this._incrementTrackerStats(resp, dbManager);
64
65
  return resp;
65
66
  }
66
67
 
@@ -79,4 +80,8 @@ export default class IKDBQueryPut extends IKDBBaseQuery {
79
80
  }
80
81
  } return expression;
81
82
  }
83
+ /* tracker support */
84
+ _incrementTrackerStats(resp, dbManager) {
85
+ if (resp && resp.ConsumedCapacity && resp.ConsumedCapacity.CapacityUnits) dbManager.stats.writeUnits += parseFloat(resp.ConsumedCapacity.CapacityUnits);
86
+ }
82
87
  }
@@ -35,6 +35,11 @@ export default class IKDBQueryScan extends IKDBBaseQuery {
35
35
  localConsole.log('Scanning: ', query, appendingItems ? appendingItems.length : 0);
36
36
  const resp = await dbManager.connection.scan(query);
37
37
  // localConsole.debug('Raw result: ', resp);
38
+ this._incrementTrackerStats(resp, dbManager);
38
39
  return resp;
39
40
  }
41
+ /* tracker support */
42
+ _incrementTrackerStats(resp, dbManager) {
43
+ if (resp && resp.ConsumedCapacity && resp.ConsumedCapacity.CapacityUnits) dbManager.stats.readUnits += parseFloat(resp.ConsumedCapacity.CapacityUnits);
44
+ }
40
45
  }
@@ -96,6 +96,7 @@ export default class IKDBQueryUpdate extends IKDBBaseQuery {
96
96
  localConsole.log("Updating item: ", query);
97
97
  let resp = await dbManager.connection.updateItem(query);
98
98
  // localConsole.debug('Raw result: ', resp);
99
+ this._incrementTrackerStats(resp, dbManager);
99
100
  return resp;
100
101
  }
101
102
 
@@ -211,4 +212,8 @@ export default class IKDBQueryUpdate extends IKDBBaseQuery {
211
212
  }
212
213
  } return expression;
213
214
  }
215
+ /* tracker support */
216
+ _incrementTrackerStats(resp, dbManager) {
217
+ if (resp && resp.ConsumedCapacity && resp.ConsumedCapacity.CapacityUnits) dbManager.stats.writeUnits += parseFloat(resp.ConsumedCapacity.CapacityUnits);
218
+ }
214
219
  }
@@ -17,6 +17,8 @@ export default class IKDB_PSQL extends IKDB {
17
17
  async commit() { return await (await this._getConnection()).query('COMMIT'); }
18
18
  async rollback() { return await (await this._getConnection()).query('ROLLBACK'); }
19
19
  async runQuery(qry) { return await qry.run(await this._getConnection()); }
20
+ //tracker support
21
+ getTrackerStats() { return null; }
20
22
  //Public
21
23
  async sanitize(val) { return val; }
22
24
 
@@ -15,4 +15,7 @@ export default class IKDB {
15
15
 
16
16
  //main interface
17
17
  async runQuery(qry) { return await qry.run(this); }
18
+
19
+ //tracker support
20
+ getTrackerStats() { return null; }
18
21
  }
@@ -7,7 +7,7 @@ export default class IKEventProcessor {
7
7
  this.event = event;
8
8
  this.context = context;
9
9
  this.apiConfig = config;
10
- this.isBatch = isBatch;
10
+ this._syncReturn = isBatch;
11
11
  }
12
12
  async processEvent(execution, doNotDecodeMessage) {
13
13
  const resp = await this._processRawEvent(execution, doNotDecodeMessage);
@@ -21,7 +21,7 @@ export default class IKEventProcessor {
21
21
  //Map records with decoded message when required
22
22
  const decodedRecords = this.event.Records.map((eventRecord) => (doNotDecodeMessage ? eventRecord.body : JSON.parse(eventRecord.body)));
23
23
  //If is batch, return execution
24
- if (this.isBatch) return await execution(transaction, decodedRecords);
24
+ if (this._syncReturn) return await execution(transaction, decodedRecords);
25
25
  //for each available event
26
26
  for (let eventRecord of decodedRecords) {
27
27
  //Call execution
package/src/IKGlobals.js CHANGED
@@ -6,6 +6,9 @@ IKGlobals.DBDrivers = {
6
6
  IKGlobals.CacheDrivers = {
7
7
  REDIS: 'REDIS'
8
8
  };
9
+ //DDB Client
10
+ IKGlobals.DDBHttpTimeout = 15000;
11
+ IKGlobals.DDBMaxAttempts = 3;
9
12
  //Error messages
10
13
  IKGlobals.ErrorResponseValidationFail = 'Input validation failed: '; //400
11
14
  IKGlobals.ErrorResponseInvalidServerResponse = 'No valid response, this is a system error.'; //400
@@ -9,7 +9,7 @@ const LOG_STRINGS = ['DEBUG', 'INFO', 'WARN', 'ERROR'];
9
9
  const PURE_CONSOLE = (console.flushLogs ? console.origin : console);
10
10
  const DEFAULT_LOG_FUNCTION = PURE_CONSOLE.log.bind(PURE_CONSOLE);
11
11
  //
12
- const blacklist = ['password','phonenumber','code','resetCode','recaptchaToken','token','mfa','REFRESH_TOKEN','SECRET_HASH','SecretHash','AccessToken','UserCode','paymentMethodNonce'];
12
+ const blacklist = ['password','phonenumber'/*,'code'*/,'resetCode','recaptchaToken','token','mfa','REFRESH_TOKEN','SECRET_HASH','SecretHash','AccessToken','UserCode','paymentMethodNonce'];
13
13
  //
14
14
  export default class IKLogger {
15
15
  constructor(_config, _LOG_LEVEL, transactionID) {
@@ -81,7 +81,7 @@ export default class IKLogger {
81
81
  if (Utils.isOffline()) {
82
82
  return ` [${this._timestamp()} - ${LOG_STRINGS[level]}] [${caller}] ${msg.join(" ")}`;
83
83
  } else if (Utils.isHybridlessContainer() && this._transactionID) {
84
- return (isRaw ? '' : ` ${this._transactionID}`) + ` [${LOG_STRINGS[level]}] [${caller}] ${this._supressSensitiveInfo(msg.join(" "))}`;
84
+ return (isRaw ? '' : ` ${this._transactionID}`) + ` [${LOG_STRINGS[level]}] [${caller}] ${(msg.map(this._supressSensitiveInfo).join(" "))}`;
85
85
  } else {
86
86
  return ` [${LOG_STRINGS[level]}] [${caller}] ${msg.join(" ")}`;
87
87
  }
@@ -127,10 +127,10 @@ export default class IKLogger {
127
127
  Object.keys(value).forEach(function (elt, i, array) {
128
128
  const match = blacklist.find((f) => elt.toLowerCase().includes(f.toLowerCase()));
129
129
  if (match) value[elt] = '**SUPRESSED_SENSITIVE_DATA**';
130
- else value[elt] = supress(value[elt]);
130
+ else value[elt] = value[elt];
131
131
  });
132
132
  return value;
133
- } else if (Array.isArray(value)) return value.map(v=>supress(v));
133
+ } else if (Array.isArray(value)) return value.map(v=>this._supressSensitiveInfo(v));
134
134
  return value;
135
135
  }
136
136
  }
@@ -0,0 +1,57 @@
1
+ import { RTClient } from '@ikonintegration/mod-resources-tracker-client';
2
+ //
3
+ import Utils from "../API/IKUtils";
4
+ //
5
+ const fs = reuiqre('fs');
6
+ //
7
+ export default class IKExecutionTracker {
8
+ constructor(config, context, db) {
9
+ this.context = context;
10
+ this.db = db;
11
+ this.startedOn = Date.now();
12
+ if (config) this.client = this._getClient(config);
13
+ this.claimers = {};
14
+ }
15
+ /* Public */
16
+ appendClaimer(value, key) { this.claimers[value] = key; }
17
+ appendEvent(event) { this.client.appendEvent(event); }
18
+ //
19
+ async stopTracking() {
20
+ try {
21
+ //Check for client and claimers
22
+ if (!this.client || Object.keys(this.claimers).length == 0) return; //disabled
23
+ //detect execution and log (ECS or lambda)
24
+ this._detectExecutionAndLog();
25
+ //detect DB and log (DDB only for now)
26
+ this._collectDDBStats();
27
+ //drain stats
28
+ await this.client.drain();
29
+ } catch (e) { console.error(`Error while tracking execution: ${e}`); }
30
+ }
31
+ /* private tracker */
32
+ _detectExecutionAndLog() {
33
+ //Is running on container?
34
+ if (Utils.isHybridlessContainer()) {
35
+ //ECS, get metadata info and log if applicable and succeeded on it
36
+ const context = this._getECSMetadataObject();
37
+ if (context && context.containerInstanceArn) {
38
+ const extraDetails = { startTime: this.startedOn, endTime: Date.now(), taskConfig: { cpu: process.env.CPU, memory: process.env.MEMORY } };
39
+ this.client.appendEvent(RTClient.newECSExecutionEvent(context.ecsTaskId, context.containerInstanceArn, extraDetails));
40
+ }
41
+ } else this.client.appendEvent(RTClient.newLambdaInvocationEvent(this.context.awsRequestId)); //lambda logging
42
+ }
43
+ _collectDDBStats() {
44
+ const stats = this.db ? this.db.getTrackerStats() : null;
45
+ if (stats) this.client.appendEvent(RTClient.newDynamoEvent(stats.tableName, stats.readUnits, stats,writeUnits));
46
+ }
47
+ /* Private */
48
+ _getClient(config) {
49
+ return new RTClient({ busName: config.busName, region: config.region, source: config.appName });
50
+ }
51
+ /* ECS helper */
52
+ _getECSMetadataObject() {
53
+ try { return JSON.parse(fs.readFileSync(process.env.ECS_CONTAINER_METADATA_FILE)); }
54
+ catch (e) { console.error('Unable to retrieve metadata object: ' + e); }
55
+ return null;
56
+ }
57
+ }