diff --git a/packages/core/src/integrations/metadata.ts b/packages/core/src/integrations/metadata.ts index 917d8a12beb8..28207ff188c3 100644 --- a/packages/core/src/integrations/metadata.ts +++ b/packages/core/src/integrations/metadata.ts @@ -1,14 +1,36 @@ -import type { EventItem, IntegrationFn } from '@sentry/types'; +import type { EventItem, Exception, IntegrationFn } from '@sentry/types'; import { forEachEnvelopeItem } from '@sentry/utils'; import { defineIntegration } from '../integration'; import { addMetadataToStackFrames, stripMetadataFromStackFrames } from '../metadata'; -const INTEGRATION_NAME = 'ModuleMetadata'; +// eslint-disable-next-line @typescript-eslint/no-explicit-any +type ModuleMetadata = any; -const _moduleMetadataIntegration = (() => { +function getAllModuleMetadata(exceptions: Exception[]): ModuleMetadata[] { + return exceptions.reduce( + (acc, exception) => { + if (exception.stacktrace && exception.stacktrace.frames) { + acc.push(...exception.stacktrace.frames.map(frame => frame.module_metadata)); + } + return acc; + }, + [] as ModuleMetadata[], + ); +} + +interface Options { + dropEvent?: { + /** + * Drop event if no stack frames have matching metadata + */ + ifNoStackFrameMetadataMatches?: (metadata: ModuleMetadata | undefined) => boolean; + }; +} + +const _moduleMetadataIntegration = ((options: Options = {}) => { return { - name: INTEGRATION_NAME, + name: 'ModuleMetadata', setup(client) { // We need to strip metadata from stack frames before sending them to Sentry since these are client side only. client.on('beforeEnvelope', envelope => { @@ -28,6 +50,19 @@ const _moduleMetadataIntegration = (() => { processEvent(event, _hint, client) { const stackParser = client.getOptions().stackParser; addMetadataToStackFrames(stackParser, event); + + if ( + event.exception && + event.exception.values && + options.dropEvent && + options.dropEvent.ifNoStackFrameMetadataMatches + ) { + const metadata = getAllModuleMetadata(event.exception.values); + if (!metadata.some(options.dropEvent.ifNoStackFrameMetadataMatches)) { + return null; + } + } + return event; }, }; diff --git a/packages/core/test/lib/integrations/metadata.test.ts b/packages/core/test/lib/integrations/metadata.test.ts index 7f53ce090275..b9da0ced3551 100644 --- a/packages/core/test/lib/integrations/metadata.test.ts +++ b/packages/core/test/lib/integrations/metadata.test.ts @@ -63,4 +63,67 @@ describe('ModuleMetadata integration', () => { captureException(new Error('Some error')); }); + + test('Drops event if no stack frames have matching metadata', done => { + expect.assertions(0); + + const options = getDefaultTestClientOptions({ + dsn: 'https://username@domain/123', + enableSend: true, + stackParser, + integrations: [ + moduleMetadataIntegration({ dropEvent: { ifNoStackFrameMetadataMatches: m => m?.team === 'backend' } }), + ], + transport: () => + createTransport({ recordDroppedEvent: () => undefined }, async req => { + expect(req.body).toBeUndefined(); + done(); + return {}; + }), + }); + + const client = new TestClient(options); + setCurrentClient(client); + client.init(); + + captureException(new Error('Some error')); + + setTimeout(() => { + done(); + }, 2000); + }); + + test('Sends event if stack frames have matching metadata', done => { + expect.assertions(1); + + let callbackCalled = false; + + const options = getDefaultTestClientOptions({ + dsn: 'https://username@domain/123', + enableSend: true, + stackParser, + integrations: [ + moduleMetadataIntegration({ + dropEvent: { + ifNoStackFrameMetadataMatches: m => { + callbackCalled = true; + return m?.team === 'frontend'; + }, + }, + }), + ], + transport: () => + createTransport({ recordDroppedEvent: () => undefined }, async _ => { + expect(callbackCalled).toBe(true); + done(); + return {}; + }), + }); + + const client = new TestClient(options); + setCurrentClient(client); + client.init(); + + captureException(new Error('Some error')); + }); });