Skip to content
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

feat: #220 AWS Lambda Handler #244

Closed
wants to merge 33 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
33 commits
Select commit Hold shift + click to select a range
ce9ff9e
create empty project
miquelbeltran May 30, 2024
dd8ca53
wip aws-lambda package
miquelbeltran May 30, 2024
bac6a7d
wrap handle
miquelbeltran May 30, 2024
707f5ec
created aws example
miquelbeltran May 30, 2024
f6bd341
update files
miquelbeltran May 30, 2024
2b47322
add breadcrumb
miquelbeltran May 30, 2024
c2d03a1
fixing example
miquelbeltran May 31, 2024
2ba7263
add readme todo
miquelbeltran May 31, 2024
589b5ef
improvements in breadcrumbs
miquelbeltran May 31, 2024
301033c
create callback example
miquelbeltran May 31, 2024
ea7f701
cleanup
miquelbeltran May 31, 2024
b8d493c
improve packaging script
miquelbeltran Jun 4, 2024
9e599d9
implement tests in aws lambda
miquelbeltran Jun 4, 2024
46554e5
add more tests
miquelbeltran Jun 4, 2024
fc98875
GitHub CI action for aws-lambda
miquelbeltran Jun 4, 2024
29368f6
add exports
miquelbeltran Jun 4, 2024
cfdc35e
run ci on root
miquelbeltran Jun 4, 2024
52b09cc
run ci on root
miquelbeltran Jun 4, 2024
ed439e2
rename test file
miquelbeltran Jun 4, 2024
892be0b
prettier
miquelbeltran Jun 4, 2024
5f26c3c
enable eslint
miquelbeltran Jun 4, 2024
0d2f96a
install eslint and configure
miquelbeltran Jun 4, 2024
52de242
setup prettier for aws-lambda
miquelbeltran Jun 4, 2024
8ea4c68
Merge branch 'develop' into aws-lambda
miquelbeltran Jun 6, 2024
a8e8dc2
add more tests
miquelbeltran Jun 6, 2024
440309b
add README
miquelbeltran Jun 6, 2024
d2852b1
initial release
miquelbeltran Jun 6, 2024
515f8c4
Update README.md
miquelbeltran Jun 6, 2024
4dac6e8
improved docs
miquelbeltran Jun 7, 2024
737ee3d
extend tsconfig.json
miquelbeltran Jun 7, 2024
d18fa34
rename package to @raygun.io/aws-lambda
miquelbeltran Jun 7, 2024
07c62cb
update project
miquelbeltran Jun 7, 2024
46add25
text correction
miquelbeltran Jun 7, 2024
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
26 changes: 26 additions & 0 deletions .github/workflows/aws-lambda.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
name: AWS Lambda Handler

on:
push:
branches: [ "master", "develop" ]
pull_request:
branches: [ "master", "develop" ]

jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Use Node.js
uses: actions/setup-node@v4
with:
cache: 'npm'
# TODO: Remove when no longer using file dependency
- run: npm ci
working-directory: ./
- run: npm ci
working-directory: ./aws-lambda
- run: npm test
working-directory: ./aws-lambda
- run: npm run eslint
working-directory: ./aws-lambda
2 changes: 1 addition & 1 deletion .github/workflows/node.js.yml
Original file line number Diff line number Diff line change
Expand Up @@ -51,5 +51,5 @@ jobs:
# "dry" causes that if any file is modified, the job fails
dry: True
# "write" performs changes in place
prettier_options: --write lib/*.ts test/*.js examples/**/*.js
prettier_options: --write lib/*.ts test/*.js examples/**/*.js aws-lambda/lib/*.ts aws-lambda/test/*.js
github_token: ${{ secrets.PERSONAL_GITHUB_TOKEN }}
2 changes: 1 addition & 1 deletion .gitignore
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
/node_modules/
node_modules/
.idea/
yarn.lock

Expand Down
3 changes: 3 additions & 0 deletions aws-lambda/CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
## 0.0.1

- Initial release
136 changes: 136 additions & 0 deletions aws-lambda/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,136 @@
# Raygun AWS Lambda + Node

[![GitHub CI](https://github.com/MindscapeHQ/raygun4node/actions/workflows/aws-lambda.yml/badge.svg)](https://github.com/MindscapeHQ/raygun4node/actions)

Raygun.com package for AWS Lambda + Node, written in TypeScript.

This package improves the experience when using Raygun with AWS Lambda using JavaScript.

Provides two main advantages compared to using Raygun directly:

1. Captures any uncaught error in the AWS Lambda function and reports it to Raygun automatically.
2. Scopes Breadcrumbs to the current function session.

## Getting Started

Install the module with: `npm install @raygun.io/aws-lambda`

Set up the Raygun client as described in the [Raygun package](https://www.npmjs.com/package/raygun).

Import the `awsHandler` method:

```js
const { awsHandler } = require("@raygun/aws-lambda");
```

### Adding Raygun to a AWS Lambda function

To add Raygun to an existing AWS Lambda function, wrap the existing handler implementation with the `awsHandler` function.

Before:

```js
exports.handler = async function (event, context) {
// your code
}
```

After:

```js
// 1. Configure your Raygun client outside the AWS Lambda function
const client = new Raygun.client().init( ... );

// 2. Wrap the existing function with awsHandler
// 3. Pass the client as the first parameter
exports.handler = awsHandler({ client }, async function (event, context) {
// your code
});
```

### Using AWS Lambda functions with callbacks

Raygun AWS Lambda handler also supports functions with three parameters and callbacks.

```js
// Wrap the existing function with callback
exports.handler = awsHandler({ client }, function (event, context, callback) {
// your code
});
```

### Sending errors to Raygun from an AWS Lambda function

Any errors thrown in code will be automatically captured by the `awsHandler`.

```js
exports.handler = awsHandler({ client }, async function (event, context) {
// Captured by awsHandler and reported to Raygun
throw "error";
});
```

Errors are also automatically reported when using the callback version.

```js
exports.handler = awsHandler({ client }, function (event, context, callback) {
// Captured by awsHandler and reported to Raygun
callback("error", null);
});
```

As well, you can keep using the `send()` method.

```js
exports.handler = awsHandler({ client }, async function (event, context) {
await client.send("error");
});
```

**Important:** The `awsHandler` will rethrow the error back to AWS once it has been captured.

### Adding breadcrumbs to Raygun error reports

Breadcumbs are included automatically to all error reports sent from the `awsHandler` or when using the `send()` method.

Call to `client.addBreadcrumb()` to add breadcrumbs.

```js
exports.handler = awsHandler({ client }, async function (event, context) {
// Add a breadcrumb
client.addBreadcrumb("breadcrumb");

// Captured error includes the above breadcrumb
throw "error";
});
```

### AWS Lambda function context in Custom Data

The `awsHandler` also adds automatically the function call context in the "Custom Data" payload of the error report.

This payload can be found in the "Custom" tab in the Raygun Crash Reporting error report page.

```
context: {
callbackWaitsForEmptyEventLoop: true,
functionVersion: "$LATEST",
functionName: "xyz",
memoryLimitInMB: "128",
logGroupName: "/aws/lambda/xyz",
logStreamName: "2024/05/31/[$LATEST]xyz",
invokedFunctionArn: "arn:aws:lambda:xyz",
awsRequestId: "xyz"
}
```

## Release History

[View the changelog here](CHANGELOG.md)

## License

Copyright (c) 2024 Raygun Limited

Licensed under the MIT license.

4 changes: 4 additions & 0 deletions aws-lambda/eslint.config.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
// @ts-check
// Use config from root project
import config from "../eslint.config.mjs";
export default config;
89 changes: 89 additions & 0 deletions aws-lambda/lib/raygun.aws.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
import { Client } from "raygun";
import { Context, Handler } from "aws-lambda";
import { runWithBreadcrumbsAsync } from "raygun/build/raygun.breadcrumbs";

export type AwsHandlerConfig = {
client: Client;
};

// Custom async Handler type without callback
type AsyncHandler<TEvent, TResult> = (
event: TEvent,
context: Context,
) => Promise<TResult>;

// Wrap sync (event, context, callback) in Promise to convert to async (event, context)
function createAsyncHandler<TEvent, TResult>(
handler: Handler<TEvent, TResult>,
): AsyncHandler<TEvent, TResult> {
return (event: TEvent, context: Context) =>
new Promise((resolve, reject) => {
handler(event, context, (error, result) => {
if (error) {
reject(error);
} else if (result) {
resolve(result as never);
} else {
reject();
}
});
});
}

async function runHandler<TEvent, TResult>(
awsHandlerConfig: AwsHandlerConfig,
event: TEvent,
context: Context,
asyncHandler: AsyncHandler<TEvent, TResult>,
) {
awsHandlerConfig.client.addBreadcrumb(
`Running AWS Function: ${context.functionName}`,
);

try {
// Call to original handler and return value
return await asyncHandler(event, context);
} catch (e) {
// Capture error and send to Raygun

// Prepare send parameters
const customData = {
context: context,
};
const tags = ["AWS Handler"];
const sendParams = {
customData,
tags,
};

if (e instanceof Error || typeof e === "string") {
await awsHandlerConfig.client.send(e, sendParams);
} else {
await awsHandlerConfig.client.send(`AWS Handler error: ${e}`, sendParams);
}

// rethrow exception to AWS
throw e;
}
}

export function awsHandler<TEvent, TResult>(
awsHandlerConfig: AwsHandlerConfig,
handler: Handler<TEvent, TResult>,
): AsyncHandler<TEvent, TResult> {
let asyncHandler: AsyncHandler<TEvent, TResult>;
if (handler.length <= 2) {
// handler is async (event, context)
asyncHandler = handler as AsyncHandler<TEvent, TResult>;
} else {
// handler is sync (event, context, callback)
asyncHandler = createAsyncHandler(handler);
}

return async (event: TEvent, context: Context) => {
// Scope breadcrumbs to this handler event
return runWithBreadcrumbsAsync(() => {
return runHandler(awsHandlerConfig, event, context, asyncHandler);
});
};
}
Loading