|
1 |
| -import type { Integration, Options, Scope } from '@sentry/core'; |
2 |
| -import { applySdkMetadata, consoleSandbox, debug, getSDKSource } from '@sentry/core'; |
| 1 | +import type { Integration, Options } from '@sentry/core'; |
| 2 | +import { applySdkMetadata, debug, getSDKSource } from '@sentry/core'; |
3 | 3 | import type { NodeClient, NodeOptions } from '@sentry/node';
|
4 |
| -import { |
5 |
| - captureException, |
6 |
| - captureMessage, |
7 |
| - flush, |
8 |
| - getCurrentScope, |
9 |
| - getDefaultIntegrationsWithoutPerformance, |
10 |
| - initWithoutDefaultIntegrations, |
11 |
| - withScope, |
12 |
| -} from '@sentry/node'; |
13 |
| -import type { Context, Handler } from 'aws-lambda'; |
| 4 | +import { getDefaultIntegrationsWithoutPerformance, initWithoutDefaultIntegrations } from '@sentry/node'; |
| 5 | +import type { Handler } from 'aws-lambda'; |
14 | 6 | import { existsSync } from 'fs';
|
15 | 7 | import { basename, resolve } from 'path';
|
16 |
| -import { performance } from 'perf_hooks'; |
17 |
| -import { types } from 'util'; |
18 | 8 | import { DEBUG_BUILD } from './debug-build';
|
19 | 9 | import { awsIntegration } from './integration/aws';
|
20 | 10 | import { awsLambdaIntegration } from './integration/awslambda';
|
21 |
| -import { markEventUnhandled } from './utils'; |
| 11 | +import { wrapHandler } from './wrappers/wrap-handler'; |
22 | 12 |
|
23 |
| -const { isPromise } = types; |
24 |
| - |
25 |
| -// https://www.npmjs.com/package/aws-lambda-consumer |
26 |
| -type SyncHandler<T extends Handler> = ( |
27 |
| - event: Parameters<T>[0], |
28 |
| - context: Parameters<T>[1], |
29 |
| - callback: Parameters<T>[2], |
30 |
| -) => void; |
| 13 | +export { wrapHandler }; |
31 | 14 |
|
32 | 15 | export type AsyncHandler<T extends Handler> = (
|
33 | 16 | event: Parameters<T>[0],
|
@@ -89,25 +72,6 @@ function tryRequire<T>(taskRoot: string, subdir: string, mod: string): T {
|
89 | 72 | return require(require.resolve(mod, { paths: [taskRoot, subdir] }));
|
90 | 73 | }
|
91 | 74 |
|
92 |
| -/** */ |
93 |
| -function isPromiseAllSettledResult<T>(result: T[]): boolean { |
94 |
| - return result.every( |
95 |
| - v => |
96 |
| - Object.prototype.hasOwnProperty.call(v, 'status') && |
97 |
| - (Object.prototype.hasOwnProperty.call(v, 'value') || Object.prototype.hasOwnProperty.call(v, 'reason')), |
98 |
| - ); |
99 |
| -} |
100 |
| - |
101 |
| -type PromiseSettledResult<T> = { status: 'rejected' | 'fulfilled'; reason?: T }; |
102 |
| - |
103 |
| -/** */ |
104 |
| -function getRejectedReasons<T>(results: PromiseSettledResult<T>[]): T[] { |
105 |
| - return results.reduce((rejected: T[], result) => { |
106 |
| - if (result.status === 'rejected' && result.reason) rejected.push(result.reason); |
107 |
| - return rejected; |
108 |
| - }, []); |
109 |
| -} |
110 |
| - |
111 | 75 | /** */
|
112 | 76 | export function tryPatchHandler(taskRoot: string, handlerPath: string): void {
|
113 | 77 | type HandlerBag = HandlerModule | Handler | null | undefined;
|
@@ -159,161 +123,3 @@ export function tryPatchHandler(taskRoot: string, handlerPath: string): void {
|
159 | 123 | // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
|
160 | 124 | (mod as HandlerModule)[functionName!] = wrapHandler(obj);
|
161 | 125 | }
|
162 |
| - |
163 |
| -/** |
164 |
| - * Tries to invoke context.getRemainingTimeInMillis if not available returns 0 |
165 |
| - * Some environments use AWS lambda but don't support this function |
166 |
| - * @param context |
167 |
| - */ |
168 |
| -function tryGetRemainingTimeInMillis(context: Context): number { |
169 |
| - return typeof context.getRemainingTimeInMillis === 'function' ? context.getRemainingTimeInMillis() : 0; |
170 |
| -} |
171 |
| - |
172 |
| -/** |
173 |
| - * Adds additional information from the environment and AWS Context to the Sentry Scope. |
174 |
| - * |
175 |
| - * @param scope Scope that should be enhanced |
176 |
| - * @param context AWS Lambda context that will be used to extract some part of the data |
177 |
| - * @param startTime performance.now() when wrapHandler was invoked |
178 |
| - */ |
179 |
| -function enhanceScopeWithEnvironmentData(scope: Scope, context: Context, startTime: number): void { |
180 |
| - scope.setContext('aws.lambda', { |
181 |
| - aws_request_id: context.awsRequestId, |
182 |
| - function_name: context.functionName, |
183 |
| - function_version: context.functionVersion, |
184 |
| - invoked_function_arn: context.invokedFunctionArn, |
185 |
| - execution_duration_in_millis: performance.now() - startTime, |
186 |
| - remaining_time_in_millis: tryGetRemainingTimeInMillis(context), |
187 |
| - 'sys.argv': process.argv, |
188 |
| - }); |
189 |
| - |
190 |
| - scope.setContext('aws.cloudwatch.logs', { |
191 |
| - log_group: context.logGroupName, |
192 |
| - log_stream: context.logStreamName, |
193 |
| - url: `https://console.aws.amazon.com/cloudwatch/home?region=${ |
194 |
| - process.env.AWS_REGION |
195 |
| - }#logsV2:log-groups/log-group/${encodeURIComponent(context.logGroupName)}/log-events/${encodeURIComponent( |
196 |
| - context.logStreamName, |
197 |
| - )}?filterPattern="${context.awsRequestId}"`, |
198 |
| - }); |
199 |
| -} |
200 |
| - |
201 |
| -/** |
202 |
| - * Wraps a lambda handler adding it error capture and tracing capabilities. |
203 |
| - * |
204 |
| - * @param handler Handler |
205 |
| - * @param options Options |
206 |
| - * @returns Handler |
207 |
| - */ |
208 |
| -export function wrapHandler<TEvent, TResult>( |
209 |
| - handler: Handler<TEvent, TResult>, |
210 |
| - wrapOptions: Partial<WrapperOptions> = {}, |
211 |
| -): Handler<TEvent, TResult> { |
212 |
| - const START_TIME = performance.now(); |
213 |
| - |
214 |
| - // eslint-disable-next-line deprecation/deprecation |
215 |
| - if (typeof wrapOptions.startTrace !== 'undefined') { |
216 |
| - consoleSandbox(() => { |
217 |
| - // eslint-disable-next-line no-console |
218 |
| - console.warn( |
219 |
| - 'The `startTrace` option is deprecated and will be removed in a future major version. If you want to disable tracing, set `SENTRY_TRACES_SAMPLE_RATE` to `0.0`.', |
220 |
| - ); |
221 |
| - }); |
222 |
| - } |
223 |
| - |
224 |
| - const options: WrapperOptions = { |
225 |
| - flushTimeout: 2000, |
226 |
| - callbackWaitsForEmptyEventLoop: false, |
227 |
| - captureTimeoutWarning: true, |
228 |
| - timeoutWarningLimit: 500, |
229 |
| - captureAllSettledReasons: false, |
230 |
| - startTrace: true, // TODO(v11): Remove this option. Set to true here to satisfy the type, but has no effect. |
231 |
| - ...wrapOptions, |
232 |
| - }; |
233 |
| - |
234 |
| - let timeoutWarningTimer: NodeJS.Timeout; |
235 |
| - |
236 |
| - // AWSLambda is like Express. It makes a distinction about handlers based on its last argument |
237 |
| - // async (event) => async handler |
238 |
| - // async (event, context) => async handler |
239 |
| - // (event, context, callback) => sync handler |
240 |
| - // Nevertheless whatever option is chosen by user, we convert it to async handler. |
241 |
| - const asyncHandler: AsyncHandler<typeof handler> = |
242 |
| - handler.length > 2 |
243 |
| - ? (event, context) => |
244 |
| - new Promise((resolve, reject) => { |
245 |
| - const rv = (handler as SyncHandler<typeof handler>)(event, context, (error, result) => { |
246 |
| - if (error === null || error === undefined) { |
247 |
| - resolve(result!); // eslint-disable-line @typescript-eslint/no-non-null-assertion |
248 |
| - } else { |
249 |
| - reject(error); |
250 |
| - } |
251 |
| - }) as unknown; |
252 |
| - |
253 |
| - // This should never happen, but still can if someone writes a handler as |
254 |
| - // `async (event, context, callback) => {}` |
255 |
| - if (isPromise(rv)) { |
256 |
| - void (rv as Promise<NonNullable<TResult>>).then(resolve, reject); |
257 |
| - } |
258 |
| - }) |
259 |
| - : (handler as AsyncHandler<typeof handler>); |
260 |
| - |
261 |
| - return async (event, context) => { |
262 |
| - context.callbackWaitsForEmptyEventLoop = options.callbackWaitsForEmptyEventLoop; |
263 |
| - |
264 |
| - // In seconds. You cannot go any more granular than this in AWS Lambda. |
265 |
| - const configuredTimeout = Math.ceil(tryGetRemainingTimeInMillis(context) / 1000); |
266 |
| - const configuredTimeoutMinutes = Math.floor(configuredTimeout / 60); |
267 |
| - const configuredTimeoutSeconds = configuredTimeout % 60; |
268 |
| - |
269 |
| - const humanReadableTimeout = |
270 |
| - configuredTimeoutMinutes > 0 |
271 |
| - ? `${configuredTimeoutMinutes}m${configuredTimeoutSeconds}s` |
272 |
| - : `${configuredTimeoutSeconds}s`; |
273 |
| - |
274 |
| - // When `callbackWaitsForEmptyEventLoop` is set to false, which it should when using `captureTimeoutWarning`, |
275 |
| - // we don't have a guarantee that this message will be delivered. Because of that, we don't flush it. |
276 |
| - if (options.captureTimeoutWarning) { |
277 |
| - const timeoutWarningDelay = tryGetRemainingTimeInMillis(context) - options.timeoutWarningLimit; |
278 |
| - |
279 |
| - timeoutWarningTimer = setTimeout(() => { |
280 |
| - withScope(scope => { |
281 |
| - scope.setTag('timeout', humanReadableTimeout); |
282 |
| - captureMessage(`Possible function timeout: ${context.functionName}`, 'warning'); |
283 |
| - }); |
284 |
| - }, timeoutWarningDelay) as unknown as NodeJS.Timeout; |
285 |
| - } |
286 |
| - |
287 |
| - async function processResult(): Promise<TResult> { |
288 |
| - const scope = getCurrentScope(); |
289 |
| - |
290 |
| - let rv: TResult; |
291 |
| - try { |
292 |
| - enhanceScopeWithEnvironmentData(scope, context, START_TIME); |
293 |
| - |
294 |
| - rv = await asyncHandler(event, context); |
295 |
| - |
296 |
| - // We manage lambdas that use Promise.allSettled by capturing the errors of failed promises |
297 |
| - if (options.captureAllSettledReasons && Array.isArray(rv) && isPromiseAllSettledResult(rv)) { |
298 |
| - const reasons = getRejectedReasons(rv); |
299 |
| - reasons.forEach(exception => { |
300 |
| - captureException(exception, scope => markEventUnhandled(scope, 'auto.function.aws-serverless.promise')); |
301 |
| - }); |
302 |
| - } |
303 |
| - } catch (e) { |
304 |
| - captureException(e, scope => markEventUnhandled(scope, 'auto.function.aws-serverless.handler')); |
305 |
| - throw e; |
306 |
| - } finally { |
307 |
| - clearTimeout(timeoutWarningTimer); |
308 |
| - await flush(options.flushTimeout).catch(e => { |
309 |
| - DEBUG_BUILD && debug.error(e); |
310 |
| - }); |
311 |
| - } |
312 |
| - return rv; |
313 |
| - } |
314 |
| - |
315 |
| - return withScope(async () => { |
316 |
| - return processResult(); |
317 |
| - }); |
318 |
| - }; |
319 |
| -} |
0 commit comments