Skip to content

Commit af5dc29

Browse files
author
j4y-m
committed
feat(serverless/awslambda/captureAllSettledReasons) : Build specific context for each captured promises
- Following getsentry#4130 # Description It can be very interesting to build a specific context depending on the error. And especially in my case, the index. This allows to find the corresponding SQSRecord, and to design tags from the body. This increases greatly the debugging capacity, and filtering at the sentry interface level.
1 parent 8342523 commit af5dc29

File tree

1 file changed

+25
-19
lines changed

1 file changed

+25
-19
lines changed

packages/serverless/src/awslambda.ts

Lines changed: 25 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
import * as Sentry from '@sentry/node';
33
import { captureException, captureMessage, flush, getCurrentHub, Scope, withScope } from '@sentry/node';
44
import { extractTraceparentData } from '@sentry/tracing';
5-
import { Integration } from '@sentry/types';
5+
import { CaptureContext,Integration } from '@sentry/types';
66
import { baggageHeaderToDynamicSamplingContext, dsnFromString, dsnToString, isString, logger } from '@sentry/utils';
77
// NOTE: I have no idea how to fix this right now, and don't want to waste more time, as it builds just fine — Kamil
88
// eslint-disable-next-line import/no-unresolved
@@ -44,7 +44,15 @@ export interface WrapperOptions {
4444
* The {@link wrapHandler} will not fail the lambda even if there are errors
4545
* @default false
4646
*/
47-
captureAllSettledReasons: boolean;
47+
captureAllSettledReasons: boolean | {
48+
/**
49+
* Build {CaptureContext} according to the error reported.
50+
* @param event The original event.
51+
* @param reason The `reason` property of the promise rejection
52+
* @param allSettledResultIndex The {PromiseSettledResult} `index`
53+
*/
54+
buildContext?: (event: Parameters<Handler>[0], reason: PromiseSettledResult<unknown>['reason'], allSettledResultIndex: number) => CaptureContext;
55+
};
4856
}
4957

5058
export const defaultIntegrations: Integration[] = [...Sentry.defaultIntegrations, new AWSServices({ optional: true })];
@@ -119,24 +127,18 @@ function tryRequire<T>(taskRoot: string, subdir: string, mod: string): T {
119127
// Node-style path
120128
return require(require.resolve(mod, { paths: [taskRoot, subdir] }));
121129
}
130+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
131+
type PromiseSettledResult<T, S extends 'rejected' | 'fulfilled' = 'rejected' | 'fulfilled'> = { status: S; reason?: any, value: T };
122132

123133
/** */
124-
function isPromiseAllSettledResult<T>(result: T[]): boolean {
125-
return result.every(
126-
v =>
127-
Object.prototype.hasOwnProperty.call(v, 'status') &&
128-
(Object.prototype.hasOwnProperty.call(v, 'value') || Object.prototype.hasOwnProperty.call(v, 'reason')),
129-
);
134+
function isPromiseAllSettledResult<T>(result: unknown): result is PromiseSettledResult<T>[] {
135+
return Array.isArray(result) && result.every(v => isPromiseSettledResult(v));
130136
}
131137

132-
type PromiseSettledResult<T> = { status: 'rejected' | 'fulfilled'; reason?: T };
133-
134138
/** */
135-
function getRejectedReasons<T>(results: PromiseSettledResult<T>[]): T[] {
136-
return results.reduce((rejected: T[], result) => {
137-
if (result.status === 'rejected' && result.reason) rejected.push(result.reason);
138-
return rejected;
139-
}, []);
139+
function isPromiseSettledResult<T>(result: unknown): result is PromiseSettledResult<T> {
140+
return Object.prototype.hasOwnProperty.call(result, 'status') &&
141+
(Object.prototype.hasOwnProperty.call(result, 'value') || Object.prototype.hasOwnProperty.call(result, 'reason'));
140142
}
141143

142144
/** */
@@ -332,10 +334,14 @@ export function wrapHandler<TEvent, TResult>(
332334
rv = await asyncHandler(event, context);
333335

334336
// We manage lambdas that use Promise.allSettled by capturing the errors of failed promises
335-
if (options.captureAllSettledReasons && Array.isArray(rv) && isPromiseAllSettledResult(rv)) {
336-
const reasons = getRejectedReasons(rv);
337-
reasons.forEach(exception => {
338-
captureException(exception);
337+
if (options.captureAllSettledReasons && isPromiseAllSettledResult(rv)) {
338+
rv.forEach((result, allSettledIndex) => {
339+
if(result.status === 'rejected') {
340+
const context = typeof options.captureAllSettledReasons !== 'boolean'
341+
? options.captureAllSettledReasons.buildContext?.(event, result.reason, allSettledIndex)
342+
: undefined;
343+
captureException(result.reason, context);
344+
}
339345
});
340346
}
341347
} catch (e) {

0 commit comments

Comments
 (0)