Skip to content

Commit

Permalink
Add support for Sentry
Browse files Browse the repository at this point in the history
  • Loading branch information
Philipp Jardas committed Sep 22, 2022
1 parent efc055f commit b5ea920
Show file tree
Hide file tree
Showing 7 changed files with 272 additions and 4 deletions.
5 changes: 5 additions & 0 deletions .changeset/quiet-dots-tease.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"easy-apigateway": minor
---

Add support for Sentry
203 changes: 201 additions & 2 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

4 changes: 4 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -23,9 +23,13 @@
"express": ">= 4.17 < 5",
"yup": ">= 0.32.11"
},
"peerDependencies": {
"@sentry/serverless": ">= 7.13"
},
"devDependencies": {
"@changesets/changelog-github": "^0.4.1",
"@changesets/cli": "^2.18.0",
"@sentry/serverless": "^7.13.0",
"@types/aws-lambda": "^8.10.84",
"@types/etag": "^1.8.1",
"@types/express": "^4.17.13",
Expand Down
19 changes: 17 additions & 2 deletions src/framework.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,12 @@ import {
defaultPermissionEvaluators,
PermissionEvaluatorFactory,
} from "./permissions";
import {
captureErrorWithSentry,
createSentryWrapper,
setSentryUser,
type SentryWrapper,
} from "./sentry";
import type {
AuthContext,
AuthorizedLambdaHandler,
Expand All @@ -26,9 +32,16 @@ import { createYupValidationErrorResponse } from "./yup";
* A framework to create lambda handlers for REST APIs.
*/
export class LambdaFramework {
private readonly sentryWrapper: SentryWrapper;
private readonly permissionEvaluatorFactory: PermissionEvaluatorFactory;

constructor(private readonly options: LambdaFrameworkOptions) {
this.sentryWrapper = createSentryWrapper(
options.sentry
? { ...options.sentry, environment: options.stage }
: undefined
);

this.permissionEvaluatorFactory = new PermissionEvaluatorFactory({
...defaultPermissionEvaluators,
...options.permissionEvaluators,
Expand Down Expand Up @@ -59,6 +72,7 @@ export class LambdaFramework {
> {
return this.unauthorized((event) => {
const context = this.createAuthContext(event);
setSentryUser({ id: context.principalId });
return handler(event, context);
}, lambdaOptions);
}
Expand All @@ -82,7 +96,7 @@ export class LambdaFramework {
handler: HTTPLambdaHandler,
lambdaOptions?: LambdaOptions
): HTTPLambdaHandler {
return async (event, ...args) => {
return this.sentryWrapper(async (event, ...args) => {
try {
const result = await handler(event, ...args);

Expand All @@ -97,7 +111,7 @@ export class LambdaFramework {
} catch (error) {
return this.createErrorResponse(error, event);
}
};
});
}

/**
Expand Down Expand Up @@ -192,6 +206,7 @@ export class LambdaFramework {

if (statusCode >= 500) {
console.warn("Error in lambda handler:", error);
captureErrorWithSentry(error);
}

// TODO: can we somehow elegantly propagate properties from the error?
Expand Down
12 changes: 12 additions & 0 deletions src/options.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,5 +24,17 @@ export function parseLambdaFrameworkOptionsFromEnv(

return {
stage: get("STAGE", "development"),
includeStackTrace: get("NODE_ENV", "development") === "development",
sentry: parseSentry(get),
};
}

function parseSentry(get: Get): LambdaFrameworkOptions["sentry"] {
const dsn = get("SENTRY_DSN", "");
if (!dsn) return;

const enabled = Boolean(get("SENTRY_ENABLED", "true"));
const tracesSampleRate = parseFloat(get("SENTRY_TRACES_SAMPLE_RATE", "0"));

return { dsn, tracesSampleRate, enabled };
}
31 changes: 31 additions & 0 deletions src/sentry.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
import { AWSLambda, type User } from "@sentry/serverless";
import type { Handler } from "aws-lambda";

export type SentryOptions = {
environment: string;
dsn: string;
enabled: boolean;
tracesSampleRate?: number;
};

export type SentryWrapper = <TEvent, TResult>(
handler: Handler<TEvent, TResult>,
wrapOptions?: Partial<AWSLambda.WrapperOptions>
) => Handler<TEvent, TResult>;

export function createSentryWrapper(options?: SentryOptions): SentryWrapper {
console.log("Sentry DSN:", options?.dsn);
if (!options?.dsn) return (handler) => handler;

AWSLambda.init(options);

return (handler, wrapOptions) => AWSLambda.wrapHandler(handler, wrapOptions);
}

export function setSentryUser(user?: User): void {
AWSLambda.setUser(user ?? null);
}

export function captureErrorWithSentry(err: unknown): void {
AWSLambda.captureException(err);
}
Loading

0 comments on commit b5ea920

Please sign in to comment.