Skip to content

Commit f3d54de

Browse files
authored
ref(core): Refactor core integrations to avoid setupOnce (#9813)
Where possible, we should use the new `processEvent` and/or `setup` hooks of the integrations, instead of `setupOnce`. This updates this for the core integrations.
1 parent 41c7782 commit f3d54de

File tree

4 files changed

+97
-91
lines changed

4 files changed

+97
-91
lines changed

packages/core/src/integrations/metadata.ts

Lines changed: 12 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import type { EventItem, EventProcessor, Hub, Integration } from '@sentry/types';
1+
import type { Client, Event, EventItem, EventProcessor, Hub, Integration } from '@sentry/types';
22
import { forEachEnvelopeItem } from '@sentry/utils';
33

44
import { addMetadataToStackFrames, stripMetadataFromStackFrames } from '../metadata';
@@ -30,10 +30,13 @@ export class ModuleMetadata implements Integration {
3030
/**
3131
* @inheritDoc
3232
*/
33-
public setupOnce(addGlobalEventProcessor: (processor: EventProcessor) => void, getCurrentHub: () => Hub): void {
34-
const client = getCurrentHub().getClient();
33+
public setupOnce(_addGlobalEventProcessor: (processor: EventProcessor) => void, _getCurrentHub: () => Hub): void {
34+
// noop
35+
}
3536

36-
if (!client || typeof client.on !== 'function') {
37+
/** @inheritDoc */
38+
public setup(client: Client): void {
39+
if (typeof client.on !== 'function') {
3740
return;
3841
}
3942

@@ -50,12 +53,12 @@ export class ModuleMetadata implements Integration {
5053
}
5154
});
5255
});
56+
}
5357

58+
/** @inheritDoc */
59+
public processEvent(event: Event, _hint: unknown, client: Client): Event {
5460
const stackParser = client.getOptions().stackParser;
55-
56-
addGlobalEventProcessor(event => {
57-
addMetadataToStackFrames(stackParser, event);
58-
return event;
59-
});
61+
addMetadataToStackFrames(stackParser, event);
62+
return event;
6063
}
6164
}

packages/core/src/integrations/requestdata.ts

Lines changed: 51 additions & 50 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import type { Event, EventProcessor, Hub, Integration, PolymorphicRequest, Transaction } from '@sentry/types';
1+
import type { Client, Event, EventProcessor, Hub, Integration, PolymorphicRequest, Transaction } from '@sentry/types';
22
import type { AddRequestDataToEventOptions, TransactionNamingScheme } from '@sentry/utils';
33
import { addRequestDataToEvent, extractPathForTransaction } from '@sentry/utils';
44

