diff --git a/packages/serverless/src/awslambda.ts b/packages/serverless/src/awslambda.ts index 9094709082dc..d9cbe177efc8 100644 --- a/packages/serverless/src/awslambda.ts +++ b/packages/serverless/src/awslambda.ts @@ -3,11 +3,21 @@ import { hostname } from 'os'; import { basename, resolve } from 'path'; import { types } from 'util'; /* eslint-disable max-lines */ -import type { Scope } from '@sentry/node'; -import * as Sentry from '@sentry/node'; -import { captureException, captureMessage, flush, getCurrentHub, withScope } from '@sentry/node'; -import type { Integration, SdkMetadata } from '@sentry/types'; -import { isString, logger, tracingContextFromHeaders } from '@sentry/utils'; +import type { NodeOptions, Scope } from '@sentry/node'; +import { SDK_VERSION } from '@sentry/node'; +import { + captureException, + captureMessage, + continueTrace, + defaultIntegrations as nodeDefaultIntegrations, + flush, + getCurrentScope, + init as initNode, + startSpanManual, + withScope, +} from '@sentry/node'; +import type { Integration, SdkMetadata, Span } from '@sentry/types'; +import { isString, logger } from '@sentry/utils'; // 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 import type { Context, Handler } from 'aws-lambda'; import { performance } from 'perf_hooks'; @@ -55,9 +65,9 @@ export interface WrapperOptions { startTrace: boolean; } -export const defaultIntegrations: Integration[] = [...Sentry.defaultIntegrations, new AWSServices({ optional: true })]; +export const defaultIntegrations: Integration[] = [...nodeDefaultIntegrations, new AWSServices({ optional: true })]; -interface AWSLambdaOptions extends Sentry.NodeOptions { +interface AWSLambdaOptions extends NodeOptions { /** * Internal field that is set to `true` when init() is called by the Sentry AWS Lambda layer. * @@ -66,7 +76,9 @@ interface AWSLambdaOptions extends Sentry.NodeOptions { } /** - * @see {@link Sentry.init} + * Initializes the Sentry AWS Lambda SDK. + * + * @param options Configuration options for the SDK, @see {@link AWSLambdaOptions}. */ export function init(options: AWSLambdaOptions = {}): void { const opts = { @@ -81,13 +93,13 @@ export function init(options: AWSLambdaOptions = {}): void { packages: [ { name: 'npm:@sentry/serverless', - version: Sentry.SDK_VERSION, + version: SDK_VERSION, }, ], - version: Sentry.SDK_VERSION, + version: SDK_VERSION, }; - Sentry.init(opts); + initNode(opts); } /** */ @@ -290,44 +302,13 @@ export function wrapHandler( }, timeoutWarningDelay) as unknown as NodeJS.Timeout; } - const hub = getCurrentHub(); - - let transaction: Sentry.Transaction | undefined; - if (options.startTrace) { - const eventWithHeaders = event as { headers?: { [key: string]: string } }; - - const sentryTrace = - eventWithHeaders.headers && isString(eventWithHeaders.headers['sentry-trace']) - ? eventWithHeaders.headers['sentry-trace'] - : undefined; - const baggage = eventWithHeaders.headers?.baggage; - const { traceparentData, dynamicSamplingContext, propagationContext } = tracingContextFromHeaders( - sentryTrace, - baggage, - ); - Sentry.getCurrentScope().setPropagationContext(propagationContext); - - transaction = hub.startTransaction({ - name: context.functionName, - op: 'function.aws.lambda', - origin: 'auto.function.serverless', - ...traceparentData, - metadata: { - dynamicSamplingContext: traceparentData && !dynamicSamplingContext ? {} : dynamicSamplingContext, - source: 'component', - }, - }); - } + async function processResult(span?: Span): Promise { + const scope = getCurrentScope(); - return withScope(async scope => { let rv: TResult; try { enhanceScopeWithEnvironmentData(scope, context, START_TIME); - if (options.startTrace) { - enhanceScopeWithTransactionData(scope, context); - // We put the transaction on the scope so users can attach children to it - scope.setSpan(transaction); - } + rv = await asyncHandler(event, context); // We manage lambdas that use Promise.allSettled by capturing the errors of failed promises @@ -342,12 +323,46 @@ export function wrapHandler( throw e; } finally { clearTimeout(timeoutWarningTimer); - transaction?.end(); + span?.end(); await flush(options.flushTimeout).catch(e => { DEBUG_BUILD && logger.error(e); }); } return rv; + } + + if (options.startTrace) { + const eventWithHeaders = event as { headers?: { [key: string]: string } }; + + const sentryTrace = + eventWithHeaders.headers && isString(eventWithHeaders.headers['sentry-trace']) + ? eventWithHeaders.headers['sentry-trace'] + : undefined; + const baggage = eventWithHeaders.headers?.baggage; + + const continueTraceContext = continueTrace({ sentryTrace, baggage }); + + return startSpanManual( + { + name: context.functionName, + op: 'function.aws.lambda', + origin: 'auto.function.serverless', + ...continueTraceContext, + metadata: { + ...continueTraceContext.metadata, + source: 'component', + }, + }, + span => { + enhanceScopeWithTransactionData(getCurrentScope(), context); + + return processResult(span); + }, + ); + } + + return withScope(async () => { + return processResult(undefined); }); }; } diff --git a/packages/serverless/src/gcpfunction/cloud_events.ts b/packages/serverless/src/gcpfunction/cloud_events.ts index 547cf93b3a5f..233d6c9a79f8 100644 --- a/packages/serverless/src/gcpfunction/cloud_events.ts +++ b/packages/serverless/src/gcpfunction/cloud_events.ts @@ -1,4 +1,4 @@ -import { captureException, flush, getCurrentHub, getCurrentScope } from '@sentry/node'; +import { captureException, flush, getCurrentScope, startSpanManual } from '@sentry/node'; import { isThenable, logger } from '@sentry/utils'; import { DEBUG_BUILD } from '../debug-build'; @@ -21,7 +21,6 @@ export function wrapCloudEventFunction( return proxyFunction(fn, f => domainify(_wrapCloudEventFunction(f, wrapOptions))); } -/** */ function _wrapCloudEventFunction( fn: CloudEventFunction | CloudEventFunctionWithCallback, wrapOptions: Partial = {}, @@ -31,63 +30,59 @@ function _wrapCloudEventFunction( ...wrapOptions, }; return (context, callback) => { - const hub = getCurrentHub(); + return startSpanManual( + { + name: context.type || '', + op: 'function.gcp.cloud_event', + origin: 'auto.function.serverless.gcp_cloud_event', + metadata: { source: 'component' }, + }, + span => { + const scope = getCurrentScope(); + scope.setContext('gcp.function.context', { ...context }); - const transaction = hub.startTransaction({ - name: context.type || '', - op: 'function.gcp.cloud_event', - origin: 'auto.function.serverless.gcp_cloud_event', - metadata: { source: 'component' }, - }) as ReturnType | undefined; + const newCallback = domainify((...args: unknown[]) => { + if (args[0] !== null && args[0] !== undefined) { + captureException(args[0], scope => markEventUnhandled(scope)); + } + span?.end(); - // getCurrentHub() is expected to use current active domain as a carrier - // since functions-framework creates a domain for each incoming request. - // So adding of event processors every time should not lead to memory bloat. - const scope = getCurrentScope(); - scope.setContext('gcp.function.context', { ...context }); - // We put the transaction on the scope so users can attach children to it - scope.setSpan(transaction); - - const newCallback = domainify((...args: unknown[]) => { - if (args[0] !== null && args[0] !== undefined) { - captureException(args[0], scope => markEventUnhandled(scope)); - } - transaction?.end(); - - // eslint-disable-next-line @typescript-eslint/no-floating-promises - flush(options.flushTimeout) - .then(null, e => { - DEBUG_BUILD && logger.error(e); - }) - .then(() => { - callback(...args); + // eslint-disable-next-line @typescript-eslint/no-floating-promises + flush(options.flushTimeout) + .then(null, e => { + DEBUG_BUILD && logger.error(e); + }) + .then(() => { + callback(...args); + }); }); - }); - if (fn.length > 1) { - let fnResult; - try { - fnResult = (fn as CloudEventFunctionWithCallback)(context, newCallback); - } catch (err) { - captureException(err, scope => markEventUnhandled(scope)); - throw err; - } + if (fn.length > 1) { + let fnResult; + try { + fnResult = (fn as CloudEventFunctionWithCallback)(context, newCallback); + } catch (err) { + captureException(err, scope => markEventUnhandled(scope)); + throw err; + } - if (isThenable(fnResult)) { - fnResult.then(null, err => { - captureException(err, scope => markEventUnhandled(scope)); - throw err; - }); - } + if (isThenable(fnResult)) { + fnResult.then(null, err => { + captureException(err, scope => markEventUnhandled(scope)); + throw err; + }); + } - return fnResult; - } + return fnResult; + } - return Promise.resolve() - .then(() => (fn as CloudEventFunction)(context)) - .then( - result => newCallback(null, result), - err => newCallback(err, undefined), - ); + return Promise.resolve() + .then(() => (fn as CloudEventFunction)(context)) + .then( + result => newCallback(null, result), + err => newCallback(err, undefined), + ); + }, + ); }; } diff --git a/packages/serverless/src/gcpfunction/events.ts b/packages/serverless/src/gcpfunction/events.ts index e3f6b30a4525..a3d1af662d3c 100644 --- a/packages/serverless/src/gcpfunction/events.ts +++ b/packages/serverless/src/gcpfunction/events.ts @@ -1,4 +1,4 @@ -import { captureException, flush, getCurrentHub, getCurrentScope } from '@sentry/node'; +import { captureException, flush, getCurrentScope, startSpanManual } from '@sentry/node'; import { isThenable, logger } from '@sentry/utils'; import { DEBUG_BUILD } from '../debug-build'; @@ -33,65 +33,61 @@ function _wrapEventFunction return (...eventFunctionArguments: Parameters): ReturnType | Promise => { const [data, context, callback] = eventFunctionArguments; - const hub = getCurrentHub(); + return startSpanManual( + { + name: context.eventType, + op: 'function.gcp.event', + origin: 'auto.function.serverless.gcp_event', + metadata: { source: 'component' }, + }, + span => { + const scope = getCurrentScope(); + scope.setContext('gcp.function.context', { ...context }); - const transaction = hub.startTransaction({ - name: context.eventType, - op: 'function.gcp.event', - origin: 'auto.function.serverless.gcp_event', - metadata: { source: 'component' }, - }) as ReturnType | undefined; - - // getCurrentHub() is expected to use current active domain as a carrier - // since functions-framework creates a domain for each incoming request. - // So adding of event processors every time should not lead to memory bloat. - const scope = getCurrentScope(); - scope.setContext('gcp.function.context', { ...context }); - // We put the transaction on the scope so users can attach children to it - scope.setSpan(transaction); - - const newCallback = domainify((...args: unknown[]) => { - if (args[0] !== null && args[0] !== undefined) { - captureException(args[0], scope => markEventUnhandled(scope)); - } - transaction?.end(); - - // eslint-disable-next-line @typescript-eslint/no-floating-promises - flush(options.flushTimeout) - .then(null, e => { - DEBUG_BUILD && logger.error(e); - }) - .then(() => { - if (typeof callback === 'function') { - callback(...args); + const newCallback = domainify((...args: unknown[]) => { + if (args[0] !== null && args[0] !== undefined) { + captureException(args[0], scope => markEventUnhandled(scope)); } + span?.end(); + + // eslint-disable-next-line @typescript-eslint/no-floating-promises + flush(options.flushTimeout) + .then(null, e => { + DEBUG_BUILD && logger.error(e); + }) + .then(() => { + if (typeof callback === 'function') { + callback(...args); + } + }); }); - }); - if (fn.length > 2) { - let fnResult; - try { - fnResult = (fn as EventFunctionWithCallback)(data, context, newCallback); - } catch (err) { - captureException(err, scope => markEventUnhandled(scope)); - throw err; - } + if (fn.length > 2) { + let fnResult; + try { + fnResult = (fn as EventFunctionWithCallback)(data, context, newCallback); + } catch (err) { + captureException(err, scope => markEventUnhandled(scope)); + throw err; + } - if (isThenable(fnResult)) { - fnResult.then(null, err => { - captureException(err, scope => markEventUnhandled(scope)); - throw err; - }); - } + if (isThenable(fnResult)) { + fnResult.then(null, err => { + captureException(err, scope => markEventUnhandled(scope)); + throw err; + }); + } - return fnResult; - } + return fnResult; + } - return Promise.resolve() - .then(() => (fn as EventFunction)(data, context)) - .then( - result => newCallback(null, result), - err => newCallback(err, undefined), - ); + return Promise.resolve() + .then(() => (fn as EventFunction)(data, context)) + .then( + result => newCallback(null, result), + err => newCallback(err, undefined), + ); + }, + ); }; } diff --git a/packages/serverless/src/gcpfunction/http.ts b/packages/serverless/src/gcpfunction/http.ts index 50bbcd76e782..26d156010373 100644 --- a/packages/serverless/src/gcpfunction/http.ts +++ b/packages/serverless/src/gcpfunction/http.ts @@ -1,7 +1,9 @@ +import { Transaction } from '@sentry/core'; import type { AddRequestDataToEventOptions } from '@sentry/node'; +import { continueTrace, startSpanManual } from '@sentry/node'; import { getCurrentScope } from '@sentry/node'; -import { captureException, flush, getCurrentHub } from '@sentry/node'; -import { isString, isThenable, logger, stripUrlQueryAndFragment, tracingContextFromHeaders } from '@sentry/utils'; +import { captureException, flush } from '@sentry/node'; +import { isString, isThenable, logger, stripUrlQueryAndFragment } from '@sentry/utils'; import { DEBUG_BUILD } from '../debug-build'; import { domainify, markEventUnhandled, proxyFunction } from './../utils'; @@ -63,78 +65,74 @@ function _wrapHttpFunction(fn: HttpFunction, wrapOptions: Partial { - const hub = getCurrentHub(); - const scope = getCurrentScope(); - const reqMethod = (req.method || '').toUpperCase(); const reqUrl = stripUrlQueryAndFragment(req.originalUrl || req.url || ''); const sentryTrace = req.headers && isString(req.headers['sentry-trace']) ? req.headers['sentry-trace'] : undefined; const baggage = req.headers?.baggage; - const { traceparentData, dynamicSamplingContext, propagationContext } = tracingContextFromHeaders( - sentryTrace, - baggage, - ); - scope.setPropagationContext(propagationContext); - - const transaction = hub.startTransaction({ - name: `${reqMethod} ${reqUrl}`, - op: 'function.gcp.http', - origin: 'auto.function.serverless.gcp_http', - ...traceparentData, - metadata: { - dynamicSamplingContext: traceparentData && !dynamicSamplingContext ? {} : dynamicSamplingContext, - source: 'route', + + const continueTraceContext = continueTrace({ sentryTrace, baggage }); + + return startSpanManual( + { + ...continueTraceContext, + name: `${reqMethod} ${reqUrl}`, + op: 'function.gcp.http', + origin: 'auto.function.serverless.gcp_http', + + metadata: { + ...continueTraceContext.metadata, + source: 'route', + }, }, - }) as ReturnType | undefined; - - // getCurrentHub() is expected to use current active domain as a carrier - // since functions-framework creates a domain for each incoming request. - // So adding of event processors every time should not lead to memory bloat. - scope.setSDKProcessingMetadata({ - request: req, - requestDataOptionsFromGCPWrapper: options.addRequestDataToEventOptions, - }); - // We put the transaction on the scope so users can attach children to it - scope.setSpan(transaction); - - // We also set __sentry_transaction on the response so people can grab the transaction there to add - // spans to it later. - // eslint-disable-next-line @typescript-eslint/no-explicit-any, @typescript-eslint/no-unsafe-member-access - (res as any).__sentry_transaction = transaction; - - // eslint-disable-next-line @typescript-eslint/unbound-method - const _end = res.end; - // eslint-disable-next-line @typescript-eslint/no-explicit-any - res.end = function (chunk?: any | (() => void), encoding?: string | (() => void), cb?: () => void): any { - transaction?.setHttpStatus(res.statusCode); - transaction?.end(); - - // eslint-disable-next-line @typescript-eslint/no-floating-promises - flush(options.flushTimeout) - .then(null, e => { - DEBUG_BUILD && logger.error(e); - }) - .then(() => { - _end.call(this, chunk, encoding, cb); + span => { + getCurrentScope().setSDKProcessingMetadata({ + request: req, + requestDataOptionsFromGCPWrapper: options.addRequestDataToEventOptions, }); - }; - - let fnResult; - try { - fnResult = fn(req, res); - } catch (err) { - captureException(err, scope => markEventUnhandled(scope)); - throw err; - } - - if (isThenable(fnResult)) { - fnResult.then(null, err => { - captureException(err, scope => markEventUnhandled(scope)); - throw err; - }); - } - - return fnResult; + + if (span instanceof Transaction) { + // We also set __sentry_transaction on the response so people can grab the transaction there to add + // spans to it later. + // TODO(v8): Remove this + // eslint-disable-next-line @typescript-eslint/no-explicit-any, @typescript-eslint/no-unsafe-member-access + (res as any).__sentry_transaction = span; + } + + // eslint-disable-next-line @typescript-eslint/unbound-method + const _end = res.end; + // eslint-disable-next-line @typescript-eslint/no-explicit-any + res.end = function (chunk?: any | (() => void), encoding?: string | (() => void), cb?: () => void): any { + span?.setHttpStatus(res.statusCode); + span?.end(); + + // eslint-disable-next-line @typescript-eslint/no-floating-promises + flush(options.flushTimeout) + .then(null, e => { + DEBUG_BUILD && logger.error(e); + }) + .then(() => { + _end.call(this, chunk, encoding, cb); + }); + }; + + let fnResult; + try { + fnResult = fn(req, res); + } catch (err) { + captureException(err, scope => markEventUnhandled(scope)); + throw err; + } + + if (isThenable(fnResult)) { + fnResult.then(null, err => { + captureException(err, scope => markEventUnhandled(scope)); + throw err; + }); + } + + return fnResult; + }, + ); }; } diff --git a/packages/serverless/test/__mocks__/@sentry/node.ts b/packages/serverless/test/__mocks__/@sentry/node.ts index 0dbf38c3d483..d37bbbd2023c 100644 --- a/packages/serverless/test/__mocks__/@sentry/node.ts +++ b/packages/serverless/test/__mocks__/@sentry/node.ts @@ -7,13 +7,8 @@ export const SDK_VERSION = '6.6.6'; export const Severity = { Warning: 'warning', }; -export const fakeHub = { - configureScope: jest.fn((fn: (arg: any) => any) => fn(fakeScope)), - pushScope: jest.fn(() => fakeScope), - popScope: jest.fn(), - getScope: jest.fn(() => fakeScope), - startTransaction: jest.fn(context => ({ ...fakeTransaction, ...context })), -}; +export const continueTrace = origSentry.continueTrace; + export const fakeScope = { addEventProcessor: jest.fn(), setTransactionName: jest.fn(), @@ -26,6 +21,7 @@ export const fakeScope = { }; export const fakeSpan = { end: jest.fn(), + setHttpStatus: jest.fn(), }; export const fakeTransaction = { end: jest.fn(), @@ -34,25 +30,20 @@ export const fakeTransaction = { }; export const init = jest.fn(); export const addGlobalEventProcessor = jest.fn(); -export const getCurrentHub = jest.fn(() => fakeHub); export const getCurrentScope = jest.fn(() => fakeScope); -export const startTransaction = jest.fn(_ => fakeTransaction); export const captureException = jest.fn(); export const captureMessage = jest.fn(); export const withScope = jest.fn(cb => cb(fakeScope)); export const flush = jest.fn(() => Promise.resolve()); export const getClient = jest.fn(() => ({})); +export const startSpanManual = jest.fn((ctx, callback: (span: any) => any) => callback(fakeSpan)); export const resetMocks = (): void => { fakeTransaction.setHttpStatus.mockClear(); fakeTransaction.end.mockClear(); fakeTransaction.startChild.mockClear(); fakeSpan.end.mockClear(); - fakeHub.configureScope.mockClear(); - fakeHub.pushScope.mockClear(); - fakeHub.popScope.mockClear(); - fakeHub.getScope.mockClear(); - fakeHub.startTransaction.mockClear(); + fakeSpan.setHttpStatus.mockClear(); fakeScope.addEventProcessor.mockClear(); fakeScope.setTransactionName.mockClear(); @@ -63,11 +54,11 @@ export const resetMocks = (): void => { init.mockClear(); addGlobalEventProcessor.mockClear(); - getCurrentHub.mockClear(); - startTransaction.mockClear(); + captureException.mockClear(); captureMessage.mockClear(); withScope.mockClear(); flush.mockClear(); getClient.mockClear(); + startSpanManual.mockClear(); }; diff --git a/packages/serverless/test/awslambda.test.ts b/packages/serverless/test/awslambda.test.ts index 49f41e67f6fd..1188f0466295 100644 --- a/packages/serverless/test/awslambda.test.ts +++ b/packages/serverless/test/awslambda.test.ts @@ -41,12 +41,10 @@ const fakeCallback: Callback = (err, result) => { function expectScopeSettings(fakeTransactionContext: any) { // @ts-expect-error see "Why @ts-expect-error" note - const fakeTransaction = { ...SentryNode.fakeTransaction, ...fakeTransactionContext }; + const fakeSpan = { ...SentryNode.fakeSpan, ...fakeTransactionContext }; // @ts-expect-error see "Why @ts-expect-error" note expect(SentryNode.fakeScope.setTransactionName).toBeCalledWith('functionName'); // @ts-expect-error see "Why @ts-expect-error" note - expect(SentryNode.fakeScope.setSpan).toBeCalledWith(fakeTransaction); - // @ts-expect-error see "Why @ts-expect-error" note expect(SentryNode.fakeScope.setTag).toBeCalledWith('server_name', expect.anything()); // @ts-expect-error see "Why @ts-expect-error" note expect(SentryNode.fakeScope.setTag).toBeCalledWith('url', 'awslambda:///functionName'); @@ -103,7 +101,7 @@ describe('AWSLambda', () => { const wrappedHandler = wrapHandler(handler); await wrappedHandler(fakeEvent, fakeContext, fakeCallback); - expect(Sentry.withScope).toBeCalledTimes(2); + expect(Sentry.withScope).toBeCalledTimes(1); expect(Sentry.captureMessage).toBeCalled(); // @ts-expect-error see "Why @ts-expect-error" note expect(SentryNode.fakeScope.setTag).toBeCalledWith('timeout', '1s'); @@ -120,7 +118,7 @@ describe('AWSLambda', () => { }); await wrappedHandler(fakeEvent, fakeContext, fakeCallback); - expect(Sentry.withScope).toBeCalledTimes(1); + expect(Sentry.withScope).toBeCalledTimes(0); expect(Sentry.captureMessage).not.toBeCalled(); // @ts-expect-error see "Why @ts-expect-error" note expect(SentryNode.fakeScope.setTag).not.toBeCalledWith('timeout', '1s'); @@ -192,14 +190,13 @@ describe('AWSLambda', () => { expect(SentryNode.fakeScope.setTransactionName).toBeCalledTimes(0); // @ts-expect-error see "Why @ts-expect-error" note expect(SentryNode.fakeScope.setTag).toBeCalledTimes(0); - // @ts-expect-error see "Why @ts-expect-error" note - expect(SentryNode.fakeHub.startTransaction).toBeCalledTimes(0); + expect(SentryNode.startSpanManual).toBeCalledTimes(0); }); }); describe('wrapHandler() on sync handler', () => { test('successful execution', async () => { - expect.assertions(10); + expect.assertions(9); const handler: Handler = (_event, _context, callback) => { callback(null, 42); @@ -215,16 +212,15 @@ describe('AWSLambda', () => { }; expect(rv).toStrictEqual(42); - // @ts-expect-error see "Why @ts-expect-error" note - expect(SentryNode.fakeHub.startTransaction).toBeCalledWith(fakeTransactionContext); + expect(SentryNode.startSpanManual).toBeCalledWith(fakeTransactionContext, expect.any(Function)); expectScopeSettings(fakeTransactionContext); // @ts-expect-error see "Why @ts-expect-error" note - expect(SentryNode.fakeTransaction.end).toBeCalled(); + expect(SentryNode.fakeSpan.end).toBeCalled(); expect(SentryNode.flush).toBeCalledWith(2000); }); test('unsuccessful execution', async () => { - expect.assertions(10); + expect.assertions(9); const error = new Error('sorry'); const handler: Handler = (_event, _context, callback) => { @@ -242,12 +238,11 @@ describe('AWSLambda', () => { metadata: { source: 'component' }, }; - // @ts-expect-error see "Why @ts-expect-error" note - expect(SentryNode.fakeHub.startTransaction).toBeCalledWith(fakeTransactionContext); + expect(SentryNode.startSpanManual).toBeCalledWith(fakeTransactionContext, expect.any(Function)); expectScopeSettings(fakeTransactionContext); expect(SentryNode.captureException).toBeCalledWith(error, expect.any(Function)); // @ts-expect-error see "Why @ts-expect-error" note - expect(SentryNode.fakeTransaction.end).toBeCalled(); + expect(SentryNode.fakeSpan.end).toBeCalled(); expect(SentryNode.flush).toBeCalledWith(2000); } }); @@ -273,8 +268,7 @@ describe('AWSLambda', () => { }; const handler: Handler = (_event, _context, callback) => { - // @ts-expect-error see "Why @ts-expect-error" note - expect(SentryNode.fakeHub.startTransaction).toBeCalledWith( + expect(SentryNode.startSpanManual).toBeCalledWith( expect.objectContaining({ parentSpanId: '1121201211212012', parentSampled: false, @@ -289,6 +283,7 @@ describe('AWSLambda', () => { source: 'component', }, }), + expect.any(Function), ); callback(undefined, { its: 'fine' }); @@ -299,7 +294,7 @@ describe('AWSLambda', () => { }); test('capture error', async () => { - expect.assertions(10); + expect.assertions(9); const error = new Error('wat'); const handler: Handler = (_event, _context, _callback) => { @@ -321,12 +316,11 @@ describe('AWSLambda', () => { metadata: { dynamicSamplingContext: {}, source: 'component' }, }; - // @ts-expect-error see "Why @ts-expect-error" note - expect(SentryNode.fakeHub.startTransaction).toBeCalledWith(fakeTransactionContext); + expect(SentryNode.startSpanManual).toBeCalledWith(fakeTransactionContext, expect.any(Function)); expectScopeSettings(fakeTransactionContext); expect(SentryNode.captureException).toBeCalledWith(e, expect.any(Function)); // @ts-expect-error see "Why @ts-expect-error" note - expect(SentryNode.fakeTransaction.end).toBeCalled(); + expect(SentryNode.fakeSpan.end).toBeCalled(); expect(SentryNode.flush).toBeCalled(); } }); @@ -334,7 +328,7 @@ describe('AWSLambda', () => { describe('wrapHandler() on async handler', () => { test('successful execution', async () => { - expect.assertions(10); + expect.assertions(9); const handler: Handler = async (_event, _context) => { return 42; @@ -350,11 +344,10 @@ describe('AWSLambda', () => { }; expect(rv).toStrictEqual(42); - // @ts-expect-error see "Why @ts-expect-error" note - expect(SentryNode.fakeHub.startTransaction).toBeCalledWith(fakeTransactionContext); + expect(SentryNode.startSpanManual).toBeCalledWith(fakeTransactionContext, expect.any(Function)); expectScopeSettings(fakeTransactionContext); // @ts-expect-error see "Why @ts-expect-error" note - expect(SentryNode.fakeTransaction.end).toBeCalled(); + expect(SentryNode.fakeSpan.end).toBeCalled(); expect(SentryNode.flush).toBeCalled(); }); @@ -370,7 +363,7 @@ describe('AWSLambda', () => { }); test('capture error', async () => { - expect.assertions(10); + expect.assertions(9); const error = new Error('wat'); const handler: Handler = async (_event, _context) => { @@ -388,12 +381,11 @@ describe('AWSLambda', () => { metadata: { source: 'component' }, }; - // @ts-expect-error see "Why @ts-expect-error" note - expect(SentryNode.fakeHub.startTransaction).toBeCalledWith(fakeTransactionContext); + expect(SentryNode.startSpanManual).toBeCalledWith(fakeTransactionContext, expect.any(Function)); expectScopeSettings(fakeTransactionContext); expect(SentryNode.captureException).toBeCalledWith(error, expect.any(Function)); // @ts-expect-error see "Why @ts-expect-error" note - expect(SentryNode.fakeTransaction.end).toBeCalled(); + expect(SentryNode.fakeSpan.end).toBeCalled(); expect(SentryNode.flush).toBeCalled(); } }); @@ -416,7 +408,7 @@ describe('AWSLambda', () => { describe('wrapHandler() on async handler with a callback method (aka incorrect usage)', () => { test('successful execution', async () => { - expect.assertions(10); + expect.assertions(9); const handler: Handler = async (_event, _context, _callback) => { return 42; @@ -432,11 +424,10 @@ describe('AWSLambda', () => { }; expect(rv).toStrictEqual(42); - // @ts-expect-error see "Why @ts-expect-error" note - expect(SentryNode.fakeHub.startTransaction).toBeCalledWith(fakeTransactionContext); + expect(SentryNode.startSpanManual).toBeCalledWith(fakeTransactionContext, expect.any(Function)); expectScopeSettings(fakeTransactionContext); // @ts-expect-error see "Why @ts-expect-error" note - expect(SentryNode.fakeTransaction.end).toBeCalled(); + expect(SentryNode.fakeSpan.end).toBeCalled(); expect(SentryNode.flush).toBeCalled(); }); @@ -452,7 +443,7 @@ describe('AWSLambda', () => { }); test('capture error', async () => { - expect.assertions(10); + expect.assertions(9); const error = new Error('wat'); const handler: Handler = async (_event, _context, _callback) => { @@ -470,12 +461,11 @@ describe('AWSLambda', () => { metadata: { source: 'component' }, }; - // @ts-expect-error see "Why @ts-expect-error" note - expect(SentryNode.fakeHub.startTransaction).toBeCalledWith(fakeTransactionContext); + expect(SentryNode.startSpanManual).toBeCalledWith(fakeTransactionContext, expect.any(Function)); expectScopeSettings(fakeTransactionContext); expect(SentryNode.captureException).toBeCalledWith(error, expect.any(Function)); // @ts-expect-error see "Why @ts-expect-error" note - expect(SentryNode.fakeTransaction.end).toBeCalled(); + expect(SentryNode.fakeSpan.end).toBeCalled(); expect(SentryNode.flush).toBeCalled(); } }); diff --git a/packages/serverless/test/gcpfunction.test.ts b/packages/serverless/test/gcpfunction.test.ts index 4f442c543678..19a3a2565cdd 100644 --- a/packages/serverless/test/gcpfunction.test.ts +++ b/packages/serverless/test/gcpfunction.test.ts @@ -88,8 +88,6 @@ describe('GCPFunction', () => { describe('wrapHttpFunction() options', () => { test('flushTimeout', async () => { - expect.assertions(1); - const handler: HttpFunction = (_, res) => { res.end(); }; @@ -102,8 +100,6 @@ describe('GCPFunction', () => { describe('wrapHttpFunction()', () => { test('successful execution', async () => { - expect.assertions(5); - const handler: HttpFunction = (_req, res) => { res.statusCode = 200; res.end(); @@ -117,23 +113,16 @@ describe('GCPFunction', () => { origin: 'auto.function.serverless.gcp_http', metadata: { source: 'route' }, }; - // @ts-expect-error see "Why @ts-expect-error" note - const fakeTransaction = { ...SentryNode.fakeTransaction, ...fakeTransactionContext }; + expect(SentryNode.startSpanManual).toBeCalledWith(fakeTransactionContext, expect.any(Function)); // @ts-expect-error see "Why @ts-expect-error" note - expect(SentryNode.fakeHub.startTransaction).toBeCalledWith(fakeTransactionContext); - // @ts-expect-error see "Why @ts-expect-error" note - expect(SentryNode.fakeScope.setSpan).toBeCalledWith(fakeTransaction); + expect(SentryNode.fakeSpan.setHttpStatus).toBeCalledWith(200); // @ts-expect-error see "Why @ts-expect-error" note - expect(SentryNode.fakeTransaction.setHttpStatus).toBeCalledWith(200); - // @ts-expect-error see "Why @ts-expect-error" note - expect(SentryNode.fakeTransaction.end).toBeCalled(); + expect(SentryNode.fakeSpan.end).toBeCalled(); expect(SentryNode.flush).toBeCalledWith(2000); }); test('incoming trace headers are correctly parsed and used', async () => { - expect.assertions(1); - const handler: HttpFunction = (_req, res) => { res.statusCode = 200; res.end(); @@ -160,13 +149,10 @@ describe('GCPFunction', () => { }, }; - // @ts-expect-error see "Why @ts-expect-error" note - expect(SentryNode.fakeHub.startTransaction).toBeCalledWith(fakeTransactionContext); + expect(SentryNode.startSpanManual).toBeCalledWith(fakeTransactionContext, expect.any(Function)); }); test('capture error', async () => { - expect.assertions(5); - const error = new Error('wat'); const handler: HttpFunction = (_req, _res) => { throw error; @@ -188,22 +174,15 @@ describe('GCPFunction', () => { parentSampled: false, metadata: { dynamicSamplingContext: {}, source: 'route' }, }; - // @ts-expect-error see "Why @ts-expect-error" note - const fakeTransaction = { ...SentryNode.fakeTransaction, ...fakeTransactionContext }; - // @ts-expect-error see "Why @ts-expect-error" note - expect(SentryNode.fakeHub.startTransaction).toBeCalledWith(fakeTransactionContext); - // @ts-expect-error see "Why @ts-expect-error" note - expect(SentryNode.fakeScope.setSpan).toBeCalledWith(fakeTransaction); + expect(SentryNode.startSpanManual).toBeCalledWith(fakeTransactionContext, expect.any(Function)); expect(SentryNode.captureException).toBeCalledWith(error, expect.any(Function)); // @ts-expect-error see "Why @ts-expect-error" note - expect(SentryNode.fakeTransaction.end).toBeCalled(); + expect(SentryNode.fakeSpan.end).toBeCalled(); expect(SentryNode.flush).toBeCalled(); }); test('should not throw when flush rejects', async () => { - expect.assertions(2); - const handler: HttpFunction = async (_req, res) => { res.statusCode = 200; res.end(); @@ -262,8 +241,6 @@ describe('GCPFunction', () => { describe('wrapEventFunction() without callback', () => { test('successful execution', async () => { - expect.assertions(5); - const func: EventFunction = (_data, _context) => { return 42; }; @@ -276,21 +253,14 @@ describe('GCPFunction', () => { origin: 'auto.function.serverless.gcp_event', metadata: { source: 'component' }, }; - // @ts-expect-error see "Why @ts-expect-error" note - const fakeTransaction = { ...SentryNode.fakeTransaction, ...fakeTransactionContext }; + expect(SentryNode.startSpanManual).toBeCalledWith(fakeTransactionContext, expect.any(Function)); // @ts-expect-error see "Why @ts-expect-error" note - expect(SentryNode.fakeHub.startTransaction).toBeCalledWith(fakeTransactionContext); - // @ts-expect-error see "Why @ts-expect-error" note - expect(SentryNode.fakeScope.setSpan).toBeCalledWith(fakeTransaction); - // @ts-expect-error see "Why @ts-expect-error" note - expect(SentryNode.fakeTransaction.end).toBeCalled(); + expect(SentryNode.fakeSpan.end).toBeCalled(); expect(SentryNode.flush).toBeCalledWith(2000); }); test('capture error', async () => { - expect.assertions(6); - const error = new Error('wat'); const handler: EventFunction = (_data, _context) => { throw error; @@ -304,24 +274,17 @@ describe('GCPFunction', () => { origin: 'auto.function.serverless.gcp_event', metadata: { source: 'component' }, }; - // @ts-expect-error see "Why @ts-expect-error" note - const fakeTransaction = { ...SentryNode.fakeTransaction, ...fakeTransactionContext }; - // @ts-expect-error see "Why @ts-expect-error" note - expect(SentryNode.fakeHub.startTransaction).toBeCalledWith(fakeTransactionContext); - // @ts-expect-error see "Why @ts-expect-error" note - expect(SentryNode.fakeScope.setSpan).toBeCalledWith(fakeTransaction); + expect(SentryNode.startSpanManual).toBeCalledWith(fakeTransactionContext, expect.any(Function)); expect(SentryNode.captureException).toBeCalledWith(error, expect.any(Function)); // @ts-expect-error see "Why @ts-expect-error" note - expect(SentryNode.fakeTransaction.end).toBeCalled(); + expect(SentryNode.fakeSpan.end).toBeCalled(); expect(SentryNode.flush).toBeCalled(); }); }); describe('wrapEventFunction() as Promise', () => { test('successful execution', async () => { - expect.assertions(5); - const func: EventFunction = (_data, _context) => new Promise(resolve => { setTimeout(() => { @@ -337,21 +300,14 @@ describe('GCPFunction', () => { origin: 'auto.function.serverless.gcp_event', metadata: { source: 'component' }, }; - // @ts-expect-error see "Why @ts-expect-error" note - const fakeTransaction = { ...SentryNode.fakeTransaction, ...fakeTransactionContext }; + expect(SentryNode.startSpanManual).toBeCalledWith(fakeTransactionContext, expect.any(Function)); // @ts-expect-error see "Why @ts-expect-error" note - expect(SentryNode.fakeHub.startTransaction).toBeCalledWith(fakeTransactionContext); - // @ts-expect-error see "Why @ts-expect-error" note - expect(SentryNode.fakeScope.setSpan).toBeCalledWith(fakeTransaction); - // @ts-expect-error see "Why @ts-expect-error" note - expect(SentryNode.fakeTransaction.end).toBeCalled(); + expect(SentryNode.fakeSpan.end).toBeCalled(); expect(SentryNode.flush).toBeCalledWith(2000); }); test('capture error', async () => { - expect.assertions(6); - const error = new Error('wat'); const handler: EventFunction = (_data, _context) => new Promise((_, reject) => { @@ -369,24 +325,17 @@ describe('GCPFunction', () => { origin: 'auto.function.serverless.gcp_event', metadata: { source: 'component' }, }; - // @ts-expect-error see "Why @ts-expect-error" note - const fakeTransaction = { ...SentryNode.fakeTransaction, ...fakeTransactionContext }; - // @ts-expect-error see "Why @ts-expect-error" note - expect(SentryNode.fakeHub.startTransaction).toBeCalledWith(fakeTransactionContext); - // @ts-expect-error see "Why @ts-expect-error" note - expect(SentryNode.fakeScope.setSpan).toBeCalledWith(fakeTransaction); + expect(SentryNode.startSpanManual).toBeCalledWith(fakeTransactionContext, expect.any(Function)); expect(SentryNode.captureException).toBeCalledWith(error, expect.any(Function)); // @ts-expect-error see "Why @ts-expect-error" note - expect(SentryNode.fakeTransaction.end).toBeCalled(); + expect(SentryNode.fakeSpan.end).toBeCalled(); expect(SentryNode.flush).toBeCalled(); }); }); describe('wrapEventFunction() with callback', () => { test('successful execution', async () => { - expect.assertions(5); - const func: EventFunctionWithCallback = (_data, _context, cb) => { cb(null, 42); }; @@ -399,21 +348,14 @@ describe('GCPFunction', () => { origin: 'auto.function.serverless.gcp_event', metadata: { source: 'component' }, }; - // @ts-expect-error see "Why @ts-expect-error" note - const fakeTransaction = { ...SentryNode.fakeTransaction, ...fakeTransactionContext }; + expect(SentryNode.startSpanManual).toBeCalledWith(fakeTransactionContext, expect.any(Function)); // @ts-expect-error see "Why @ts-expect-error" note - expect(SentryNode.fakeHub.startTransaction).toBeCalledWith(fakeTransactionContext); - // @ts-expect-error see "Why @ts-expect-error" note - expect(SentryNode.fakeScope.setSpan).toBeCalledWith(fakeTransaction); - // @ts-expect-error see "Why @ts-expect-error" note - expect(SentryNode.fakeTransaction.end).toBeCalled(); + expect(SentryNode.fakeSpan.end).toBeCalled(); expect(SentryNode.flush).toBeCalledWith(2000); }); test('capture error', async () => { - expect.assertions(6); - const error = new Error('wat'); const handler: EventFunctionWithCallback = (_data, _context, cb) => { cb(error); @@ -427,22 +369,15 @@ describe('GCPFunction', () => { origin: 'auto.function.serverless.gcp_event', metadata: { source: 'component' }, }; - // @ts-expect-error see "Why @ts-expect-error" note - const fakeTransaction = { ...SentryNode.fakeTransaction, ...fakeTransactionContext }; - // @ts-expect-error see "Why @ts-expect-error" note - expect(SentryNode.fakeHub.startTransaction).toBeCalledWith(fakeTransactionContext); - // @ts-expect-error see "Why @ts-expect-error" note - expect(SentryNode.fakeScope.setSpan).toBeCalledWith(fakeTransaction); + expect(SentryNode.startSpanManual).toBeCalledWith(fakeTransactionContext, expect.any(Function)); expect(SentryNode.captureException).toBeCalledWith(error, expect.any(Function)); // @ts-expect-error see "Why @ts-expect-error" note - expect(SentryNode.fakeTransaction.end).toBeCalled(); + expect(SentryNode.fakeSpan.end).toBeCalled(); expect(SentryNode.flush).toBeCalled(); }); test('capture exception', async () => { - expect.assertions(4); - const error = new Error('wat'); const handler: EventFunctionWithCallback = (_data, _context, _cb) => { throw error; @@ -456,20 +391,13 @@ describe('GCPFunction', () => { origin: 'auto.function.serverless.gcp_event', metadata: { source: 'component' }, }; - // @ts-expect-error see "Why @ts-expect-error" note - const fakeTransaction = { ...SentryNode.fakeTransaction, ...fakeTransactionContext }; - // @ts-expect-error see "Why @ts-expect-error" note - expect(SentryNode.fakeHub.startTransaction).toBeCalledWith(fakeTransactionContext); - // @ts-expect-error see "Why @ts-expect-error" note - expect(SentryNode.fakeScope.setSpan).toBeCalledWith(fakeTransaction); + expect(SentryNode.startSpanManual).toBeCalledWith(fakeTransactionContext, expect.any(Function)); expect(SentryNode.captureException).toBeCalledWith(error, expect.any(Function)); }); }); test('marks the captured error as unhandled', async () => { - expect.assertions(4); - const error = new Error('wat'); const handler: EventFunctionWithCallback = (_data, _context, _cb) => { throw error; @@ -494,8 +422,6 @@ describe('GCPFunction', () => { }); test('wrapEventFunction scope data', async () => { - expect.assertions(1); - const handler: EventFunction = (_data, _context) => 42; const wrappedHandler = wrapEventFunction(handler); await handleEvent(wrappedHandler); @@ -508,8 +434,6 @@ describe('GCPFunction', () => { describe('wrapCloudEventFunction() without callback', () => { test('successful execution', async () => { - expect.assertions(5); - const func: CloudEventFunction = _context => { return 42; }; @@ -522,21 +446,14 @@ describe('GCPFunction', () => { origin: 'auto.function.serverless.gcp_cloud_event', metadata: { source: 'component' }, }; - // @ts-expect-error see "Why @ts-expect-error" note - const fakeTransaction = { ...SentryNode.fakeTransaction, ...fakeTransactionContext }; + expect(SentryNode.startSpanManual).toBeCalledWith(fakeTransactionContext, expect.any(Function)); // @ts-expect-error see "Why @ts-expect-error" note - expect(SentryNode.fakeHub.startTransaction).toBeCalledWith(fakeTransactionContext); - // @ts-expect-error see "Why @ts-expect-error" note - expect(SentryNode.fakeScope.setSpan).toBeCalledWith(fakeTransaction); - // @ts-expect-error see "Why @ts-expect-error" note - expect(SentryNode.fakeTransaction.end).toBeCalled(); + expect(SentryNode.fakeSpan.end).toBeCalled(); expect(SentryNode.flush).toBeCalledWith(2000); }); test('capture error', async () => { - expect.assertions(6); - const error = new Error('wat'); const handler: CloudEventFunction = _context => { throw error; @@ -550,24 +467,17 @@ describe('GCPFunction', () => { origin: 'auto.function.serverless.gcp_cloud_event', metadata: { source: 'component' }, }; - // @ts-expect-error see "Why @ts-expect-error" note - const fakeTransaction = { ...SentryNode.fakeTransaction, ...fakeTransactionContext }; - // @ts-expect-error see "Why @ts-expect-error" note - expect(SentryNode.fakeHub.startTransaction).toBeCalledWith(fakeTransactionContext); - // @ts-expect-error see "Why @ts-expect-error" note - expect(SentryNode.fakeScope.setSpan).toBeCalledWith(fakeTransaction); + expect(SentryNode.startSpanManual).toBeCalledWith(fakeTransactionContext, expect.any(Function)); expect(SentryNode.captureException).toBeCalledWith(error, expect.any(Function)); // @ts-expect-error see "Why @ts-expect-error" note - expect(SentryNode.fakeTransaction.end).toBeCalled(); + expect(SentryNode.fakeSpan.end).toBeCalled(); expect(SentryNode.flush).toBeCalled(); }); }); describe('wrapCloudEventFunction() with callback', () => { test('successful execution', async () => { - expect.assertions(5); - const func: CloudEventFunctionWithCallback = (_context, cb) => { cb(null, 42); }; @@ -580,21 +490,14 @@ describe('GCPFunction', () => { origin: 'auto.function.serverless.gcp_cloud_event', metadata: { source: 'component' }, }; - // @ts-expect-error see "Why @ts-expect-error" note - const fakeTransaction = { ...SentryNode.fakeTransaction, ...fakeTransactionContext }; + expect(SentryNode.startSpanManual).toBeCalledWith(fakeTransactionContext, expect.any(Function)); // @ts-expect-error see "Why @ts-expect-error" note - expect(SentryNode.fakeHub.startTransaction).toBeCalledWith(fakeTransactionContext); - // @ts-expect-error see "Why @ts-expect-error" note - expect(SentryNode.fakeScope.setSpan).toBeCalledWith(fakeTransaction); - // @ts-expect-error see "Why @ts-expect-error" note - expect(SentryNode.fakeTransaction.end).toBeCalled(); + expect(SentryNode.fakeSpan.end).toBeCalled(); expect(SentryNode.flush).toBeCalledWith(2000); }); test('capture error', async () => { - expect.assertions(6); - const error = new Error('wat'); const handler: CloudEventFunctionWithCallback = (_context, cb) => { cb(error); @@ -608,22 +511,15 @@ describe('GCPFunction', () => { origin: 'auto.function.serverless.gcp_cloud_event', metadata: { source: 'component' }, }; - // @ts-expect-error see "Why @ts-expect-error" note - const fakeTransaction = { ...SentryNode.fakeTransaction, ...fakeTransactionContext }; - // @ts-expect-error see "Why @ts-expect-error" note - expect(SentryNode.fakeHub.startTransaction).toBeCalledWith(fakeTransactionContext); - // @ts-expect-error see "Why @ts-expect-error" note - expect(SentryNode.fakeScope.setSpan).toBeCalledWith(fakeTransaction); + expect(SentryNode.startSpanManual).toBeCalledWith(fakeTransactionContext, expect.any(Function)); expect(SentryNode.captureException).toBeCalledWith(error, expect.any(Function)); // @ts-expect-error see "Why @ts-expect-error" note - expect(SentryNode.fakeTransaction.end).toBeCalled(); + expect(SentryNode.fakeSpan.end).toBeCalled(); expect(SentryNode.flush).toBeCalled(); }); test('capture exception', async () => { - expect.assertions(4); - const error = new Error('wat'); const handler: CloudEventFunctionWithCallback = (_context, _cb) => { throw error; @@ -637,21 +533,13 @@ describe('GCPFunction', () => { origin: 'auto.function.serverless.gcp_cloud_event', metadata: { source: 'component' }, }; - // @ts-expect-error see "Why @ts-expect-error" note - const fakeTransaction = { ...SentryNode.fakeTransaction, ...fakeTransactionContext }; - - // @ts-expect-error see "Why @ts-expect-error" note - expect(SentryNode.fakeHub.startTransaction).toBeCalledWith(fakeTransactionContext); - // @ts-expect-error see "Why @ts-expect-error" note - expect(SentryNode.fakeScope.setSpan).toBeCalledWith(fakeTransaction); + expect(SentryNode.startSpanManual).toBeCalledWith(fakeTransactionContext, expect.any(Function)); expect(SentryNode.captureException).toBeCalledWith(error, expect.any(Function)); }); }); test('wrapCloudEventFunction scope data', async () => { - expect.assertions(1); - const handler: CloudEventFunction = _context => 42; const wrappedHandler = wrapCloudEventFunction(handler); await handleCloudEvent(wrappedHandler);