Skip to content
Merged
Show file tree
Hide file tree
Changes from 7 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
1 change: 1 addition & 0 deletions packages/core/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -95,6 +95,7 @@ export { extraErrorDataIntegration } from './integrations/extraerrordata';
export { rewriteFramesIntegration } from './integrations/rewriteframes';
export { sessionTimingIntegration } from './integrations/sessiontiming';
export { zodErrorsIntegration } from './integrations/zoderrors';
export { thirdPartyErrorFilterIntegration } from './integrations/third-party-errors-filter';
export { metrics } from './metrics/exports';
export type { MetricData } from '@sentry/types';
export { metricsDefault } from './metrics/exports-default';
Expand Down
20 changes: 3 additions & 17 deletions packages/core/src/integrations/dedupe.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import type { Event, Exception, IntegrationFn, StackFrame } from '@sentry/types';
import { logger } from '@sentry/utils';
import { getFramesFromEvent, logger } from '@sentry/utils';
import { defineIntegration } from '../integration';

import { DEBUG_BUILD } from '../debug-build';
Expand Down Expand Up @@ -106,8 +106,8 @@ function _isSameExceptionEvent(currentEvent: Event, previousEvent: Event): boole
}

function _isSameStacktrace(currentEvent: Event, previousEvent: Event): boolean {
let currentFrames = _getFramesFromEvent(currentEvent);
let previousFrames = _getFramesFromEvent(previousEvent);
let currentFrames = getFramesFromEvent(currentEvent);
let previousFrames = getFramesFromEvent(previousEvent);

// If neither event has a stacktrace, they are assumed to be the same
if (!currentFrames && !previousFrames) {
Expand Down Expand Up @@ -173,17 +173,3 @@ function _isSameFingerprint(currentEvent: Event, previousEvent: Event): boolean
function _getExceptionFromEvent(event: Event): Exception | undefined {
return event.exception && event.exception.values && event.exception.values[0];
}

function _getFramesFromEvent(event: Event): StackFrame[] | undefined {
const exception = event.exception;

if (exception) {
try {
// @ts-expect-error Object could be undefined
return exception.values[0].stacktrace.frames;
} catch (_oO) {
return undefined;
}
}
return undefined;
}
114 changes: 114 additions & 0 deletions packages/core/src/integrations/third-party-errors-filter.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,114 @@
import type { Event, EventItem } from '@sentry/types';
import { forEachEnvelopeItem, getFramesFromEvent } from '@sentry/utils';
import { defineIntegration } from '../integration';
import { addMetadataToStackFrames, stripMetadataFromStackFrames } from '../metadata';

interface Options {
/**
* Keys that have been provided in the Sentry bundler plugin via the the `applicationKey` option, identifying your bundles.
*
* - Webpack plugin: https://www.npmjs.com/package/@sentry/webpack-plugin#applicationkey
* - Vite plugin: https://www.npmjs.com/package/@sentry/vite-plugin#applicationkey
* - Esbuild plugin: https://www.npmjs.com/package/@sentry/esbuild-plugin#applicationkey
* - Rollup plugin: https://www.npmjs.com/package/@sentry/rollup-plugin#applicationkey
*/
filterKeys: string[];

/**
* Defines how the integration should behave. "Third-Party Stack Frames" are stack frames that did not come from files marked with a matching bundle key.
*
* You can define the behaviour with one of 4 modes:
* - `drop-error-if-contains-third-party-frames`: Drop error events that contain at least one third-party stack frame.
* - `drop-error-if-exclusively-contains-third-party-frames`: Drop error events that exclusively contain third-party stack frames.
* - `apply-tag-if-contains-third-party-frames`: Keep all error events, but apply a `third_party_code: true` tag in case the error contains at least one third-party stack frame.
* - `apply-tag-if-exclusively-contains-third-party-frames`: Keep all error events, but apply a `third_party_code: true` tag in case the error contains exclusively third-party stack frames.
*
* If you chose the mode to only apply tags, the tags can then be used in Sentry to filter your issue stream by entering `!third_party_code:True` in the search bar.
*/
behaviour:
| 'drop-error-if-contains-third-party-frames'
| 'drop-error-if-exclusively-contains-third-party-frames'
| 'apply-tag-if-contains-third-party-frames'
| 'apply-tag-if-exclusively-contains-third-party-frames';
}
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

These are much better names!

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yup, I agree. Your comment was a wake-up call 😂


/**
* This integration allows you to filter out, or tag error events that do not come from user code marked with a bundle key via the Sentry bundler plugins.
*/
export const thirdPartyErrorFilterIntegration = defineIntegration((options: Options) => {
return {
name: 'ThirdPartyErrorsFilter',
setup(client) {
// We need to strip metadata from stack frames before sending them to Sentry since these are client side only.
// TODO(lforst): Move this cleanup logic into a more central place in the SDK.
client.on('beforeEnvelope', envelope => {
forEachEnvelopeItem(envelope, (item, type) => {
if (type === 'event') {
const event = Array.isArray(item) ? (item as EventItem)[1] : undefined;

if (event) {
stripMetadataFromStackFrames(event);
item[1] = event;
}
}
});
});
},
processEvent(event, _hint, client) {
const stackParser = client.getOptions().stackParser;
addMetadataToStackFrames(stackParser, event);

const frameKeys = getBundleKeysForAllFramesWithFilenames(event);

if (frameKeys) {
const arrayMethod =
options.behaviour === 'drop-error-if-contains-third-party-frames' ||
options.behaviour === 'apply-tag-if-contains-third-party-frames'
? 'some'
: 'every';

const behaviourApplies = frameKeys[arrayMethod](keys => !keys.some(key => options.filterKeys.includes(key)));

if (behaviourApplies) {
const shouldDrop =
options.behaviour === 'drop-error-if-contains-third-party-frames' ||
options.behaviour === 'drop-error-if-exclusively-contains-third-party-frames';
if (shouldDrop) {
return null;
} else {
event.tags = {
...event.tags,
third_party_code: true,
};
}
}
}

return event;
},
};
});

function getBundleKeysForAllFramesWithFilenames(event: Event): string[][] | undefined {
const frames = getFramesFromEvent(event);

if (!frames) {
return undefined;
}

return (
frames
// Exclude frames without a filename since these are likely native code or built-ins
.filter(frame => !!frame.filename)
.map(frame => {
if (frame.module_metadata) {
return Object.keys(frame.module_metadata)
.filter(key => key.startsWith(BUNDLER_PLUGIN_APP_KEY_PREFIX))
.map(key => key.slice(BUNDLER_PLUGIN_APP_KEY_PREFIX.length));
}
return [];
})
);
}

const BUNDLER_PLUGIN_APP_KEY_PREFIX = '_sentryBundlerPluginAppKey:';
2 changes: 1 addition & 1 deletion packages/core/src/metadata.ts
Original file line number Diff line number Diff line change
Expand Up @@ -60,7 +60,7 @@ export function addMetadataToStackFrames(parser: StackParser, event: Event): voi
}

for (const frame of exception.stacktrace.frames || []) {
if (!frame.filename) {
if (!frame.filename || frame.module_metadata) {
continue;
}

Expand Down
Loading