Skip to content

Serverless integration for AWS Lambda - NodeJS #2865

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
wants to merge 6 commits into from
Closed
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 10 additions & 6 deletions packages/integrations/package.json
Original file line number Diff line number Diff line change
@@ -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",
Expand All @@ -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",
Expand All @@ -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"
},
Expand Down
230 changes: 230 additions & 0 deletions packages/integrations/src/awslambda.ts
Original file line number Diff line number Diff line change
@@ -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', {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
scope.setContext('Runtime', {
scope.setContext('runtime', {

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Changes are made in the file but not reflecting.

Name: 'node',
// global.process.version return the version of node
Version: global.process.version,
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
Name: 'node',
// global.process.version return the version of node
Version: global.process.version,
name: 'node',
// global.process.version return the version of node
version: global.process.version,

We usually never capitalize keys, or is there a specific reason doing so?

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Changes are made in the file but not reflecting.

});
};

/**
* 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<void> {
/**
* 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);
};
}
}
1 change: 1 addition & 0 deletions packages/integrations/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,3 +10,4 @@ export { RewriteFrames } from './rewriteframes';
export { SessionTiming } from './sessiontiming';
export { Transaction } from './transaction';
export { Vue } from './vue';
export { AWSLambda } from './awslambda';