Skip to content

Commit 1ed5507

Browse files
committed
add experimental option
1 parent 1657815 commit 1ed5507

1 file changed

Lines changed: 67 additions & 13 deletions

File tree

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

Lines changed: 67 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 exclude 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+
experimentalExcludeSentryInternalFrames?: 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.experimentalExcludeSentryInternalFrames);
7179

7280
if (frameKeys) {
7381
const arrayMethod =
@@ -98,27 +106,73 @@ 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+
// Filename would look something like this: 'node_modules/@sentry/browser/build/npm/esm/helpers.js'
124+
if (!frame.filename.includes('sentry') || !frame.filename.includes('helpers')) {
125+
return false;
126+
}
127+
128+
// Must have context_line with the exact fn.apply pattern (case-sensitive)
129+
if (!frame.context_line.includes(SENTRY_INTERNAL_FN_APPLY)) {
130+
return false;
131+
}
132+
133+
// Check pre_context array for comment pattern
134+
if (frame.pre_context) {
135+
const len = frame.pre_context.length;
136+
for (let i = 0; i < len; i++) {
137+
if (frame.pre_context[i]?.includes(SENTRY_INTERNAL_COMMENT)) {
138+
return true;
139+
}
140+
}
141+
}
142+
143+
return false;
144+
}
145+
146+
function getBundleKeysForAllFramesWithFilenames(
147+
event: Event,
148+
excludeSentryInternalFrames?: boolean,
149+
): string[][] | undefined {
102150
const frames = getFramesFromEvent(event);
103151

104152
if (!frames) {
105153
return undefined;
106154
}
107155

108-
return (
109-
frames
156+
return frames
157+
.filter((frame, index) => {
110158
// Exclude frames without a filename or without lineno and colno,
111159
// 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-
}
160+
if (!frame.filename || (frame.lineno == null && frame.colno == null)) {
161+
return false;
162+
}
163+
// Optionally exclude Sentry internal frames
164+
return !excludeSentryInternalFrames || !isSentryInternalFrame(frame, index);
165+
})
166+
.map(frame => {
167+
if (!frame.module_metadata) {
119168
return [];
120-
})
121-
);
169+
}
170+
return Object.keys(frame.module_metadata)
171+
.filter(key => key.startsWith(BUNDLER_PLUGIN_APP_KEY_PREFIX))
172+
.map(key => key.slice(BUNDLER_PLUGIN_APP_KEY_PREFIX.length));
173+
});
122174
}
123175

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

0 commit comments

Comments
 (0)