Skip to content

Commit 04db7fc

Browse files
authored
Merge branch 'develop' into sig/ioredis-setex
2 parents 014dc7f + bc5d2e1 commit 04db7fc

File tree

11 files changed

+487
-21
lines changed

11 files changed

+487
-21
lines changed

.size-limit.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@ module.exports = [
2222
path: 'packages/browser/build/npm/esm/index.js',
2323
import: createImport('init', 'browserTracingIntegration', 'replayIntegration'),
2424
gzip: true,
25-
limit: '70 KB',
25+
limit: '71 KB',
2626
},
2727
{
2828
name: '@sentry/browser (incl. Tracing, Replay) - with treeshaking flags',

packages/browser/src/stack-parsers.ts

Lines changed: 16 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -50,14 +50,29 @@ function createFrame(filename: string, func: string, lineno?: number, colno?: nu
5050
return frame;
5151
}
5252

53-
// Chromium based browsers: Chrome, Brave, new Opera, new Edge
53+
// This regex matches frames that have no function name (ie. are at the top level of a module).
54+
// For example "at http://localhost:5000//script.js:1:126"
55+
// Frames _with_ function names usually look as follows: "at commitLayoutEffects (react-dom.development.js:23426:1)"
56+
const chromeRegexNoFnName = /^\s*at (\S+?)(?::(\d+))(?::(\d+))\s*$/i;
57+
58+
// This regex matches all the frames that have a function name.
5459
const chromeRegex =
5560
/^\s*at (?:(.+?\)(?: \[.+\])?|.*?) ?\((?:address at )?)?(?:async )?((?:<anonymous>|[-a-z]+:|.*bundle|\/)?.*?)(?::(\d+))?(?::(\d+))?\)?\s*$/i;
61+
5662
const chromeEvalRegex = /\((\S*)(?::(\d+))(?::(\d+))\)/;
5763