@@ -95,65 +95,66 @@ export class RequestData implements Integration {
9595
/**
9696
* @inheritDoc
9797
*/
98-
public setupOnce(addGlobalEventProcessor: (eventProcessor: EventProcessor) => void, getCurrentHub: () => Hub): void {
98+
public setupOnce(
99+
_addGlobalEventProcessor: (eventProcessor: EventProcessor) => void,
100+
_getCurrentHub: () => Hub,
101+
): void {
102+
// noop
103+
}
104+
105+
/** @inheritdoc */
106+
public processEvent(event: Event, _hint: unknown, client: Client): Event {
99107
// Note: In the long run, most of the logic here should probably move into the request data utility functions. For
100108
// the moment it lives here, though, until https://github.com/getsentry/sentry-javascript/issues/5718 is addressed.
101109
// (TL;DR: Those functions touch many parts of the repo in many different ways, and need to be clened up. Once
102110
// that's happened, it will be easier to add this logic in without worrying about unexpected side effects.)
103111
const { transactionNamingScheme } = this._options;
104112

105-
addGlobalEventProcessor(event => {
106-
const hub = getCurrentHub();
107-
const self = hub.getIntegration(RequestData);
108-
109-
const { sdkProcessingMetadata = {} } = event;
110-
const req = sdkProcessingMetadata.request;
113+
const { sdkProcessingMetadata = {} } = event;
114+
const req = sdkProcessingMetadata.request;
111115

112-
// If the globally installed instance of this integration isn't associated with the current hub, `self` will be
113-
// undefined
114-
if (!self || !req) {
115-
return event;
116-
}
116+
if (!req) {
117+
return event;
118+
}
117119

118-
// The Express request handler takes a similar `include` option to that which can be passed to this integration.
119-
// If passed there, we store it in `sdkProcessingMetadata`. TODO(v8): Force express and GCP people to use this
120-
// integration, so that all of this passing and conversion isn't necessary
121-
const addRequestDataOptions =
122-
sdkProcessingMetadata.requestDataOptionsFromExpressHandler ||
123-
sdkProcessingMetadata.requestDataOptionsFromGCPWrapper ||
124-
convertReqDataIntegrationOptsToAddReqDataOpts(this._options);
120+
// The Express request handler takes a similar `include` option to that which can be passed to this integration.
121+
// If passed there, we store it in `sdkProcessingMetadata`. TODO(v8): Force express and GCP people to use this
122+
// integration, so that all of this passing and conversion isn't necessary
123+
const addRequestDataOptions =
124+
sdkProcessingMetadata.requestDataOptionsFromExpressHandler ||
125+
sdkProcessingMetadata.requestDataOptionsFromGCPWrapper ||
126+
convertReqDataIntegrationOptsToAddReqDataOpts(this._options);
125127

126-
const processedEvent = this._addRequestData(event, req, addRequestDataOptions);
128+
const processedEvent = this._addRequestData(event, req, addRequestDataOptions);
127129

128-
// Transaction events already have the right `transaction` value
129-
if (event.type === 'transaction' || transactionNamingScheme === 'handler') {
130-
return processedEvent;
131-
}
130+
// Transaction events already have the right `transaction` value
131+
if (event.type === 'transaction' || transactionNamingScheme === 'handler') {
132+
return processedEvent;
133+
}
132134

133-
// In all other cases, use the request's associated transaction (if any) to overwrite the event's `transaction`
134-
// value with a high-quality one
135-
const reqWithTransaction = req as { _sentryTransaction?: Transaction };
136-
const transaction = reqWithTransaction._sentryTransaction;
137-
if (transaction) {
138-
// TODO (v8): Remove the nextjs check and just base it on `transactionNamingScheme` for all SDKs. (We have to
139-
// keep it the way it is for the moment, because changing the names of transactions in Sentry has the potential
140-
// to break things like alert rules.)
141-
const shouldIncludeMethodInTransactionName =
142-
getSDKName(hub) === 'sentry.javascript.nextjs'
143-
? transaction.name.startsWith('/api')
144-
: transactionNamingScheme !== 'path';
145-
146-
const [transactionValue] = extractPathForTransaction(req, {
147-
path: true,
148-
method: shouldIncludeMethodInTransactionName,
149-
customRoute: transaction.name,
150-
});
151-
152-
processedEvent.transaction = transactionValue;
153-
}
135+
// In all other cases, use the request's associated transaction (if any) to overwrite the event's `transaction`
136+
// value with a high-quality one
137+
const reqWithTransaction = req as { _sentryTransaction?: Transaction };
138+
const transaction = reqWithTransaction._sentryTransaction;
139+
if (transaction) {
140+
// TODO (v8): Remove the nextjs check and just base it on `transactionNamingScheme` for all SDKs. (We have to
141+
// keep it the way it is for the moment, because changing the names of transactions in Sentry has the potential
142+
// to break things like alert rules.)
143+
const shouldIncludeMethodInTransactionName =
144+
getSDKName(client) === 'sentry.javascript.nextjs'
145+
? transaction.name.startsWith('/api')
146+
: transactionNamingScheme !== 'path';
147+
148+
const [transactionValue] = extractPathForTransaction(req, {
149+
path: true,
150+
method: shouldIncludeMethodInTransactionName,
151+
customRoute: transaction.name,
152+
});
153+
154+
processedEvent.transaction = transactionValue;
155+
}
154156

155-
return processedEvent;
156-
});
157+
return processedEvent;
157158
}
158159
}
159160

@@ -199,12 +200,12 @@ function convertReqDataIntegrationOptsToAddReqDataOpts(
199200
};
200201
}
201202

