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

1040 add defaultHandler to lambdas #1357

Closed
Closed
Show file tree
Hide file tree
Changes from all commits
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
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@ import path from "path";
import * as stream from "stream";
import { DOMParser } from "xmldom";
import { MetriportError } from "../../../util/error/metriport-error";
import NotFoundError from "../../../util/error/not-found";
import { detectFileType, isContentTypeAccepted } from "../../../util/file-type";
import { isMimeTypeXML } from "../../../util/mime";
import { makeS3Client, S3Utils } from "../../aws/s3";
import {
Expand All @@ -13,8 +15,6 @@ import {
DownloadResult,
FileInfo,
} from "./document-downloader";
import NotFoundError from "../../../util/error/not-found";
import { detectFileType, isContentTypeAccepted } from "../../../util/file-type";

export type DocumentDownloaderLocalConfig = DocumentDownloaderConfig & {
commonWell: {
Expand Down Expand Up @@ -292,12 +292,9 @@ export class DocumentDownloaderLocal extends DocumentDownloader {
if (error instanceof CommonwellError && error.cause?.response?.status === 404) {
const msg = "CW - Document not found";
console.log(`${msg} - ${JSON.stringify(additionalInfo)}`);
throw new NotFoundError(msg, undefined, additionalInfo);
throw new NotFoundError(msg, error, additionalInfo);
}
const msg = `CW - Error downloading document`;
this.config.capture &&
this.config.capture.message(msg, { extra: { ...additionalInfo, error }, level: "error" });
throw new MetriportError(msg, error, additionalInfo);
throw new MetriportError(`CW - Error downloading document`, error, additionalInfo);
}
}
}
2 changes: 2 additions & 0 deletions packages/core/src/util/capture.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
import { ScopeContext } from "@sentry/types";

export type AdditionalInfo = Record<string, string | number | boolean | undefined | null>;

export type Capture = {
/**
* Captures an exception event and sends it to Sentry.
Expand Down
3 changes: 2 additions & 1 deletion packages/core/src/util/error/bad-request.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import httpStatus from "http-status";
import { AdditionalInfo } from "../capture";
import { MetriportError } from "./metriport-error";

const numericStatus = httpStatus.BAD_REQUEST;
Expand All @@ -7,7 +8,7 @@ export default class BadRequestError extends MetriportError {
constructor(
message = "Unexpected issue with the request - check inputs and try again",
cause?: unknown,
additionalInfo?: Record<string, string | number | undefined | null>
additionalInfo?: AdditionalInfo
) {
super(message, cause, additionalInfo);
this.status = numericStatus;
Expand Down
3 changes: 1 addition & 2 deletions packages/core/src/util/error/metriport-error.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
import status from "http-status";

export type AdditionalInfo = Record<string, string | number | undefined | null>;
import { AdditionalInfo } from "../capture";

export class MetriportError extends Error {
status: number = status.INTERNAL_SERVER_ERROR;
Expand Down
3 changes: 2 additions & 1 deletion packages/core/src/util/error/not-found.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import httpStatus from "http-status";
import { AdditionalInfo } from "../capture";
import { MetriportError } from "./metriport-error";

const numericStatus = httpStatus.NOT_FOUND;
Expand All @@ -7,7 +8,7 @@ export default class NotFoundError extends MetriportError {
constructor(
message = "Could not find the requested resource",
cause?: unknown,
additionalInfo?: Record<string, string | undefined | null>
additionalInfo?: AdditionalInfo
) {
super(message, cause, additionalInfo);
this.status = numericStatus;
Expand Down
3 changes: 2 additions & 1 deletion packages/lambdas/src/cw-session-management.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,8 @@ import {
SessionManagementConfig,
} from "@metriport/core/external/commonwell/management/session";
import { base64ToBuffer } from "@metriport/core/util/base64";
import { AdditionalInfo, MetriportError } from "@metriport/core/util/error/metriport-error";
import { AdditionalInfo } from "@metriport/core/util/capture";
import { MetriportError } from "@metriport/core/util/error/metriport-error";
import * as Sentry from "@sentry/serverless";

import * as playwright from "playwright-aws-lambda";
Expand Down
4 changes: 2 additions & 2 deletions packages/lambdas/src/document-downloader.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,9 +10,9 @@ import { DownloadResult } from "@metriport/core/external/commonwell/document/doc
import { DocumentDownloaderLambdaRequest } from "@metriport/core/external/commonwell/document/document-downloader-lambda";
import { DocumentDownloaderLocal } from "@metriport/core/external/commonwell/document/document-downloader-local";
import { getEnvType } from "@metriport/core/util/env-var";
import * as Sentry from "@sentry/serverless";
import { capture } from "./shared/capture";
import { getEnv, getEnvOrFail, isProduction } from "./shared/env";
import { defaultHandler } from "./shared/handler";

// Keep this as early on the file as possible
capture.init();
Expand All @@ -27,7 +27,7 @@ const cwOrgPrivateKeySecret = getEnvOrFail("CW_ORG_PRIVATE_KEY");

const apiMode = isProduction() ? APIMode.production : APIMode.integration;

export const handler = Sentry.AWSLambda.wrapHandler(
export const handler = defaultHandler(
async (req: DocumentDownloaderLambdaRequest): Promise<DownloadResult> => {
const { orgName, orgOid, npi, cxId, fileInfo, document } = req;
capture.setUser({ id: cxId });
Expand Down
9 changes: 2 additions & 7 deletions packages/lambdas/src/shared/capture.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { Capture } from "@metriport/core/util/capture";
import { AdditionalInfo } from "@metriport/core/util/capture";
import { getEnvType, getEnvVar } from "@metriport/core/util/env-var";
import * as Sentry from "@sentry/serverless";
import { Extras } from "@sentry/types";
Expand All @@ -8,11 +8,6 @@ const sentryDsn = getEnvVar("SENTRY_DSN");

export type UserData = Pick<Sentry.AWSLambda.User, "id" | "email">;

export type LambdaCapture = Capture & {
setUser: (user: UserData) => void;
setExtra: (extra: Record<string, unknown>) => void;
};

export const capture = {
// TODO #499 Review 'tracesSampleRate' based on the load on our app and Sentry's quotas
/**
Expand All @@ -35,7 +30,7 @@ export const capture = {
Sentry.setUser(user);
},

setExtra: (extra: Record<string, unknown>): void => {
setExtra: (extra: AdditionalInfo): void => {
Object.entries(extra).forEach(([key, value]) => {
Sentry.setExtra(key, value);
});
Expand Down
52 changes: 52 additions & 0 deletions packages/lambdas/src/shared/handler.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
/**
* Borrowed from @sentry/serverless:
* https://github.com/getsentry/sentry-javascript/blob/develop/packages/serverless/src/awslambda.ts
*/
import { MetriportError } from "@metriport/core/util/error/metriport-error";
import { AsyncHandler } from "@sentry/serverless/types/awslambda";
import { Handler } from "aws-lambda";
import { types } from "util";
import { capture } from "./capture";

type SyncHandler<T extends Handler> = (
event: Parameters<T>[0],
context: Parameters<T>[1],
callback: Parameters<T>[2]
) => void;

const { isPromise } = types;

export function defaultHandler<TEvent, TResult>(
handler: Handler<TEvent, TResult>
): Handler<TEvent, TResult> {
const asyncHandler: AsyncHandler<typeof handler> =
handler.length > 2
? (event, context) =>
new Promise((resolve, reject) => {
const rv = (handler as SyncHandler<typeof handler>)(event, context, (error, result) => {
if (error === null || error === undefined) {
resolve(result!); // eslint-disable-line @typescript-eslint/no-non-null-assertion
} else {
reject(error);
}
}) as unknown;

// This should never happen, but still can if someone writes a handler as
// `async (event, context, callback) => {}`
if (isPromise(rv)) {
void (rv as Promise<NonNullable<TResult>>).then(resolve, reject);
}
})
: (handler as AsyncHandler<typeof handler>);

return async (event, context) => {
try {
return await asyncHandler(event, context);
} catch (error) {
if (error instanceof MetriportError && error.additionalInfo) {
capture.setExtra(error.additionalInfo);
}
throw error;
}
};
}
Loading