64+
// Chromium based browsers: Chrome, Brave, new Opera, new Edge
5865
// We cannot call this variable `chrome` because it can conflict with global `chrome` variable in certain environments
5966
// See: https://github.com/getsentry/sentry-javascript/issues/6880
6067
const chromeStackParserFn: StackLineParserFn = line => {
68+
// If the stack line has no function name, we need to parse it differently
69+
const noFnParts = chromeRegexNoFnName.exec(line);
70+
71+
if (noFnParts) {
72+
const [, filename, line, col] = noFnParts;
73+
return createFrame(filename, UNKNOWN_FUNCTION, +line, +col);
74+
}
75+
6176
const parts = chromeRegex.exec(line);
6277

6378
if (parts) {

packages/browser/test/unit/tracekit/chromium.test.ts

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -579,6 +579,7 @@ describe('Tracekit - Chrome Tests', () => {
579579
name: 'Error',
580580
stack: `Error: bad
581581
at something (http://localhost:5000/(some)/(thing)/index.html:20:16)
582+
at http://localhost:5000/(group)/[route]/script.js:1:126
582583
at more (http://localhost:5000/(some)/(thing)/index.html:25:7)`,
583584
};
584585

@@ -596,6 +597,13 @@ describe('Tracekit - Chrome Tests', () => {
596597
colno: 7,
597598
in_app: true,
598599
},
600+
{
601+
filename: 'http://localhost:5000/(group)/[route]/script.js',
602+
function: '?',
603+
lineno: 1,
604+
colno: 126,
605+
in_app: true,
606+
},
599607
{
600608
filename: 'http://localhost:5000/(some)/(thing)/index.html',
601609
function: 'something',

packages/browser/test/unit/tracekit/firefox.test.ts

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -311,6 +311,41 @@ describe('Tracekit - Firefox Tests', () => {
311311
});
312312
});
313313

314+
it('should correctly parse parentheses', () => {
315+
const PARENTHESIS_FRAME_EXCEPTION = {
316+
message: 'aha',
317+
name: 'Error',
318+
stack:
319+
'onClick@http://localhost:3002/_next/static/chunks/app/(group)/[route]/script.js:1:644\n' +
320+
'@http://localhost:3002/_next/static/chunks/app/(group)/[route]/script.js:1:126',
321+
};
322+
323+
const stacktrace = exceptionFromError(parser, PARENTHESIS_FRAME_EXCEPTION);
324+
325+
expect(stacktrace).toEqual({
326+
value: 'aha',
327+
type: 'Error',
328+
stacktrace: {
329+
frames: [
330+
{
331+
colno: 126,
332+
filename: 'http://localhost:3002/_next/static/chunks/app/(group)/[route]/script.js',
333+
function: '?',
334+
in_app: true,
335+
lineno: 1,
336+
},
337+
{
338+
colno: 644,
339+
filename: 'http://localhost:3002/_next/static/chunks/app/(group)/[route]/script.js',
340+
function: 'onClick',
341+
in_app: true,
342+
lineno: 1,
343+
},
344+
],
345+
},
346+
});
347+
});
348+
314349
it('should parse Firefox errors with `file` inside an identifier', () => {
315350
const FIREFOX_FILE_IN_IDENTIFIER = {
316351
stack:

packages/browser/test/unit/tracekit/safari.test.ts

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -320,4 +320,39 @@ describe('Tracekit - Safari Tests', () => {
320320
},
321321
});
322322
});
323+
324+
it('should correctly parse parentheses', () => {
325+
const PARENTHESIS_FRAME_EXCEPTION = {
326+
message: 'aha',
327+
name: 'Error',
328+
stack:
329+
'@http://localhost:3000/(group)/[route]/script.js:1:131\n' +
330+
'global code@http://localhost:3000/(group)/[route]/script.js:1:334',
331+
};
332+
333+
const ex = exceptionFromError(parser, PARENTHESIS_FRAME_EXCEPTION);
334+
335+
expect(ex).toEqual({
336+
value: 'aha',
337+
type: 'Error',
338+
stacktrace: {
339+
frames: [
340+
{
341+
colno: 334,
342+
filename: 'http://localhost:3000/(group)/[route]/script.js',
343+
function: 'global code',
344+
in_app: true,
345+
lineno: 1,
346+
},
347+
{
348+
colno: 131,
349+
filename: 'http://localhost:3000/(group)/[route]/script.js',
350+
function: '?',
351+
in_app: true,
352+
lineno: 1,
353+
},
354+
],
355+
},
356+
});
357+
});
323358
});

packages/core/src/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -95,6 +95,7 @@ export { extraErrorDataIntegration } from './integrations/extraerrordata';
9595
export { rewriteFramesIntegration } from './integrations/rewriteframes';
9696
export { sessionTimingIntegration } from './integrations/sessiontiming';
9797
export { zodErrorsIntegration } from './integrations/zoderrors';
98+
export { thirdPartyErrorFilterIntegration } from './integrations/third-party-errors-filter';
9899
export { metrics } from './metrics/exports';
99100
export type { MetricData } from '@sentry/types';
100101
export { metricsDefault } from './metrics/exports-default';

packages/core/src/integrations/dedupe.ts

Lines changed: 3 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import type { Event, Exception, IntegrationFn, StackFrame } from '@sentry/types';
2-
import { logger } from '@sentry/utils';
2+
import { getFramesFromEvent, logger } from '@sentry/utils';
33
import { defineIntegration } from '../integration';
44

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

108108
function _isSameStacktrace(currentEvent: Event, previousEvent: Event): boolean {
109-
let currentFrames = _getFramesFromEvent(currentEvent);
110-
let previousFrames = _getFramesFromEvent(previousEvent);
109+
let currentFrames = getFramesFromEvent(currentEvent);
110+
let previousFrames = getFramesFromEvent(previousEvent);
111111

112112
// If neither event has a stacktrace, they are assumed to be the same
113113
if (!currentFrames && !previousFrames) {
@@ -173,17 +173,3 @@ function _isSameFingerprint(currentEvent: Event, previousEvent: Event): boolean
173173
function _getExceptionFromEvent(event: Event): Exception | undefined {
174174
return event.exception && event.exception.values && event.exception.values[0];
175175
}
176-
177-
function _getFramesFromEvent(event: Event): StackFrame[] | undefined {
178-
const exception = event.exception;
179-
180-
if (exception) {
181-
try {
182-
// @ts-expect-error Object could be undefined
183-
return exception.values[0].stacktrace.frames;
184-
} catch (_oO) {
185-
return undefined;
186-
}
187-
}
188-
return undefined;
189-
}
Lines changed: 114 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,114 @@
1+
import type { Event, EventItem } from '@sentry/types';
2+
import { forEachEnvelopeItem, getFramesFromEvent } from '@sentry/utils';
3+
import { defineIntegration } from '../integration';
4+
import { addMetadataToStackFrames, stripMetadataFromStackFrames } from '../metadata';
5+
6+
interface Options {
7+
/**
8+
* Keys that have been provided in the Sentry bundler plugin via the the `applicationKey` option, identifying your bundles.
9+
*
10+
* - Webpack plugin: https://www.npmjs.com/package/@sentry/webpack-plugin#applicationkey
11+
* - Vite plugin: https://www.npmjs.com/package/@sentry/vite-plugin#applicationkey
12+
* - Esbuild plugin: https://www.npmjs.com/package/@sentry/esbuild-plugin#applicationkey
13+
* - Rollup plugin: https://www.npmjs.com/package/@sentry/rollup-plugin#applicationkey
14+
*/
15+
filterKeys: string[];
16+
17+
/**
18+
* 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.
19+
*
20+
* You can define the behaviour with one of 4 modes:
21+
* - `drop-error-if-contains-third-party-frames`: Drop error events that contain at least one third-party stack frame.
22+
* - `drop-error-if-exclusively-contains-third-party-frames`: Drop error events that exclusively contain third-party stack frames.
23+
* - `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.
24+
* - `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.
25+
*
26+
* 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.
27+
*/
28+
behaviour:
29+
| 'drop-error-if-contains-third-party-frames'
30+
| 'drop-error-if-exclusively-contains-third-party-frames'
31+
| 'apply-tag-if-contains-third-party-frames'
32+
| 'apply-tag-if-exclusively-contains-third-party-frames';
33+
}
34+
35+
/**
36+
* 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.
37+
*/
38+
export const thirdPartyErrorFilterIntegration = defineIntegration((options: Options) => {
39+
return {
40+
name: 'ThirdPartyErrorsFilter',
41+
setup(client) {
42+
// We need to strip metadata from stack frames before sending them to Sentry since these are client side only.
43+
// TODO(lforst): Move this cleanup logic into a more central place in the SDK.
44+
client.on('beforeEnvelope', envelope => {
45+
forEachEnvelopeItem(envelope, (item, type) => {
46+
if (type === 'event') {
47+
const event = Array.isArray(item) ? (item as EventItem)[1] : undefined;
48+
49+
if (event) {
50+
stripMetadataFromStackFrames(event);
51+
item[1] = event;
52+
}
53+
}
54+
});
55+
});
56+
},
57+
processEvent(event, _hint, client) {
58+
const stackParser = client.getOptions().stackParser;
59+
addMetadataToStackFrames(stackParser, event);
60+
61+
const frameKeys = getBundleKeysForAllFramesWithFilenames(event);
62+
63+
if (frameKeys) {
64+
const arrayMethod =
65+
options.behaviour === 'drop-error-if-contains-third-party-frames' ||
66+
options.behaviour === 'apply-tag-if-contains-third-party-frames'
67+
? 'some'
68+
: 'every';
69+
70+
const behaviourApplies = frameKeys[arrayMethod](keys => !keys.some(key => options.filterKeys.includes(key)));
71+
72+
if (behaviourApplies) {
73+
const shouldDrop =
74+
options.behaviour === 'drop-error-if-contains-third-party-frames' ||
75+
options.behaviour === 'drop-error-if-exclusively-contains-third-party-frames';
76+
if (shouldDrop) {
77+
return null;
78+
} else {
79+
event.tags = {
80+
...event.tags,
81+
third_party_code: true,
82+
};
83+
}
84+
}
85+
}
86+
87+
return event;
88+
},
89+
};
90+
});
91+
92+
function getBundleKeysForAllFramesWithFilenames(event: Event): string[][] | undefined {
93+
const frames = getFramesFromEvent(event);
94+
95+
if (!frames) {
96+
return undefined;
97+
}
98+
99+
return (
100+
frames
101+
// Exclude frames without a filename since these are likely native code or built-ins
102+
.filter(frame => !!frame.filename)
103+
.map(frame => {
104+
if (frame.module_metadata) {
105+
return Object.keys(frame.module_metadata)
106+
.filter(key => key.startsWith(BUNDLER_PLUGIN_APP_KEY_PREFIX))
107+
.map(key => key.slice(BUNDLER_PLUGIN_APP_KEY_PREFIX.length));
108+
}
109+
return [];
110+
})
111+
);
112+
}
113+
114+
const BUNDLER_PLUGIN_APP_KEY_PREFIX = '_sentryBundlerPluginAppKey:';

packages/core/src/metadata.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -60,7 +60,7 @@ export function addMetadataToStackFrames(parser: StackParser, event: Event): voi
6060
}
6161

6262
for (const frame of exception.stacktrace.frames || []) {
63-
if (!frame.filename) {
63+
if (!frame.filename || frame.module_metadata) {
6464
continue;
6565
}
6666

0 commit comments

Comments
 (0)