Skip to content

Commit a5ea680

Browse files
author
Luca Forstner
authored
fix: Apply stack frame metadata before event processors (#12799)
1 parent 580e6a4 commit a5ea680

File tree

14 files changed

+232
-36
lines changed

14 files changed

+232
-36
lines changed
Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
import * as Sentry from '@sentry/browser';
2+
3+
Sentry.init({
4+
dsn: 'https://[email protected]/1337',
5+
integrations: [Sentry.moduleMetadataIntegration()],
6+
beforeSend(event) {
7+
const moduleMetadataEntries = [];
8+
9+
if (event.type === undefined) {
10+
try {
11+
event.exception.values.forEach(value => {
12+
value.stacktrace.frames.forEach(frame => {
13+
moduleMetadataEntries.push(frame.module_metadata);
14+
});
15+
});
16+
} catch (e) {
17+
// noop
18+
}
19+
}
20+
21+
event.extra = {
22+
...event.extra,
23+
module_metadata_entries: moduleMetadataEntries,
24+
};
25+
26+
return event;
27+
},
28+
});
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
var _sentryModuleMetadataGlobal =
2+
typeof window !== 'undefined'
3+
? window
4+
: typeof global !== 'undefined'
5+
? global
6+
: typeof self !== 'undefined'
7+
? self
8+
: {};
9+
10+
_sentryModuleMetadataGlobal._sentryModuleMetadata = _sentryModuleMetadataGlobal._sentryModuleMetadata || {};
11+
12+
_sentryModuleMetadataGlobal._sentryModuleMetadata[new Error().stack] = Object.assign(
13+
{},
14+
_sentryModuleMetadataGlobal._sentryModuleMetadata[new Error().stack],
15+
{
16+
foo: 'bar',
17+
},
18+
);
19+
20+
setTimeout(() => {
21+
throw new Error('I am a module metadata Error');
22+
}, 0);
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
import { expect } from '@playwright/test';
2+
import type { Event } from '@sentry/types';
3+
4+
import { sentryTest } from '../../../../utils/fixtures';
5+
import { getFirstSentryEnvelopeRequest } from '../../../../utils/helpers';
6+
7+
sentryTest('should provide module_metadata on stack frames in beforeSend', async ({ getLocalTestPath, page }) => {
8+
// moduleMetadataIntegration is not included in any CDN bundles
9+
if (process.env.PW_BUNDLE?.startsWith('bundle')) {
10+
sentryTest.skip();
11+
}
12+
13+
const url = await getLocalTestPath({ testDir: __dirname });
14+
15+
const errorEvent = await getFirstSentryEnvelopeRequest<Event>(page, url);
16+
expect(errorEvent.extra?.['module_metadata_entries']).toEqual([{ foo: 'bar' }]);
17+
});
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
import * as Sentry from '@sentry/browser';
2+
3+
Sentry.init({
4+
dsn: 'https://[email protected]/1337',
5+
integrations: [
6+
Sentry.moduleMetadataIntegration(),
7+
Sentry.rewriteFramesIntegration({
8+
iteratee: frame => {
9+
return {
10+
...frame,
11+
filename: 'bloop', // something that should completely mess with module metadata association
12+
};
13+
},
14+
}),
15+
],
16+
beforeSend(event) {
17+
const moduleMetadataEntries = [];
18+
19+
if (event.type === undefined) {
20+
try {
21+
event.exception.values.forEach(value => {
22+
value.stacktrace.frames.forEach(frame => {
23+
moduleMetadataEntries.push(frame.module_metadata);
24+
});
25+
});
26+
} catch (e) {
27+
// noop
28+
}
29+
}
30+
31+
event.extra = {
32+
...event.extra,
33+
module_metadata_entries: moduleMetadataEntries,
34+
};
35+
36+
return event;
37+
},
38+
});
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
var _sentryModuleMetadataGlobal =
2+
typeof window !== 'undefined'
3+
? window
4+
: typeof global !== 'undefined'
5+
? global
6+
: typeof self !== 'undefined'
7+
? self
8+
: {};
9+
10+
_sentryModuleMetadataGlobal._sentryModuleMetadata = _sentryModuleMetadataGlobal._sentryModuleMetadata || {};
11+
12+
_sentryModuleMetadataGlobal._sentryModuleMetadata[new Error().stack] = Object.assign(
13+
{},
14+
_sentryModuleMetadataGlobal._sentryModuleMetadata[new Error().stack],
15+
{
16+
foo: 'baz',
17+
},
18+
);
19+
20+
setTimeout(() => {
21+
throw new Error('I am a module metadata Error');
22+
}, 0);
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
import { expect } from '@playwright/test';
2+
import type { Event } from '@sentry/types';
3+
4+
import { sentryTest } from '../../../../utils/fixtures';
5+
import { getFirstSentryEnvelopeRequest } from '../../../../utils/helpers';
6+
7+
sentryTest(
8+
'should provide module_metadata on stack frames in beforeSend even though an event processor (rewriteFramesIntegration) modified the filename',
9+
async ({ getLocalTestPath, page }) => {
10+
// moduleMetadataIntegration is not included in any CDN bundles
11+
if (process.env.PW_BUNDLE?.startsWith('bundle')) {
12+
sentryTest.skip();
13+
}
14+
15+
const url = await getLocalTestPath({ testDir: __dirname });
16+
17+
const errorEvent = await getFirstSentryEnvelopeRequest<Event>(page, url);
18+
expect(errorEvent?.extra?.['module_metadata_entries']).toEqual([{ foo: 'baz' }]);
19+
},
20+
);

packages/core/src/baseclient.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -455,6 +455,8 @@ export abstract class BaseClient<O extends ClientOptions> implements Client<O> {
455455

456456
public on(hook: 'close', callback: () => void): () => void;
457457

458+
public on(hook: 'applyFrameMetadata', callback: (event: Event) => void): () => void;
459+
458460
/** @inheritdoc */
459461
public on(hook: string, callback: unknown): () => void {
460462
// Note that the code below, with nullish coalescing assignment,
@@ -541,6 +543,9 @@ export abstract class BaseClient<O extends ClientOptions> implements Client<O> {
541543
/** @inheritdoc */
542544
public emit(hook: 'close'): void;
543545

546+
/** @inheritdoc */
547+
public emit(hook: 'applyFrameMetadata', event: Event): void;
548+
544549
/** @inheritdoc */
545550
public emit(hook: string, ...rest: unknown[]): void {
546551
const callbacks = this._hooks[hook];
Lines changed: 22 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,21 @@
1-
import type { EventItem, IntegrationFn } from '@sentry/types';
1+
import type { EventItem } from '@sentry/types';
22
import { forEachEnvelopeItem } from '@sentry/utils';
33
import { defineIntegration } from '../integration';
44

55
import { addMetadataToStackFrames, stripMetadataFromStackFrames } from '../metadata';
66

7-
const INTEGRATION_NAME = 'ModuleMetadata';
8-
9-
const _moduleMetadataIntegration = (() => {
7+
/**
8+
* Adds module metadata to stack frames.
9+
*
10+
* Metadata can be injected by the Sentry bundler plugins using the `moduleMetadata` config option.
11+
*
12+
* When this integration is added, the metadata passed to the bundler plugin is added to the stack frames of all events
13+
* under the `module_metadata` property. This can be used to help in tagging or routing of events from different teams
14+
* our sources
15+
*/
16+
export const moduleMetadataIntegration = defineIntegration(() => {
1017
return {
11-
name: INTEGRATION_NAME,
18+
name: 'ModuleMetadata',
1219
setup(client) {
1320
// We need to strip metadata from stack frames before sending them to Sentry since these are client side only.
1421
client.on('beforeEnvelope', envelope => {
@@ -23,23 +30,16 @@ const _moduleMetadataIntegration = (() => {
2330
}
2431
});
2532
});
26-
},
2733

28-
processEvent(event, _hint, client) {
29-
const stackParser = client.getOptions().stackParser;
30-
addMetadataToStackFrames(stackParser, event);
31-
return event;
34+
client.on('applyFrameMetadata', event => {
35+
// Only apply stack frame metadata to error events
36+
if (event.type !== undefined) {
37+
return;
38+
}
39+
40+
const stackParser = client.getOptions().stackParser;
41+
addMetadataToStackFrames(stackParser, event);
42+
});
3243
},
3344
};
34-
}) satisfies IntegrationFn;
35-
36-
/**
37-
* Adds module metadata to stack frames.
38-
*
39-
* Metadata can be injected by the Sentry bundler plugins using the `_experiments.moduleMetadata` config option.
40-
*
41-
* When this integration is added, the metadata passed to the bundler plugin is added to the stack frames of all events
42-
* under the `module_metadata` property. This can be used to help in tagging or routing of events from different teams
43-
* our sources
44-
*/
45-
export const moduleMetadataIntegration = defineIntegration(_moduleMetadataIntegration);
45+
});

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

Lines changed: 11 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -53,11 +53,19 @@ export const thirdPartyErrorFilterIntegration = defineIntegration((options: Opti
5353
}
5454
});
5555
});
56+
57+
client.on('applyFrameMetadata', event => {
58+
// Only apply stack frame metadata to error events
59+
if (event.type !== undefined) {
60+
return;
61+
}
62+
63+
const stackParser = client.getOptions().stackParser;
64+
addMetadataToStackFrames(stackParser, event);
65+
});
5666
},
57-
processEvent(event, _hint, client) {
58-
const stackParser = client.getOptions().stackParser;
59-
addMetadataToStackFrames(stackParser, event);
6067

68+
processEvent(event) {
6169
const frameKeys = getBundleKeysForAllFramesWithFilenames(event);
6270

6371
if (frameKeys) {

packages/core/src/utils/prepareEvent.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -60,6 +60,10 @@ export function prepareEvent(
6060
applyClientOptions(prepared, options);
6161
applyIntegrationsMetadata(prepared, integrations);
6262

63+
if (client) {
64+
client.emit('applyFrameMetadata', event);
65+
}
66+
6367
// Only put debug IDs onto frames for error events.
6468
if (event.type === undefined) {
6569
applyDebugIds(prepared, options.stackParser);

packages/core/test/lib/integrations/third-party-errors-filter.test.ts

Lines changed: 7 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,14 @@
11
import type { Client, Event } from '@sentry/types';
22
import { GLOBAL_OBJ, createStackParser, nodeStackLineParser } from '@sentry/utils';
33
import { thirdPartyErrorFilterIntegration } from '../../../src/integrations/third-party-errors-filter';
4+
import { addMetadataToStackFrames } from '../../../src/metadata';
45

56
function clone<T>(data: T): T {
67
return JSON.parse(JSON.stringify(data));
78
}
89

910
const stack = new Error().stack || '';
11+
const stackParser = createStackParser(nodeStackLineParser());
1012

1113
const eventWithThirdAndFirstPartyFrames: Event = {
1214
exception: {
@@ -90,11 +92,7 @@ const eventWithOnlyThirdPartyFrames: Event = {
9092
};
9193

9294
// This only needs the stackParser
93-
const MOCK_CLIENT = {
94-
getOptions: () => ({
95-
stackParser: createStackParser(nodeStackLineParser()),
96-
}),
97-
} as unknown as Client;
95+
const MOCK_CLIENT = {} as unknown as Client;
9896

9997
describe('ThirdPartyErrorFilter', () => {
10098
beforeEach(() => {
@@ -103,6 +101,10 @@ describe('ThirdPartyErrorFilter', () => {
103101
'_sentryBundlerPluginAppKey:some-key': true,
104102
'_sentryBundlerPluginAppKey:some-other-key': true,
105103
};
104+
105+
addMetadataToStackFrames(stackParser, eventWithThirdAndFirstPartyFrames);
106+
addMetadataToStackFrames(stackParser, eventWithOnlyFirstPartyFrames);
107+
addMetadataToStackFrames(stackParser, eventWithOnlyThirdPartyFrames);
106108
});
107109

108110
describe('drop-error-if-contains-third-party-frames', () => {

packages/core/test/lib/prepareEvent.test.ts

Lines changed: 16 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -205,10 +205,13 @@ describe('prepareEvent', () => {
205205

206206
const options = {} as ClientOptions;
207207
const client = {
208+
emit() {
209+
// noop
210+
},
208211
getEventProcessors() {
209212
return [eventProcessor];
210213
},
211-
} as Client;
214+
} as unknown as Client;
212215
const processedEvent = await prepareEvent(
213216
options,
214217
event,
@@ -393,10 +396,13 @@ describe('prepareEvent', () => {
393396

394397
const options = {} as ClientOptions;
395398
const client = {
399+
emit() {
400+
// noop
401+
},
396402
getEventProcessors() {
397403
return [] as EventProcessor[];
398404
},
399-
} as Client;
405+
} as unknown as Client;
400406

401407
const processedEvent = await prepareEvent(
402408
options,
@@ -430,10 +436,13 @@ describe('prepareEvent', () => {
430436

431437
const options = {} as ClientOptions;
432438
const client = {
439+
emit() {
440+
// noop
441+
},
433442
getEventProcessors() {
434443
return [] as EventProcessor[];
435444
},
436-
} as Client;
445+
} as unknown as Client;
437446

438447
const captureContext = new Scope();
439448
captureContext.setTags({ foo: 'bar' });
@@ -470,10 +479,13 @@ describe('prepareEvent', () => {
470479

471480
const options = {} as ClientOptions;
472481
const client = {
482+
emit() {
483+
// noop
484+
},
473485
getEventProcessors() {
474486
return [] as EventProcessor[];
475487
},
476-
} as Client;
488+
} as unknown as Client;
477489

478490
const captureContextScope = new Scope();
479491
captureContextScope.setTags({ foo: 'bar' });

packages/node/test/sdk/scope.test.ts

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -88,7 +88,11 @@ describe('Unit | Scope', () => {
8888
it('allows to set & get a client', () => {
8989
const scope = new Scope();
9090
expect(scope.getClient()).toBeUndefined();
91-
const client = {} as Client;
91+
const client = {
92+
emit() {
93+
// noop
94+
},
95+
} as unknown as Client;
9296
scope.setClient(client);
9397
expect(scope.getClient()).toBe(client);
9498
});
@@ -108,7 +112,10 @@ describe('Unit | Scope', () => {
108112
getEventProcessors() {
109113
return [eventProcessor];
110114
},
111-
} as Client;
115+
emit() {
116+
// noop
117+
},
118+
} as unknown as Client;
112119
const processedEvent = await prepareEvent(
113120
options,
114121
event,

0 commit comments

Comments
 (0)