Skip to content

Commit fdbddaa

Browse files
authored
feat(core): Add ignoreSentryInternalFrames option to thirdPartyErrorFilterIntegration (#18632)
Some users get flooded by third party errors despite having set their third party error filtering to `"drop-error-if-contains-third-party-frames"`. This comes from the fact that often the stacktrace includes our internal wrapper logic as last trace in the stack. With this PR we're trying to work around this by specifically ignoring these frames with a new opt-in mechanism. Marked this as experimental, so users will know this option might lead to errors being misclassified. - Adds a new experimental option `experimentalExcludeSentryInternalFrames` to the `thirdPartyErrorFilterIntegration` - Once enabled we apply a strict filter for frames to detect our internal wrapping logic and filter them out so they do not misclassify injected code as internal errors. Closes #13835
1 parent 2fb0f99 commit fdbddaa

File tree

2 files changed

+450
-13
lines changed

2 files changed

+450
-13
lines changed

packages/core/src/integrations/third-party-errors-filter.ts

Lines changed: 65 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ import { defineIntegration } from '../integration';
22
import { addMetadataToStackFrames, stripMetadataFromStackFrames } from '../metadata';
33
import type { EventItem } from '../types-hoist/envelope';
44
import type { Event } from '../types-hoist/event';
5+
import type { StackFrame } from '../types-hoist/stackframe';
56
import { forEachEnvelopeItem } from '../utils/envelope';
67
import { getFramesFromEvent } from '../utils/stacktrace';
78

@@ -32,6 +33,13 @@ interface Options {
3233
| 'drop-error-if-exclusively-contains-third-party-frames'
3334
| 'apply-tag-if-contains-third-party-frames'
3435
| 'apply-tag-if-exclusively-contains-third-party-frames';
36+
37+
/**
38+
* @experimental
39+
* If set to true, the integration will ignore frames that are internal to the Sentry SDK from the third-party frame detection.
40+
* Note that enabling this option might lead to errors being misclassified as third-party errors.
41+
*/
42+
ignoreSentryInternalFrames?: boolean;
3543
}
3644

3745
/**
@@ -67,7 +75,7 @@ export const thirdPartyErrorFilterIntegration = defineIntegration((options: Opti
6775
},
6876

6977
processEvent(event) {
70-
const frameKeys = getBundleKeysForAllFramesWithFilenames(event);
78+
const frameKeys = getBundleKeysForAllFramesWithFilenames(event, options.ignoreSentryInternalFrames);
7179

7280
if (frameKeys) {
7381
const arrayMethod =
@@ -98,27 +106,71 @@ export const thirdPartyErrorFilterIntegration = defineIntegration((options: Opti
98106
};
99107
});
100108

101-
function getBundleKeysForAllFramesWithFilenames(event: Event): string[][] | undefined {
109+
/**
110+
* Checks if a stack frame is a Sentry internal frame by strictly matching:
111+
* 1. The frame must be the last frame in the stack
112+
* 2. The filename must indicate the internal helpers file
113+
* 3. The context_line must contain the exact pattern "fn.apply(this, wrappedArguments)"
114+
* 4. The comment pattern "Attempt to invoke user-land function" must be present in pre_context
115+
*
116+
*/
117+
function isSentryInternalFrame(frame: StackFrame, frameIndex: number): boolean {
118+
// Only match the last frame (index 0 in reversed stack)
119+
if (frameIndex !== 0 || !frame.context_line || !frame.filename) {
120+
return false;
121+
}
122+
123+
if (
124+
!frame.filename.includes('sentry') ||
125+
!frame.filename.includes('helpers') || // Filename would look something like this: 'node_modules/@sentry/browser/build/npm/esm/helpers.js'
126+
!frame.context_line.includes(SENTRY_INTERNAL_FN_APPLY) // Must have context_line with the exact fn.apply pattern
127+
) {
128+
return false;
129+
}
130+
131+
// Check pre_context array for comment pattern
132+
if (frame.pre_context) {
133+
const len = frame.pre_context.length;
134+
for (let i = 0; i < len; i++) {
135+
if (frame.pre_context[i]?.includes(SENTRY_INTERNAL_COMMENT)) {
136+
return true;
137+
}
138+
}
139+
}
140+
141+
return false;
142+
}
143+
144+
function getBundleKeysForAllFramesWithFilenames(
145+
event: Event,
146+
ignoreSentryInternalFrames?: boolean,
147+
): string[][] | undefined {
102148
const frames = getFramesFromEvent(event);
103149

104150
if (!frames) {
105151
return undefined;
106152
}
107153

108-
return (
109-
frames
154+
return frames
155+
.filter((frame, index) => {
110156
// Exclude frames without a filename or without lineno and colno,
111157
// since these are likely native code or built-ins
112-
.filter(frame => !!frame.filename && (frame.lineno ?? frame.colno) != null)
113-
.map(frame => {
114-
if (frame.module_metadata) {
115-
return Object.keys(frame.module_metadata)
116-
.filter(key => key.startsWith(BUNDLER_PLUGIN_APP_KEY_PREFIX))
117-
.map(key => key.slice(BUNDLER_PLUGIN_APP_KEY_PREFIX.length));
118-
}
158+
if (!frame.filename || (frame.lineno == null && frame.colno == null)) {
159+
return false;
160+
}
161+
// Optionally ignore Sentry internal frames
162+
return !ignoreSentryInternalFrames || !isSentryInternalFrame(frame, index);
163+
})
164+
.map(frame => {
165+
if (!frame.module_metadata) {
119166
return [];
120-
})
121-
);
167+
}
168+
return Object.keys(frame.module_metadata)
169+
.filter(key => key.startsWith(BUNDLER_PLUGIN_APP_KEY_PREFIX))
170+
.map(key => key.slice(BUNDLER_PLUGIN_APP_KEY_PREFIX.length));
171+
});
122172
}
123173

124174
const BUNDLER_PLUGIN_APP_KEY_PREFIX = '_sentryBundlerPluginAppKey:';
175+
const SENTRY_INTERNAL_COMMENT = 'Attempt to invoke user-land function';
176+
const SENTRY_INTERNAL_FN_APPLY = 'fn.apply(this, wrappedArguments)';

0 commit comments

Comments
 (0)