202-
function getSDKName(hub: Hub): string | undefined {
203+
function getSDKName(client: Client): string | undefined {
203204
try {
204205
// For a long chain like this, it's fewer bytes to combine a try-catch with assuming everything is there than to
205206
// write out a long chain of `a && a.b && a.b.c && ...`
206207
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
207-
return hub.getClient()!.getOptions()!._metadata!.sdk!.name;
208+
return client.getOptions()._metadata!.sdk!.name;
208209
} catch (err) {
209210
// In theory we should never get here
210211
return undefined;

packages/core/test/lib/integrations/requestdata.test.ts

Lines changed: 21 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,12 @@
11
import type { IncomingMessage } from 'http';
22
import type { RequestDataIntegrationOptions } from '@sentry/core';
3-
import { Hub, RequestData, getCurrentHub, makeMain } from '@sentry/core';
3+
import { RequestData, getCurrentHub } from '@sentry/core';
44
import type { Event, EventProcessor } from '@sentry/types';
55
import * as sentryUtils from '@sentry/utils';
66

77
import { TestClient, getDefaultTestClientOptions } from '../../mocks/client';
88

99
const addRequestDataToEventSpy = jest.spyOn(sentryUtils, 'addRequestDataToEvent');
10-
const requestDataEventProcessor = jest.fn();
1110

1211
const headers = { ears: 'furry', nose: 'wet', tongue: 'spotted', cookie: 'favorite=zukes' };
1312
const method = 'wagging';
@@ -16,10 +15,7 @@ const hostname = 'the.dog.park';
1615
const path = '/by/the/trees/';
1716
const queryString = 'chase=me&please=thankyou';
1817

19-
function initWithRequestDataIntegrationOptions(integrationOptions: RequestDataIntegrationOptions): void {
20-
const setMockEventProcessor = (eventProcessor: EventProcessor) =>
21-
requestDataEventProcessor.mockImplementationOnce(eventProcessor);
22-
18+
function initWithRequestDataIntegrationOptions(integrationOptions: RequestDataIntegrationOptions): EventProcessor {
2319
const requestDataIntegration = new RequestData({
2420
...integrationOptions,
2521
});
@@ -30,12 +26,15 @@ function initWithRequestDataIntegrationOptions(integrationOptions: RequestDataIn
3026
integrations: [requestDataIntegration],
3127
}),
3228
);
33-
client.setupIntegrations = () => requestDataIntegration.setupOnce(setMockEventProcessor, getCurrentHub);
34-
client.getIntegration = () => requestDataIntegration as any;
3529

36-
const hub = new Hub(client);
30+
getCurrentHub().bindClient(client);
31+
32+
const eventProcessors = client['_eventProcessors'] as EventProcessor[];
33+
const eventProcessor = eventProcessors.find(processor => processor.id === 'RequestData');
34+
35+
expect(eventProcessor).toBeDefined();
3736

38-
makeMain(hub);
37+
return eventProcessor!;
3938
}
4039

4140
describe('`RequestData` integration', () => {
@@ -58,29 +57,31 @@ describe('`RequestData` integration', () => {
5857

5958
describe('option conversion', () => {
6059
it('leaves `ip` and `user` at top level of `include`', () => {
61-
initWithRequestDataIntegrationOptions({ include: { ip: false, user: true } });
60+
const requestDataEventProcessor = initWithRequestDataIntegrationOptions({ include: { ip: false, user: true } });
6261

63-
requestDataEventProcessor(event);
62+
void requestDataEventProcessor(event, {});
6463

6564
const passedOptions = addRequestDataToEventSpy.mock.calls[0][2];
6665

6766
expect(passedOptions?.include).toEqual(expect.objectContaining({ ip: false, user: true }));
6867
});
6968

7069
it('moves `transactionNamingScheme` to `transaction` include', () => {
71-
initWithRequestDataIntegrationOptions({ transactionNamingScheme: 'path' });
70+
const requestDataEventProcessor = initWithRequestDataIntegrationOptions({ transactionNamingScheme: 'path' });
7271

73-
requestDataEventProcessor(event);
72+
void requestDataEventProcessor(event, {});
7473

7574
const passedOptions = addRequestDataToEventSpy.mock.calls[0][2];
7675

7776
expect(passedOptions?.include).toEqual(expect.objectContaining({ transaction: 'path' }));
7877
});
7978

8079
it('moves `true` request keys into `request` include, but omits `false` ones', async () => {
81-
initWithRequestDataIntegrationOptions({ include: { data: true, cookies: false } });
80+
const requestDataEventProcessor = initWithRequestDataIntegrationOptions({
81+
include: { data: true, cookies: false },
82+
});
8283

83-
requestDataEventProcessor(event);
84+
void requestDataEventProcessor(event, {});
8485

8586
const passedOptions = addRequestDataToEventSpy.mock.calls[0][2];
8687

@@ -89,9 +90,11 @@ describe('`RequestData` integration', () => {
8990
});
9091

9192
it('moves `true` user keys into `user` include, but omits `false` ones', async () => {
92-
initWithRequestDataIntegrationOptions({ include: { user: { id: true, email: false } } });
93+
const requestDataEventProcessor = initWithRequestDataIntegrationOptions({
94+
include: { user: { id: true, email: false } },
95+
});
9396

94-
requestDataEventProcessor(event);
97+
void requestDataEventProcessor(event, {});
9598

9699
const passedOptions = addRequestDataToEventSpy.mock.calls[0][2];
97100

packages/node/test/integrations/requestdata.test.ts

Lines changed: 13 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import * as http from 'http';
22
import type { RequestDataIntegrationOptions } from '@sentry/core';
3-
import { Hub, RequestData, getCurrentHub, makeMain } from '@sentry/core';
3+
import { RequestData, getCurrentHub } from '@sentry/core';
44
import type { Event, EventProcessor, PolymorphicRequest } from '@sentry/types';
55
import * as sentryUtils from '@sentry/utils';
66

@@ -9,7 +9,6 @@ import { requestHandler } from '../../src/handlers';
99
import { getDefaultNodeClientOptions } from '../helper/node-client-options';
1010

1111
const addRequestDataToEventSpy = jest.spyOn(sentryUtils, 'addRequestDataToEvent');
12-
const requestDataEventProcessor = jest.fn();
1312

1413
const headers = { ears: 'furry', nose: 'wet', tongue: 'spotted', cookie: 'favorite=zukes' };
1514
const method = 'wagging';
@@ -18,10 +17,7 @@ const hostname = 'the.dog.park';
1817
const path = '/by/the/trees/';
1918
const queryString = 'chase=me&please=thankyou';
2019

21-
function initWithRequestDataIntegrationOptions(integrationOptions: RequestDataIntegrationOptions): void {
22-
const setMockEventProcessor = (eventProcessor: EventProcessor) =>
23-
requestDataEventProcessor.mockImplementationOnce(eventProcessor);
24-
20+
function initWithRequestDataIntegrationOptions(integrationOptions: RequestDataIntegrationOptions): EventProcessor {
2521
const requestDataIntegration = new RequestData({
2622
...integrationOptions,
2723
});
@@ -32,12 +28,15 @@ function initWithRequestDataIntegrationOptions(integrationOptions: RequestDataIn
3228
integrations: [requestDataIntegration],
3329
}),
3430
);
35-
client.setupIntegrations = () => requestDataIntegration.setupOnce(setMockEventProcessor, getCurrentHub);
36-
client.getIntegration = () => requestDataIntegration as any;
3731

38-
const hub = new Hub(client);
32+
getCurrentHub().bindClient(client);
33+
34+
const eventProcessors = client['_eventProcessors'] as EventProcessor[];
35+
const eventProcessor = eventProcessors.find(processor => processor.id === 'RequestData');
36+
37+
expect(eventProcessor).toBeDefined();
3938

40-
makeMain(hub);
39+
return eventProcessor!;
4140
}
4241

4342
describe('`RequestData` integration', () => {
@@ -64,12 +63,12 @@ describe('`RequestData` integration', () => {
6463
const res = new http.ServerResponse(req);
6564
const next = jest.fn();
6665

67-
initWithRequestDataIntegrationOptions({ transactionNamingScheme: 'path' });
66+
const requestDataEventProcessor = initWithRequestDataIntegrationOptions({ transactionNamingScheme: 'path' });
6867

6968
sentryRequestMiddleware(req, res, next);
7069

7170
await getCurrentHub().getScope()!.applyToEvent(event, {});
72-
requestDataEventProcessor(event);
71+
void requestDataEventProcessor(event, {});
7372

7473
const passedOptions = addRequestDataToEventSpy.mock.calls[0][2];
7574

@@ -93,12 +92,12 @@ describe('`RequestData` integration', () => {
9392
const wrappedGCPFunction = mockGCPWrapper(jest.fn(), { include: { transaction: 'methodPath' } });
9493
const res = new http.ServerResponse(req);
9594

96-
initWithRequestDataIntegrationOptions({ transactionNamingScheme: 'path' });
95+
const requestDataEventProcessor = initWithRequestDataIntegrationOptions({ transactionNamingScheme: 'path' });
9796

9897
wrappedGCPFunction(req, res);
9998

10099
await getCurrentHub().getScope()!.applyToEvent(event, {});
101-
requestDataEventProcessor(event);
100+
void requestDataEventProcessor(event, {});
102101

103102
const passedOptions = addRequestDataToEventSpy.mock.calls[0][2];
104103

0 commit comments

Comments
 (0)