From 7582e17d7bd4bc43d4ee90c78144c7fa6b4d80bc Mon Sep 17 00:00:00 2001 From: Amar Vasekar Date: Mon, 31 Aug 2020 13:02:18 +0530 Subject: [PATCH 1/6] Changes: 1. Added awslambda.ts file in integrations folder. 2. Added AWSLambda integration in index.ts file 3. Added new packages in package.json file. --- packages/integrations/package.json | 16 +- packages/integrations/src/awslambda.ts | 230 +++++++++++++++++++++++++ packages/integrations/src/index.ts | 1 + 3 files changed, 241 insertions(+), 6 deletions(-) create mode 100644 packages/integrations/src/awslambda.ts diff --git a/packages/integrations/package.json b/packages/integrations/package.json index 0823005bcf08..bc401092c763 100644 --- a/packages/integrations/package.json +++ b/packages/integrations/package.json @@ -1,6 +1,6 @@ { "name": "@sentry/integrations", - "version": "5.22.3", + "version": "5.22.2", "description": "Pluggable integrations that can be used to enhance JS SDKs", "repository": "git://github.com/getsentry/sentry-javascript.git", "homepage": "https://github.com/getsentry/sentry-javascript/tree/master/packages/integrations", @@ -16,13 +16,17 @@ "module": "esm/index.js", "types": "dist/index.d.ts", "dependencies": { - "@sentry/types": "5.22.3", - "@sentry/utils": "5.22.3", + "@sentry/angular": "^5.22.2", + "@sentry/types": "5.22.2", + "@sentry/utils": "5.22.2", + "aws-sdk": "^2.741.0", + "fs": "0.0.1-security", "localforage": "1.8.1", - "tslib": "^1.9.3" + "tslib": "^1.9.3", + "uuid": "^8.3.0" }, "devDependencies": { - "@sentry-internal/eslint-config-sdk": "5.22.3", + "@sentry-internal/eslint-config-sdk": "5.22.2", "chai": "^4.1.2", "eslint": "7.6.0", "jest": "^24.7.1", @@ -32,7 +36,7 @@ "rollup": "^1.10.1", "rollup-plugin-commonjs": "^9.3.4", "rollup-plugin-node-resolve": "^4.2.3", - "rollup-plugin-terser": "^4.0.4", + "rollup-plugin-terser": "^7.0.0", "rollup-plugin-typescript2": "^0.21.0", "typescript": "3.7.5" }, diff --git a/packages/integrations/src/awslambda.ts b/packages/integrations/src/awslambda.ts new file mode 100644 index 000000000000..178905fe1ccf --- /dev/null +++ b/packages/integrations/src/awslambda.ts @@ -0,0 +1,230 @@ +import * as Sentry from '@sentry/browser'; +import { EventProcessor, Hub, Integration, Scope } from '@sentry/types'; + +/** + * NodeJS integration + * + * Provides a mechanism for NodeJS + * that raises an exception for handled, unhandled, timeout and similar times of error + * and captures the same in Sentry Dashboard + */ +export class AWSLambda implements Integration { + /** + * @inheritDoc + */ + public static id: string = 'AWSLambda'; + + /** + * @inheritDoc + */ + public name: string = AWSLambda.id; + + /** + * context. + */ + private _awsContext: { + getRemainingTimeInMillis: () => number; + callbackWaitsForEmptyEventLoop: boolean; + awsRequestId: string; + functionName: string; + functionVersion: string; + invokedFunctionArn: string; + logGroupName: string; + logStreamName: string; + }; + + /** + * timeout flag. + */ + private _timeoutWarning?: boolean = false; + + /** + * @inheritDoc + */ + + /** + * flush time in milliseconds to set time for flush. + */ + private _flushTime?: number; + + public constructor(options: { context?: any; timeoutWarning?: boolean; flushTime?: number } = {}) { + this._awsContext = options.context; + this._timeoutWarning = options.timeoutWarning; + this._flushTime = options.flushTime; + } + + /** + * @inheritDoc + */ + public setupOnce(_: (callback: EventProcessor) => void, getCurrentHub: () => Hub): void { + const lambdaBootstrap: any = require.main; + + /** configured time to timeout error and calculate execution time */ + const configuredTimeInMilliseconds = this._awsContext.getRemainingTimeInMillis(); + + if (!this._awsContext && !lambdaBootstrap) { + return; + } + const processEnv: any = process.env; + /** rapid runtime instance */ + const rapidRuntime = lambdaBootstrap.children[0].exports; + + /** handler that is invoked in case of unhandled and handled exception */ + const originalPostInvocationError = rapidRuntime.prototype.postInvocationError; + + const hub = getCurrentHub && getCurrentHub(); + + /** + * This function sets Additional Runtime Data which are displayed in Sentry Dashboard + * @param scope - holds additional event information + * @hidden + */ + const setAdditionalRuntimeData = (scope: Scope): void => { + scope.setContext('Runtime', { + Name: 'node', + // global.process.version return the version of node + Version: global.process.version, + }); + }; + + /** + * This function sets Additional Lambda Parameters which are displayed in Sentry Dashboard + * @param scope - holds additional event information + * @hidden + */ + const setAdditionalLambdaParameters = (scope: Scope): void => { + const remainingTimeInMillisecond: number = this._awsContext.getRemainingTimeInMillis(); + const executionTime: number = configuredTimeInMilliseconds - remainingTimeInMillisecond; + + scope.setExtra('lambda', { + aws_request_id: this._awsContext.awsRequestId, + function_name: this._awsContext.functionName, + function_version: this._awsContext.functionVersion, + invoked_function_arn: this._awsContext.invokedFunctionArn, + execution_duration_in_millis: executionTime, + remaining_time_in_millis: this._awsContext.getRemainingTimeInMillis(), + }); + }; + + /** + * This function use to generate cloud watch url + */ + const cloudwatchUrl = (): string => { + /** + * processEnv.AWS_REGION - this parameters given the AWS region + * processEnv.AWS_LAMBDA_FUNCTION_NAME - this parameter provides the AWS Lambda Function name + */ + return `https://${processEnv.AWS_REGION}.console.aws.amazon.com/cloudwatch/home?region=${processEnv.AWS_REGION}#logsV2:log-groups/log-group/$252Faws$252Flambda$252F${processEnv.AWS_LAMBDA_FUNCTION_NAME}`; + }; + + /** + * This function sets Cloud Watch Logs data which are displayed in Sentry Dashboard + * @param scope - holds additional event information + * @hidden + */ + const setCloudwatchLogsData = (scope: Scope): void => { + scope.setExtra('cloudwatch logs', { + log_group: this._awsContext.logGroupName, + log_stream: this._awsContext.logStreamName, + url: cloudwatchUrl(), + }); + }; + + /** + * This function sets tags which are displayed in Sentry Dashboard + * @param scope - holds additional event information + * @hidden + */ + + const setTags = (scope: Scope): void => { + scope.setTag('runtime', `node${global.process.version}`); + scope.setTag('transaction', this._awsContext.functionName); + scope.setTag('runtime.name', 'node'); + scope.setTag('server_name', processEnv._AWS_XRAY_DAEMON_ADDRESS); + scope.setTag('url', `awslambda:///${this._awsContext.functionName}`); + }; + + /** + * setting parameters in scope which will be displayed as additional data in Sentry dashboard + * @hidden + */ + const setParameters = (): void => { + // setting parameters in scope which will be displayed as additional data in Sentry dashboard + hub.configureScope((scope: Scope) => { + setTags(scope); + // runtime + setAdditionalRuntimeData(scope); + // setting the lambda parameters + setAdditionalLambdaParameters(scope); + // setting the cloudwatch logs parameter + setCloudwatchLogsData(scope); + // setting the sys.argv parameter + scope.setExtra('sys.argv', process.argv); + }); + }; + + // timeout warning buffer for timeout error + const timeoutWarningBuffer: number = 1500; + + /** check timeout flag and checking if configured Time In Milliseconds is greater than timeout Warning Buffer */ + if (this._timeoutWarning === true && configuredTimeInMilliseconds > timeoutWarningBuffer) { + const configuredTimeInSec = Math.floor(configuredTimeInMilliseconds / 1000); + const configuredTimeInMilli = configuredTimeInSec * 1000; + + /** + * This function is invoked when there is timeout error + * Here, we make sure the error has been captured by Sentry Dashboard + * and then re-raise the exception + * @param configuredTime - configured time in seconds + * @hidden + */ + const timeOutError = (configuredTime: number): void => { + setTimeout(() => { + /** + * setting parameters in scope which will be displayed as additional data in Sentry dashboard + */ + setParameters(); + + const error = new Error( + `WARNING : Function is expected to get timed out. Configured timeout duration = ${configuredTimeInSec + + 1} seconds.`, + ); + + /** capturing the exception and re-directing it to the Sentry Dashboard */ + hub.captureException(error); + Sentry.flush(this._flushTime); + }, configuredTime); + }; + + this._awsContext.callbackWaitsForEmptyEventLoop = false; + timeOutError(configuredTimeInMilli); + } + + /** + * unhandled and handled exception + * @param error - holds the error captured in AWS Lambda Function + * @param id - holds event id value + * @param callback - callback function + */ + rapidRuntime.prototype.postInvocationError = async function( + error: Error, + id: string, + callback: () => void, + ): Promise { + /** + * setting parameters in scope which will be displayed as additional data in Sentry dashboard + */ + setParameters(); + + /** capturing the exception and re-directing it to the Sentry Dashboard */ + hub.captureException(error); + await Sentry.flush(this.flushTime); + + /** + * Here, we make sure the error has been captured by Sentry Dashboard + * and then re-raised the exception + */ + originalPostInvocationError.call(this, error, id, callback); + }; + } +} diff --git a/packages/integrations/src/index.ts b/packages/integrations/src/index.ts index f1ba52e92026..75e61096046f 100644 --- a/packages/integrations/src/index.ts +++ b/packages/integrations/src/index.ts @@ -10,3 +10,4 @@ export { RewriteFrames } from './rewriteframes'; export { SessionTiming } from './sessiontiming'; export { Transaction } from './transaction'; export { Vue } from './vue'; +export { AWSLambda } from './awslambda'; From 023e7596cc1effb5cf7095436bdef63d9e42b36d Mon Sep 17 00:00:00 2001 From: Amar Vasekar Date: Mon, 31 Aug 2020 17:32:17 +0530 Subject: [PATCH 2/6] Fixed lint issue for awslambda.ts file. --- packages/integrations/package.json | 2 + packages/integrations/src/awslambda.ts | 64 +++++++++++++++++--------- 2 files changed, 44 insertions(+), 22 deletions(-) diff --git a/packages/integrations/package.json b/packages/integrations/package.json index bc401092c763..8d93549b86fe 100644 --- a/packages/integrations/package.json +++ b/packages/integrations/package.json @@ -17,6 +17,7 @@ "types": "dist/index.d.ts", "dependencies": { "@sentry/angular": "^5.22.2", + "@sentry/browser": "^5.22.3", "@sentry/types": "5.22.2", "@sentry/utils": "5.22.2", "aws-sdk": "^2.741.0", @@ -27,6 +28,7 @@ }, "devDependencies": { "@sentry-internal/eslint-config-sdk": "5.22.2", + "@types/uuid": "^8.3.0", "chai": "^4.1.2", "eslint": "7.6.0", "jest": "^24.7.1", diff --git a/packages/integrations/src/awslambda.ts b/packages/integrations/src/awslambda.ts index 178905fe1ccf..5281e4bc0d09 100644 --- a/packages/integrations/src/awslambda.ts +++ b/packages/integrations/src/awslambda.ts @@ -1,6 +1,28 @@ import * as Sentry from '@sentry/browser'; import { EventProcessor, Hub, Integration, Scope } from '@sentry/types'; +interface AWSLambdaContext { + getRemainingTimeInMillis: () => number; + callbackWaitsForEmptyEventLoop: boolean; + awsRequestId: string; + functionName: string; + functionVersion: string; + invokedFunctionArn: string; + logGroupName: string; + logStreamName: string; +} + +interface Module { + // eslint-disable-next-line @typescript-eslint/no-explicit-any + exports: any; + id: string; + filename: string; + loaded: boolean; + parent: Module | null; + children: Module[]; + path: string; + paths: string[]; +} /** * NodeJS integration * @@ -22,16 +44,7 @@ export class AWSLambda implements Integration { /** * context. */ - private _awsContext: { - getRemainingTimeInMillis: () => number; - callbackWaitsForEmptyEventLoop: boolean; - awsRequestId: string; - functionName: string; - functionVersion: string; - invokedFunctionArn: string; - logGroupName: string; - logStreamName: string; - }; + private _awsContext!: AWSLambdaContext; /** * timeout flag. @@ -47,8 +60,11 @@ export class AWSLambda implements Integration { */ private _flushTime?: number; - public constructor(options: { context?: any; timeoutWarning?: boolean; flushTime?: number } = {}) { - this._awsContext = options.context; + public constructor(options: { context?: AWSLambdaContext; timeoutWarning?: boolean; flushTime?: number } = {}) { + if (options.context) { + this._awsContext = options.context; + } + this._timeoutWarning = options.timeoutWarning; this._flushTime = options.flushTime; } @@ -57,7 +73,7 @@ export class AWSLambda implements Integration { * @inheritDoc */ public setupOnce(_: (callback: EventProcessor) => void, getCurrentHub: () => Hub): void { - const lambdaBootstrap: any = require.main; + const lambdaBootstrap: Module | undefined = require.main; /** configured time to timeout error and calculate execution time */ const configuredTimeInMilliseconds = this._awsContext.getRemainingTimeInMillis(); @@ -65,12 +81,13 @@ export class AWSLambda implements Integration { if (!this._awsContext && !lambdaBootstrap) { return; } - const processEnv: any = process.env; + /** rapid runtime instance */ - const rapidRuntime = lambdaBootstrap.children[0].exports; + const rapidRuntime = lambdaBootstrap?.children[0].exports; /** handler that is invoked in case of unhandled and handled exception */ - const originalPostInvocationError = rapidRuntime.prototype.postInvocationError; + // eslint-disable-next-line @typescript-eslint/no-unsafe-member-access + const originalPostInvocationError = rapidRuntime?.prototype?.postInvocationError; const hub = getCurrentHub && getCurrentHub(); @@ -111,10 +128,10 @@ export class AWSLambda implements Integration { */ const cloudwatchUrl = (): string => { /** - * processEnv.AWS_REGION - this parameters given the AWS region - * processEnv.AWS_LAMBDA_FUNCTION_NAME - this parameter provides the AWS Lambda Function name + * process.env.AWS_REGION - this parameters given the AWS region + * process.env.AWS_LAMBDA_FUNCTION_NAME - this parameter provides the AWS Lambda Function name */ - return `https://${processEnv.AWS_REGION}.console.aws.amazon.com/cloudwatch/home?region=${processEnv.AWS_REGION}#logsV2:log-groups/log-group/$252Faws$252Flambda$252F${processEnv.AWS_LAMBDA_FUNCTION_NAME}`; + return `https://${process.env.AWS_REGION}.console.aws.amazon.com/cloudwatch/home?region=${process.env.AWS_REGION}#logsV2:log-groups/log-group/$252Faws$252Flambda$252F${process.env.AWS_LAMBDA_FUNCTION_NAME}`; }; /** @@ -140,7 +157,7 @@ export class AWSLambda implements Integration { scope.setTag('runtime', `node${global.process.version}`); scope.setTag('transaction', this._awsContext.functionName); scope.setTag('runtime.name', 'node'); - scope.setTag('server_name', processEnv._AWS_XRAY_DAEMON_ADDRESS); + scope.setTag('server_name', process.env._AWS_XRAY_DAEMON_ADDRESS || ''); scope.setTag('url', `awslambda:///${this._awsContext.functionName}`); }; @@ -163,6 +180,7 @@ export class AWSLambda implements Integration { }); }; + const flushTime = this._flushTime; // timeout warning buffer for timeout error const timeoutWarningBuffer: number = 1500; @@ -192,7 +210,7 @@ export class AWSLambda implements Integration { /** capturing the exception and re-directing it to the Sentry Dashboard */ hub.captureException(error); - Sentry.flush(this._flushTime); + void Sentry.flush(flushTime); }, configuredTime); }; @@ -206,6 +224,7 @@ export class AWSLambda implements Integration { * @param id - holds event id value * @param callback - callback function */ + // eslint-disable-next-line @typescript-eslint/no-unsafe-member-access rapidRuntime.prototype.postInvocationError = async function( error: Error, id: string, @@ -218,12 +237,13 @@ export class AWSLambda implements Integration { /** capturing the exception and re-directing it to the Sentry Dashboard */ hub.captureException(error); - await Sentry.flush(this.flushTime); + await Sentry.flush(flushTime); /** * Here, we make sure the error has been captured by Sentry Dashboard * and then re-raised the exception */ + // eslint-disable-next-line @typescript-eslint/no-unsafe-member-access originalPostInvocationError.call(this, error, id, callback); }; } From d1cf54c3dd12294652e91b19a0da3895b7d6d665 Mon Sep 17 00:00:00 2001 From: amar vasekar Date: Tue, 1 Sep 2020 15:22:48 +0530 Subject: [PATCH 3/6] 1. Fixed awslambda.ts and package.json file comments. --- packages/integrations/package.json | 8 +- packages/integrations/src/awslambda.ts | 127 +++++++++++++------------ 2 files changed, 68 insertions(+), 67 deletions(-) diff --git a/packages/integrations/package.json b/packages/integrations/package.json index 8d93549b86fe..7d5f14191759 100644 --- a/packages/integrations/package.json +++ b/packages/integrations/package.json @@ -16,15 +16,11 @@ "module": "esm/index.js", "types": "dist/index.d.ts", "dependencies": { - "@sentry/angular": "^5.22.2", "@sentry/browser": "^5.22.3", "@sentry/types": "5.22.2", "@sentry/utils": "5.22.2", - "aws-sdk": "^2.741.0", - "fs": "0.0.1-security", "localforage": "1.8.1", - "tslib": "^1.9.3", - "uuid": "^8.3.0" + "tslib": "^1.9.3" }, "devDependencies": { "@sentry-internal/eslint-config-sdk": "5.22.2", @@ -38,7 +34,7 @@ "rollup": "^1.10.1", "rollup-plugin-commonjs": "^9.3.4", "rollup-plugin-node-resolve": "^4.2.3", - "rollup-plugin-terser": "^7.0.0", + "rollup-plugin-terser": "^4.0.4", "rollup-plugin-typescript2": "^0.21.0", "typescript": "3.7.5" }, diff --git a/packages/integrations/src/awslambda.ts b/packages/integrations/src/awslambda.ts index 5281e4bc0d09..e6cc38ade660 100644 --- a/packages/integrations/src/awslambda.ts +++ b/packages/integrations/src/awslambda.ts @@ -1,4 +1,3 @@ -import * as Sentry from '@sentry/browser'; import { EventProcessor, Hub, Integration, Scope } from '@sentry/types'; interface AWSLambdaContext { @@ -60,13 +59,13 @@ export class AWSLambda implements Integration { */ private _flushTime?: number; - public constructor(options: { context?: AWSLambdaContext; timeoutWarning?: boolean; flushTime?: number } = {}) { + public constructor(options: { context?: AWSLambdaContext; timeoutWarning?: boolean; flushTimeout?: number } = {}) { if (options.context) { this._awsContext = options.context; } this._timeoutWarning = options.timeoutWarning; - this._flushTime = options.flushTime; + this._flushTime = options.flushTimeout; } /** @@ -75,21 +74,27 @@ export class AWSLambda implements Integration { public setupOnce(_: (callback: EventProcessor) => void, getCurrentHub: () => Hub): void { const lambdaBootstrap: Module | undefined = require.main; - /** configured time to timeout error and calculate execution time */ - const configuredTimeInMilliseconds = this._awsContext.getRemainingTimeInMillis(); - if (!this._awsContext && !lambdaBootstrap) { return; } + /** configured time to timeout error and calculate execution time */ + const configuredTimeInMilliseconds = + this._awsContext.getRemainingTimeInMillis && this._awsContext.getRemainingTimeInMillis(); /** rapid runtime instance */ - const rapidRuntime = lambdaBootstrap?.children[0].exports; + // const rapidRuntime = lambdaBootstrap?.children[0].exports; + let rapidRuntime; + try { + rapidRuntime = lambdaBootstrap?.children[0].exports; + } catch (err) { + rapidRuntime = {}; + } /** handler that is invoked in case of unhandled and handled exception */ // eslint-disable-next-line @typescript-eslint/no-unsafe-member-access const originalPostInvocationError = rapidRuntime?.prototype?.postInvocationError; - const hub = getCurrentHub && getCurrentHub(); + const hub = getCurrentHub(); /** * This function sets Additional Runtime Data which are displayed in Sentry Dashboard @@ -113,7 +118,7 @@ export class AWSLambda implements Integration { const remainingTimeInMillisecond: number = this._awsContext.getRemainingTimeInMillis(); const executionTime: number = configuredTimeInMilliseconds - remainingTimeInMillisecond; - scope.setExtra('lambda', { + scope.setContext('lambda', { aws_request_id: this._awsContext.awsRequestId, function_name: this._awsContext.functionName, function_version: this._awsContext.functionVersion, @@ -140,7 +145,7 @@ export class AWSLambda implements Integration { * @hidden */ const setCloudwatchLogsData = (scope: Scope): void => { - scope.setExtra('cloudwatch logs', { + scope.setContext('cloudwatch logs', { log_group: this._awsContext.logGroupName, log_stream: this._awsContext.logStreamName, url: cloudwatchUrl(), @@ -161,59 +166,49 @@ export class AWSLambda implements Integration { scope.setTag('url', `awslambda:///${this._awsContext.functionName}`); }; - /** - * setting parameters in scope which will be displayed as additional data in Sentry dashboard - * @hidden - */ - const setParameters = (): void => { - // setting parameters in scope which will be displayed as additional data in Sentry dashboard - hub.configureScope((scope: Scope) => { - setTags(scope); - // runtime - setAdditionalRuntimeData(scope); - // setting the lambda parameters - setAdditionalLambdaParameters(scope); - // setting the cloudwatch logs parameter - setCloudwatchLogsData(scope); - // setting the sys.argv parameter - scope.setExtra('sys.argv', process.argv); - }); - }; - const flushTime = this._flushTime; // timeout warning buffer for timeout error const timeoutWarningBuffer: number = 1500; - /** check timeout flag and checking if configured Time In Milliseconds is greater than timeout Warning Buffer */ - if (this._timeoutWarning === true && configuredTimeInMilliseconds > timeoutWarningBuffer) { - const configuredTimeInSec = Math.floor(configuredTimeInMilliseconds / 1000); - const configuredTimeInMilli = configuredTimeInSec * 1000; - - /** - * This function is invoked when there is timeout error - * Here, we make sure the error has been captured by Sentry Dashboard - * and then re-raise the exception - * @param configuredTime - configured time in seconds - * @hidden - */ - const timeOutError = (configuredTime: number): void => { - setTimeout(() => { - /** - * setting parameters in scope which will be displayed as additional data in Sentry dashboard - */ - setParameters(); - - const error = new Error( - `WARNING : Function is expected to get timed out. Configured timeout duration = ${configuredTimeInSec + - 1} seconds.`, - ); + const configuredTimeInSec = Math.floor(configuredTimeInMilliseconds / 1000); + const configuredTimeInMilli = configuredTimeInSec * 1000; + /** + * This function is invoked when there is timeout error + * Here, we make sure the error has been captured by Sentry Dashboard + * and then re-raise the exception + * @param configuredTime - configured time in seconds + * @hidden + */ + const timeOutError = (configuredTime: number): void => { + setTimeout(() => { + const error = new Error( + `WARNING : Function is expected to get timed out. Configured timeout duration = ${configuredTimeInSec + + 1} seconds.`, + ); + + // setting parameters in scope which will be displayed as additional data in Sentry dashboard + hub.withScope((scope: Scope) => { + setTags(scope); + // runtime + setAdditionalRuntimeData(scope); + // setting the lambda parameters + setAdditionalLambdaParameters(scope); + // setting the cloudwatch logs parameter + setCloudwatchLogsData(scope); + // setting the sys.argv parameter + scope.setExtra('sys.argv', process.argv); /** capturing the exception and re-directing it to the Sentry Dashboard */ hub.captureException(error); - void Sentry.flush(flushTime); - }, configuredTime); - }; + }); + /** capturing the exception and re-directing it to the Sentry Dashboard */ + void hub.getClient()?.flush(flushTime); + }, configuredTime); + }; + + /** check timeout flag and checking if configured Time In Milliseconds is greater than timeout Warning Buffer */ + if (this._timeoutWarning === true && configuredTimeInMilliseconds > timeoutWarningBuffer) { this._awsContext.callbackWaitsForEmptyEventLoop = false; timeOutError(configuredTimeInMilli); } @@ -230,14 +225,24 @@ export class AWSLambda implements Integration { id: string, callback: () => void, ): Promise { - /** - * setting parameters in scope which will be displayed as additional data in Sentry dashboard - */ - setParameters(); + // setting parameters in scope which will be displayed as additional data in Sentry dashboard + hub.withScope((scope: Scope) => { + setTags(scope); + // runtime + setAdditionalRuntimeData(scope); + // setting the lambda parameters + setAdditionalLambdaParameters(scope); + // setting the cloudwatch logs parameter + setCloudwatchLogsData(scope); + // setting the sys.argv parameter + scope.setExtra('sys.argv', process.argv); + /** capturing the exception and re-directing it to the Sentry Dashboard */ + hub.captureException(error); + }); /** capturing the exception and re-directing it to the Sentry Dashboard */ - hub.captureException(error); - await Sentry.flush(flushTime); + // hub.captureException(error); + await hub.getClient()?.flush(flushTime); /** * Here, we make sure the error has been captured by Sentry Dashboard From f3e76113edcbfc9f0cf98ce8a5331818d30373bb Mon Sep 17 00:00:00 2001 From: amar vasekar Date: Wed, 2 Sep 2020 18:06:07 +0530 Subject: [PATCH 4/6] Fixed git comments on awslambda.ts and package.json files --- packages/integrations/package.json | 4 +- packages/integrations/src/awslambda.ts | 86 +++++++++++--------------- 2 files changed, 38 insertions(+), 52 deletions(-) diff --git a/packages/integrations/package.json b/packages/integrations/package.json index 7d5f14191759..0e7f35dfc248 100644 --- a/packages/integrations/package.json +++ b/packages/integrations/package.json @@ -16,15 +16,13 @@ "module": "esm/index.js", "types": "dist/index.d.ts", "dependencies": { - "@sentry/browser": "^5.22.3", "@sentry/types": "5.22.2", "@sentry/utils": "5.22.2", "localforage": "1.8.1", "tslib": "^1.9.3" }, "devDependencies": { - "@sentry-internal/eslint-config-sdk": "5.22.2", - "@types/uuid": "^8.3.0", + "@sentry-internal/eslint-config-sdk": "5.22.3", "chai": "^4.1.2", "eslint": "7.6.0", "jest": "^24.7.1", diff --git a/packages/integrations/src/awslambda.ts b/packages/integrations/src/awslambda.ts index e6cc38ade660..ba0c7a5d7991 100644 --- a/packages/integrations/src/awslambda.ts +++ b/packages/integrations/src/awslambda.ts @@ -43,7 +43,7 @@ export class AWSLambda implements Integration { /** * context. */ - private _awsContext!: AWSLambdaContext; + private _awsContext: AWSLambdaContext = {} as AWSLambdaContext; /** * timeout flag. @@ -57,15 +57,18 @@ export class AWSLambda implements Integration { /** * flush time in milliseconds to set time for flush. */ - private _flushTime?: number; + private _flushTimeout?: number = 2000; public constructor(options: { context?: AWSLambdaContext; timeoutWarning?: boolean; flushTimeout?: number } = {}) { if (options.context) { this._awsContext = options.context; } - - this._timeoutWarning = options.timeoutWarning; - this._flushTime = options.flushTimeout; + if (options.timeoutWarning) { + this._timeoutWarning = options.timeoutWarning; + } + if (options.flushTimeout) { + this._flushTimeout = options.flushTimeout; + } } /** @@ -74,7 +77,7 @@ export class AWSLambda implements Integration { public setupOnce(_: (callback: EventProcessor) => void, getCurrentHub: () => Hub): void { const lambdaBootstrap: Module | undefined = require.main; - if (!this._awsContext && !lambdaBootstrap) { + if (!this._awsContext.awsRequestId || !lambdaBootstrap) { return; } /** configured time to timeout error and calculate execution time */ @@ -82,18 +85,17 @@ export class AWSLambda implements Integration { this._awsContext.getRemainingTimeInMillis && this._awsContext.getRemainingTimeInMillis(); /** rapid runtime instance */ - // const rapidRuntime = lambdaBootstrap?.children[0].exports; let rapidRuntime; - try { - rapidRuntime = lambdaBootstrap?.children[0].exports; - } catch (err) { - rapidRuntime = {}; + let originalPostInvocationError = function(): void { + return; + }; + if (lambdaBootstrap.children && lambdaBootstrap.children.length) { + rapidRuntime = lambdaBootstrap.children[0].exports; + /** handler that is invoked in case of unhandled and handled exception */ + // eslint-disable-next-line @typescript-eslint/no-unsafe-member-access + originalPostInvocationError = rapidRuntime.prototype.postInvocationError; } - /** handler that is invoked in case of unhandled and handled exception */ - // eslint-disable-next-line @typescript-eslint/no-unsafe-member-access - const originalPostInvocationError = rapidRuntime?.prototype?.postInvocationError; - const hub = getCurrentHub(); /** @@ -115,7 +117,8 @@ export class AWSLambda implements Integration { * @hidden */ const setAdditionalLambdaParameters = (scope: Scope): void => { - const remainingTimeInMillisecond: number = this._awsContext.getRemainingTimeInMillis(); + const remainingTimeInMillisecond: number = + this._awsContext.getRemainingTimeInMillis && this._awsContext.getRemainingTimeInMillis(); const executionTime: number = configuredTimeInMilliseconds - remainingTimeInMillisecond; scope.setContext('lambda', { @@ -124,20 +127,16 @@ export class AWSLambda implements Integration { function_version: this._awsContext.functionVersion, invoked_function_arn: this._awsContext.invokedFunctionArn, execution_duration_in_millis: executionTime, - remaining_time_in_millis: this._awsContext.getRemainingTimeInMillis(), + remaining_time_in_millis: remainingTimeInMillisecond, }); }; /** - * This function use to generate cloud watch url + * This variable use to generate cloud watch url + * process.env.AWS_REGION - this parameters given the AWS region + * process.env.AWS_LAMBDA_FUNCTION_NAME - this parameter provides the AWS Lambda Function name */ - const cloudwatchUrl = (): string => { - /** - * process.env.AWS_REGION - this parameters given the AWS region - * process.env.AWS_LAMBDA_FUNCTION_NAME - this parameter provides the AWS Lambda Function name - */ - return `https://${process.env.AWS_REGION}.console.aws.amazon.com/cloudwatch/home?region=${process.env.AWS_REGION}#logsV2:log-groups/log-group/$252Faws$252Flambda$252F${process.env.AWS_LAMBDA_FUNCTION_NAME}`; - }; + const cloudwatchUrl: string = `https://${process.env.AWS_REGION}.console.aws.amazon.com/cloudwatch/home?region=${process.env.AWS_REGION}#logsV2:log-groups/log-group/$252Faws$252Flambda$252F${process.env.AWS_LAMBDA_FUNCTION_NAME}`; /** * This function sets Cloud Watch Logs data which are displayed in Sentry Dashboard @@ -148,7 +147,7 @@ export class AWSLambda implements Integration { scope.setContext('cloudwatch logs', { log_group: this._awsContext.logGroupName, log_stream: this._awsContext.logStreamName, - url: cloudwatchUrl(), + url: cloudwatchUrl, }); }; @@ -157,7 +156,6 @@ export class AWSLambda implements Integration { * @param scope - holds additional event information * @hidden */ - const setTags = (scope: Scope): void => { scope.setTag('runtime', `node${global.process.version}`); scope.setTag('transaction', this._awsContext.functionName); @@ -166,21 +164,17 @@ export class AWSLambda implements Integration { scope.setTag('url', `awslambda:///${this._awsContext.functionName}`); }; - const flushTime = this._flushTime; + const flushTimeout = this._flushTimeout; + // timeout warning buffer for timeout error const timeoutWarningBuffer: number = 1500; - const configuredTimeInSec = Math.floor(configuredTimeInMilliseconds / 1000); const configuredTimeInMilli = configuredTimeInSec * 1000; - /** - * This function is invoked when there is timeout error - * Here, we make sure the error has been captured by Sentry Dashboard - * and then re-raise the exception - * @param configuredTime - configured time in seconds - * @hidden - */ - const timeOutError = (configuredTime: number): void => { + /** check timeout flag and checking if configured Time In Milliseconds is greater than timeout Warning Buffer */ + if (this._timeoutWarning === true && configuredTimeInMilliseconds > timeoutWarningBuffer) { + this._awsContext.callbackWaitsForEmptyEventLoop = false; + setTimeout(() => { const error = new Error( `WARNING : Function is expected to get timed out. Configured timeout duration = ${configuredTimeInSec + @@ -201,16 +195,8 @@ export class AWSLambda implements Integration { /** capturing the exception and re-directing it to the Sentry Dashboard */ hub.captureException(error); }); - - /** capturing the exception and re-directing it to the Sentry Dashboard */ - void hub.getClient()?.flush(flushTime); - }, configuredTime); - }; - - /** check timeout flag and checking if configured Time In Milliseconds is greater than timeout Warning Buffer */ - if (this._timeoutWarning === true && configuredTimeInMilliseconds > timeoutWarningBuffer) { - this._awsContext.callbackWaitsForEmptyEventLoop = false; - timeOutError(configuredTimeInMilli); + }, configuredTimeInMilli); + void hub.getClient()?.flush(flushTimeout); } /** @@ -241,8 +227,10 @@ export class AWSLambda implements Integration { }); /** capturing the exception and re-directing it to the Sentry Dashboard */ - // hub.captureException(error); - await hub.getClient()?.flush(flushTime); + const client = hub.getClient(); + if (client) { + await client.flush(flushTimeout); + } /** * Here, we make sure the error has been captured by Sentry Dashboard From ab6b8f1eabc1c1ec87e202d55cbad81e46a6a3bd Mon Sep 17 00:00:00 2001 From: amar vasekar Date: Fri, 4 Sep 2020 20:07:54 +0530 Subject: [PATCH 5/6] Changed: 1. Changed AWS lambda Integration in awslambda.ts 2. Changed git comments. 3. Export AWSLambdaIntegration in index.ts file --- packages/integrations/package.json | 6 +-- packages/integrations/src/awslambda.ts | 62 ++++++++++++++++---------- packages/integrations/src/index.ts | 2 +- 3 files changed, 42 insertions(+), 28 deletions(-) diff --git a/packages/integrations/package.json b/packages/integrations/package.json index 0e7f35dfc248..0823005bcf08 100644 --- a/packages/integrations/package.json +++ b/packages/integrations/package.json @@ -1,6 +1,6 @@ { "name": "@sentry/integrations", - "version": "5.22.2", + "version": "5.22.3", "description": "Pluggable integrations that can be used to enhance JS SDKs", "repository": "git://github.com/getsentry/sentry-javascript.git", "homepage": "https://github.com/getsentry/sentry-javascript/tree/master/packages/integrations", @@ -16,8 +16,8 @@ "module": "esm/index.js", "types": "dist/index.d.ts", "dependencies": { - "@sentry/types": "5.22.2", - "@sentry/utils": "5.22.2", + "@sentry/types": "5.22.3", + "@sentry/utils": "5.22.3", "localforage": "1.8.1", "tslib": "^1.9.3" }, diff --git a/packages/integrations/src/awslambda.ts b/packages/integrations/src/awslambda.ts index ba0c7a5d7991..78c0d09c552b 100644 --- a/packages/integrations/src/awslambda.ts +++ b/packages/integrations/src/awslambda.ts @@ -51,30 +51,44 @@ export class AWSLambda implements Integration { private _timeoutWarning?: boolean = false; /** - * @inheritDoc + * flush time in milliseconds to set time for flush. */ + private _flushTimeout: number = 2000; /** - * flush time in milliseconds to set time for flush. + * Assign Hub */ - private _flushTimeout?: number = 2000; + private _hub: Hub = {} as Hub; - public constructor(options: { context?: AWSLambdaContext; timeoutWarning?: boolean; flushTimeout?: number } = {}) { - if (options.context) { - this._awsContext = options.context; - } - if (options.timeoutWarning) { - this._timeoutWarning = options.timeoutWarning; - } - if (options.flushTimeout) { - this._flushTimeout = options.flushTimeout; - } + public constructor() { + // empty constructor } /** * @inheritDoc */ public setupOnce(_: (callback: EventProcessor) => void, getCurrentHub: () => Hub): void { + this._hub = getCurrentHub(); + } + + /** + * @inheritDoc + */ + public providedContext( + context: AWSLambdaContext, + timeoutWarning: boolean = false, + flushTimeout: number = 2000, + ): void { + if (context) { + this._awsContext = context; + } + if (flushTimeout) { + this._flushTimeout = flushTimeout; + } + if (timeoutWarning) { + this._timeoutWarning = timeoutWarning; + } + const flushTime = this._flushTimeout; const lambdaBootstrap: Module | undefined = require.main; if (!this._awsContext.awsRequestId || !lambdaBootstrap) { @@ -96,7 +110,7 @@ export class AWSLambda implements Integration { originalPostInvocationError = rapidRuntime.prototype.postInvocationError; } - const hub = getCurrentHub(); + const hub = this._hub; /** * This function sets Additional Runtime Data which are displayed in Sentry Dashboard @@ -104,10 +118,9 @@ export class AWSLambda implements Integration { * @hidden */ const setAdditionalRuntimeData = (scope: Scope): void => { - scope.setContext('Runtime', { - Name: 'node', - // global.process.version return the version of node - Version: global.process.version, + scope.setContext('runtime', { + name: 'node', + version: global.process.version, }); }; @@ -144,7 +157,7 @@ export class AWSLambda implements Integration { * @hidden */ const setCloudwatchLogsData = (scope: Scope): void => { - scope.setContext('cloudwatch logs', { + scope.setContext('cloudwatch.logs', { log_group: this._awsContext.logGroupName, log_stream: this._awsContext.logStreamName, url: cloudwatchUrl, @@ -162,10 +175,10 @@ export class AWSLambda implements Integration { scope.setTag('runtime.name', 'node'); scope.setTag('server_name', process.env._AWS_XRAY_DAEMON_ADDRESS || ''); scope.setTag('url', `awslambda:///${this._awsContext.functionName}`); + scope.setTag('handled', 'no'); + scope.setTag('mechanism', 'awslambda'); }; - const flushTimeout = this._flushTimeout; - // timeout warning buffer for timeout error const timeoutWarningBuffer: number = 1500; const configuredTimeInSec = Math.floor(configuredTimeInMilliseconds / 1000); @@ -196,7 +209,7 @@ export class AWSLambda implements Integration { hub.captureException(error); }); }, configuredTimeInMilli); - void hub.getClient()?.flush(flushTimeout); + void hub.getClient()?.flush(flushTime); } /** @@ -229,15 +242,16 @@ export class AWSLambda implements Integration { /** capturing the exception and re-directing it to the Sentry Dashboard */ const client = hub.getClient(); if (client) { - await client.flush(flushTimeout); + await client.flush(flushTime); } /** * Here, we make sure the error has been captured by Sentry Dashboard * and then re-raised the exception */ - // eslint-disable-next-line @typescript-eslint/no-unsafe-member-access originalPostInvocationError.call(this, error, id, callback); }; } } + +export const AWSLambdaIntegration = new AWSLambda(); diff --git a/packages/integrations/src/index.ts b/packages/integrations/src/index.ts index 75e61096046f..eb6f8978b556 100644 --- a/packages/integrations/src/index.ts +++ b/packages/integrations/src/index.ts @@ -10,4 +10,4 @@ export { RewriteFrames } from './rewriteframes'; export { SessionTiming } from './sessiontiming'; export { Transaction } from './transaction'; export { Vue } from './vue'; -export { AWSLambda } from './awslambda'; +export { AWSLambda, AWSLambdaIntegration } from './awslambda'; From 56ae1977c636592c41c3156d88664549b93323f6 Mon Sep 17 00:00:00 2001 From: amar vasekar Date: Tue, 8 Sep 2020 13:42:05 +0530 Subject: [PATCH 6/6] 1. Added unit test cases for AWS lambda for NodeJS with respective files. 2. Added awsLambdaPrelude.ts and setup.json files for creating development package. 3. Added fs module in package.json for unit test case. --- packages/integrations/package.json | 1 + .../integrations/test/awsLambdaPrelude.ts | 56 +++ packages/integrations/test/awslambda.test.ts | 351 ++++++++++++++++++ packages/integrations/test/setup.json | 21 ++ 4 files changed, 429 insertions(+) create mode 100644 packages/integrations/test/awsLambdaPrelude.ts create mode 100644 packages/integrations/test/awslambda.test.ts create mode 100644 packages/integrations/test/setup.json diff --git a/packages/integrations/package.json b/packages/integrations/package.json index 0823005bcf08..9ccb0fb157cb 100644 --- a/packages/integrations/package.json +++ b/packages/integrations/package.json @@ -18,6 +18,7 @@ "dependencies": { "@sentry/types": "5.22.3", "@sentry/utils": "5.22.3", + "fs": "0.0.1-security", "localforage": "1.8.1", "tslib": "^1.9.3" }, diff --git a/packages/integrations/test/awsLambdaPrelude.ts b/packages/integrations/test/awsLambdaPrelude.ts new file mode 100644 index 000000000000..f70250d3b2cf --- /dev/null +++ b/packages/integrations/test/awsLambdaPrelude.ts @@ -0,0 +1,56 @@ +/** + * This function returns the client file. + * @param additionalLambdaPrelude - Holds the scenario, timeoutWarning and error string. + */ + +export function lambdaPreludeReplacer(additionalLambdaPrelude: { + scenario: string; + timeoutWarning: boolean; + error: string; +}): string { + const lambdaPrelude = ` +const http = require('http'); +const Sentry = require('@sentry/node'); +const { AWSLambdaIntegration } = require('@sentry/integrations'); +const { HTTPSTransport } = require('@sentry/node/dist/transports'); + +class testTransport extends HTTPSTransport { + constructor() { + super(...arguments); + } + async sendEvent(event) { + console.log('Event:', JSON.stringify(event)); + } +} + +// Configure the Sentry SDK. +Sentry.init({ + dsn: 'https://e16b3f19d01b4989b118f20dbcc11f87@o388065.ingest.sentry.io/5224330', + transport: testTransport, + integrations: [AWSLambdaIntegration], +}); + +const lambdaBootstrap = require.main; +let rapidRuntime = lambdaBootstrap.children[0].exports; +rapidRuntime.prototype = { + postInvocationError: function(error, id, callback) {}, +}; + +exports.handler = ${additionalLambdaPrelude.scenario} (event,callback) => { + const context = { + functionVersion: '$LATEST', + functionName: 'aws-lambda-unit-test', + awsRequestId: '95a31fc8-7490-4474-aa7d-951aca216381', + getRemainingTimeInMillis: function getRemainingTimeInMillis() { + return 3000; + }, + }; + AWSLambdaIntegration.providedContext(context, ${additionalLambdaPrelude.timeoutWarning}, 2000); + + ${additionalLambdaPrelude.error} +}; + +exports.handler(); +`; + return lambdaPrelude; +} diff --git a/packages/integrations/test/awslambda.test.ts b/packages/integrations/test/awslambda.test.ts new file mode 100644 index 000000000000..8e4eb3a614cc --- /dev/null +++ b/packages/integrations/test/awslambda.test.ts @@ -0,0 +1,351 @@ +import childProcess from 'child_process'; +import fs from 'fs'; + +import { lambdaPreludeReplacer as lambdaFixures } from './awsLambdaPrelude'; + +/** + * This function create a development package. + * @param remainingPrelude - holds remaining prelude data + * @param callback -return the development package code output. + * @hidden + */ +function runLambdaFunction(remainingPrelude: any, callback: any) { + const lambdaPrelude = lambdaFixures(remainingPrelude); + + const __packageDir: string = './test/testCase'; + const __setupJsonFile: string = './test/setup.json'; + const __buildDist: string = './dist'; + + if (!fs.existsSync(__buildDist)) { + throw Error('Build not found in specified directory.'); + } + + if (fs.existsSync(__packageDir)) { + childProcess.execSync('cd ./test/; rm -rf testCase; cd ..; cd ..;'); + } + + fs.mkdirSync(__packageDir); + + fs.appendFileSync(`${__packageDir}/index.js`, lambdaPrelude); + + if (fs.existsSync(__setupJsonFile)) { + const packageData = fs.readFileSync(__setupJsonFile); + + fs.appendFileSync(`${__packageDir}/package.json`, packageData); + + childProcess.execSync('cd ./test/testCase; npm install --prefix; cd ..; cd ..;'); + childProcess.execSync('cp -r build dist esm ./test/testCase/node_modules/@sentry/integrations/'); + + /** + * Check the async/sync scenario, run the development package code and return the package output. + */ + if (remainingPrelude.scenario === 'async') { + const event = childProcess.execSync('node ./test/testCase/index.js'); + + const eventParser = event.toString('utf-8'); + const mainEvent = eventParser.split('Event:'); + callback(null, mainEvent[1]); + } else { + childProcess.exec('node ./test/testCase/index.js', (error, res) => { + if (error !== null) { + const mainEventSync = res.split('Event:'); + callback(null, JSON.parse(mainEventSync[1])); + } + }); + } + } +} + +/** + * Unit test case suit + */ +describe('AWS Lambda serverless test suit ', () => { + const timeout: number = 3000; + /** + * Test case for handled exception for sync and async scenario with timeout warning true. + */ + test( + 'should be capture handled exception for async scenario timeoutWarning=true', + done => { + const remainingPrelude = { + error: ` + notDefinedFunction(); + `, + scenario: 'async', + timeoutWarning: true, + }; + runLambdaFunction(remainingPrelude, (err: any, _res: any) => { + if (err) { + throw new Error(err); + } else { + const res = JSON.parse(_res); + expect(res.exception.values[0].type).toBe('ReferenceError'); + expect(res.exception.values[0].value).toBe('notDefinedFunction is not defined'); + } + }); + done(); + }, + timeout, + ); + + test( + 'should be capture handled exception for sync scenario timeoutWarning=true', + done => { + const remainingPrelude = { + error: ` + notDefinedFunction(); + `, + scenario: '', + timeoutWarning: true, + }; + runLambdaFunction(remainingPrelude, (err: any, _res: any) => { + if (err) { + throw new Error(err); + } else { + expect('ReferenceError').toBe(_res.exception.values[0].type); + expect('notDefinedFunction is not defined').toBe(_res.exception.values[0].value); + } + }); + done(); + }, + timeout, + ); + + /** + * Test case for handled exception for sync and async scenario with timeout warning false. + */ + test( + 'should be capture handled exception for async scenario timeoutWarning=false', + done => { + const remainingPrelude = { + error: ` + notDefinedFunction(); + `, + scenario: 'async', + timeoutWarning: false, + }; + runLambdaFunction(remainingPrelude, (err: any, _res: any) => { + if (err) { + throw new Error(err); + } else { + const res = JSON.parse(_res); + expect(res.exception.values[0].type).toBe('ReferenceError'); + expect(res.exception.values[0].value).toBe('notDefinedFunction is not defined'); + } + }); + done(); + }, + timeout, + ); + + test( + 'should be capture handled exception for sync scenario timeoutWarning=false', + done => { + const remainingPrelude = { + error: ` + notDefinedFunction(); + `, + scenario: '', + timeoutWarning: false, + }; + runLambdaFunction(remainingPrelude, (err: any, _res: any) => { + if (err) { + throw new Error(err); + } else { + expect('ReferenceError').toBe(_res.exception.values[0].type); + expect('notDefinedFunction is not defined').toBe(_res.exception.values[0].value); + } + }); + done(); + }, + timeout, + ); + + /** + * Test case for unhandled exception for sync and async scenario with timeout warning true. + */ + test( + 'should be capture unhandled exception for async scenario timeoutWarning=true', + done => { + const remainingPrelude = { + error: ` + throw new Error('Dummy error'); + `, + scenario: 'async', + timeoutWarning: true, + }; + runLambdaFunction(remainingPrelude, (err: any, _res: any) => { + if (err) { + throw new Error(err); + } else { + const res = JSON.parse(_res); + expect(res.exception.values[0].type).toBe('Error'); + expect(res.exception.values[0].value).toBe('Dummy error'); + } + }); + done(); + }, + timeout, + ); + + test( + 'should be capture unhandled exception for sync scenario timeoutWarning=true', + done => { + const remainingPrelude = { + error: ` + throw new Error('Dummy error'); + `, + scenario: '', + timeoutWarning: true, + }; + runLambdaFunction(remainingPrelude, (err: any, _res: any) => { + if (err) { + throw new Error(err); + } else { + expect(_res.exception.values[0].type).toBe('Error'); + expect(_res.exception.values[0].value).toBe('Dummy error'); + } + }); + done(); + }, + timeout, + ); + + /** + * Test case for handled exception for sync and async scenario with timeout warning false. + */ + test( + 'should be capture unhandled exception for async scenario timeoutWarning=false', + done => { + const remainingPrelude = { + error: ` + throw new Error('Dummy error'); + `, + scenario: 'async', + timeoutWarning: false, + }; + runLambdaFunction(remainingPrelude, (err: any, _res: any) => { + if (err) { + throw new Error(err); + } else { + const res = JSON.parse(_res); + expect(res.exception.values[0].type).toBe('Error'); + expect(res.exception.values[0].value).toBe('Dummy error'); + } + }); + done(); + }, + timeout, + ); + + test( + 'should be capture unhandled exception for sync scenario timeoutWarning=false', + done => { + const remainingPrelude = { + error: ` + throw new Error('Dummy error'); + `, + scenario: '', + timeoutWarning: false, + }; + runLambdaFunction(remainingPrelude, (err: any, _res: any) => { + if (err) { + throw new Error(err); + } else { + expect(_res.exception.values[0].type).toBe('Error'); + expect(_res.exception.values[0].value).toBe('Dummy error'); + } + }); + done(); + }, + timeout, + ); + + /** + * Test case for timeout error for sync and async scenario. + */ + test( + 'should be timeout error for async scenario timeoutWarning=true', + done => { + const remainingPrelude = { + error: ` + let data = []; + let ip_address = "192.0.2.1"; // Dummy IP which does not exist + let url = "http://" + ip_address + "/api/test"; + // let url = 'http://dummy.restapiexample.com/api/v1/employees'; + const response = await new Promise((resolve) => { + // callback function for the get the data from provided URL + const req = http.get(url, function (res) { + res.on("data", (chunk) => { + data += chunk; + }); + res.on("end", () => { + resolve({ + statusCode: 200, + body: JSON.parse(data), + }); + }); + }); + // error is showing in the console. + req.on("error", (e) => { + console.log("Error is:- " + e); + }); + }); + return response; + `, + scenario: 'async', + timeoutWarning: true, + }; + runLambdaFunction(remainingPrelude, (err: any, _res: any) => { + if (err) { + throw new Error(err); + } else { + const event = _res.split('\nError is:- Error: connect ETIMEDOUT'); + + const res = JSON.parse(event[0]); + + expect(res.exception.values[0].type).toBe('Error'); + expect(res.exception.values[0].value).toBe( + 'WARNING : Function is expected to get timed out. Configured timeout duration = 4 seconds.', + ); + } + }); + done(); + }, + timeout, + ); + + test( + 'should be timeout error for sync scenario timeoutWarning=true', + done => { + const remainingPrelude = { + error: ` + let data = []; + let ip_address = "192.0.2.1"; // Dummy IP which does not exist + let url = "http://" + ip_address + "/api/test"; + http + .get(url, (res) => { + callback(null, res.statusCode); + }) + .on("error", (e) => { + callback(Error(e)); + }); + `, + scenario: '', + timeoutWarning: true, + }; + runLambdaFunction(remainingPrelude, (err: any, _res: any) => { + if (err) { + throw new Error(err); + } else { + expect(_res.exception.values[0].type).toBe('Error'); + expect(_res.exception.values[0].value).toBe( + 'WARNING : Function is expected to get timed out. Configured timeout duration = 4 seconds.', + ); + } + }); + done(); + }, + timeout, + ); +}); diff --git a/packages/integrations/test/setup.json b/packages/integrations/test/setup.json new file mode 100644 index 000000000000..06d11874d2c9 --- /dev/null +++ b/packages/integrations/test/setup.json @@ -0,0 +1,21 @@ +{ + "name": "AWSLAMBDA", + "version": "1.0.0", + "description": "Test case for AWS Lambda for NodeJS.", + "main": "index.js", + "scripts": { + "test": "echo \"Error: no test specified\" && exit 1" + }, + "keywords": [], + "author": "Sentry", + "license": "MIT", + "dependencies": { + "@sentry/integrations": "^5.21.4", + "@sentry/node": "^5.19.0", + "lru-cache": "^5.1.1", + "lru-map": "^1.6.1", + "middy": "^0.36.0", + "tslib": "^1.13.0", + "util": "^0.12.3" + } +}