Skip to content

Commit f9d3139

Browse files
authored
ref(nextjs): UseRequestData integration for errors (#5729)
In #5703, a new integration, `RequestData`, was added to take the place of the request-specific event processors we've been using to add request data to transaction events in the nextjs SDK. This builds on that work by making the same switch for error events. Notes: - Because of #5718, it's hard to reason about the potential side effects of making major changes to the logic in `@sentry/utils/requestData`. Therefore, the majority of the new logic in this PR has been added to the integration, and just overwrites the `transaction` value added by the functions in `requestData`. Once we've cleaned up the request data code, we can consolidate the logic.
1 parent 874b09a commit f9d3139

File tree

5 files changed

+59
-38
lines changed

5 files changed

+59
-38
lines changed

packages/nextjs/src/config/wrappers/wrapperUtils.ts

+1-13
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,4 @@
11
import { captureException, getCurrentHub, startTransaction } from '@sentry/core';
2-
import { addRequestDataToEvent } from '@sentry/node';
32
import { getActiveTransaction } from '@sentry/tracing';
43
import { Transaction } from '@sentry/types';
54
import { baggageHeaderToDynamicSamplingContext, extractTraceparentData, fill } from '@sentry/utils';
@@ -123,18 +122,7 @@ export function callTracedServerSideDataFetcher<F extends (...args: any[]) => Pr
123122
const currentScope = getCurrentHub().getScope();
124123
if (currentScope) {
125124
currentScope.setSpan(dataFetcherSpan);
126-
currentScope.addEventProcessor(event =>
127-
event.type !== 'transaction'
128-
? addRequestDataToEvent(event, req, {
129-
include: {
130-
// When the `transaction` option is set to true, it tries to extract a transaction name from the request
131-
// object. We don't want this since we already have a high-quality transaction name with a parameterized
132-
// route. Setting `transaction` to `true` will clobber that transaction name.
133-
transaction: false,
134-
},
135-
})
136-
: event,
137-
);
125+
currentScope.setSDKProcessingMetadata({ request: req });
138126
}
139127

140128
try {

packages/nextjs/src/utils/_error.ts

+2-2
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import { captureException, withScope } from '@sentry/core';
22
import { getCurrentHub } from '@sentry/hub';
3-
import { addExceptionMechanism, addRequestDataToEvent } from '@sentry/utils';
3+
import { addExceptionMechanism } from '@sentry/utils';
44
import { NextPageContext } from 'next';
55

66
type ContextOrProps = {
@@ -55,7 +55,7 @@ export async function captureUnderscoreErrorException(contextOrProps: ContextOrP
5555
});
5656

5757
if (req) {
58-
scope.addEventProcessor(event => addRequestDataToEvent(event, req));
58+
scope.setSDKProcessingMetadata({ request: req });
5959
}
6060

6161
// If third-party libraries (or users themselves) throw something falsy, we want to capture it as a message (which

packages/nextjs/src/utils/instrumentServer.ts

+3-15
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,5 @@
11
/* eslint-disable max-lines */
2-
import {
3-
addRequestDataToEvent,
4-
captureException,
5-
configureScope,
6-
deepReadDirSync,
7-
getCurrentHub,
8-
startTransaction,
9-
} from '@sentry/node';
2+
import { captureException, configureScope, deepReadDirSync, getCurrentHub, startTransaction } from '@sentry/node';
103
import { extractTraceparentData, getActiveTransaction, hasTracingEnabled } from '@sentry/tracing';
114
import {
125
addExceptionMechanism,
@@ -244,9 +237,8 @@ function makeWrappedReqHandler(origReqHandler: ReqHandler): WrappedReqHandler {
244237
const currentScope = getCurrentHub().getScope();
245238

246239
if (currentScope) {
247-
currentScope.addEventProcessor(event =>
248-
event.type !== 'transaction' ? addRequestDataToEvent(event, nextReq) : event,
249-
);
240+
// Store the request on the scope so we can pull data from it and add it to the event
241+
currentScope.setSDKProcessingMetadata({ request: req });
250242

251243
// We only want to record page and API requests
252244
if (hasTracingEnabled() && shouldTraceRequest(nextReq.url, publicDirFiles)) {
@@ -297,10 +289,6 @@ function makeWrappedReqHandler(origReqHandler: ReqHandler): WrappedReqHandler {
297289
if (transaction) {
298290
transaction.setHttpStatus(res.statusCode);
299291

300-
// we'll collect this data in a more targeted way in the event processor we added above,
301-
// `addRequestDataToEvent`
302-
delete transaction.metadata.requestPath;
303-
304292
// Push `transaction.finish` to the next event loop so open spans have a chance to finish before the
305293
// transaction closes
306294
setImmediate(() => {

packages/nextjs/src/utils/withSentry.ts

+2-4
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { addRequestDataToEvent, captureException, flush, getCurrentHub, startTransaction } from '@sentry/node';
1+
import { captureException, flush, getCurrentHub, startTransaction } from '@sentry/node';
22
import { extractTraceparentData, hasTracingEnabled } from '@sentry/tracing';
33
import { Transaction } from '@sentry/types';
44
import {
@@ -56,9 +56,7 @@ export const withSentry = (origHandler: NextApiHandler): WrappedNextApiHandler =
5656
const currentScope = getCurrentHub().getScope();
5757

5858
if (currentScope) {
59-
currentScope.addEventProcessor(event =>
60-
event.type !== 'transaction' ? addRequestDataToEvent(event, req) : event,
61-
);
59+
currentScope.setSDKProcessingMetadata({ request: req });
6260

6361
if (hasTracingEnabled()) {
6462
// If there is a trace header set, extract the data from it (parentSpanId, traceId, and sampling decision)

packages/node/src/integrations/requestdata.ts

+51-4
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,8 @@
11
// TODO (v8 or v9): Whenever this becomes a default integration for `@sentry/browser`, move this to `@sentry/core`. For
22
// now, we leave it in `@sentry/integrations` so that it doesn't contribute bytes to our CDN bundles.
33

4-
import { EventProcessor, Hub, Integration } from '@sentry/types';
4+
import { EventProcessor, Hub, Integration, Transaction } from '@sentry/types';
5+
import { extractPathForTransaction } from '@sentry/utils';
56

67
import {
78
addRequestDataToEvent,
@@ -86,10 +87,15 @@ export class RequestData implements Integration {
8687
* @inheritDoc
8788
*/
8889
public setupOnce(addGlobalEventProcessor: (eventProcessor: EventProcessor) => void, getCurrentHub: () => Hub): void {
89-
const { include, addRequestData } = this._options;
90+
// Note: In the long run, most of the logic here should probably move into the request data utility functions. For
91+
// the moment it lives here, though, until https://github.com/getsentry/sentry-javascript/issues/5718 is addressed.
92+
// (TL;DR: Those functions touch many parts of the repo in many different ways, and need to be clened up. Once
93+
// that's happened, it will be easier to add this logic in without worrying about unexpected side effects.)
94+
const { include, addRequestData, transactionNamingScheme } = this._options;
9095

9196
addGlobalEventProcessor(event => {
92-
const self = getCurrentHub().getIntegration(RequestData);
97+
const hub = getCurrentHub();
98+
const self = hub.getIntegration(RequestData);
9399
const req = event.sdkProcessingMetadata && event.sdkProcessingMetadata.request;
94100

95101
// If the globally installed instance of this integration isn't associated with the current hub, `self` will be
@@ -98,7 +104,36 @@ export class RequestData implements Integration {
98104
return event;
99105
}
100106

101-
return addRequestData(event, req, { include: formatIncludeOption(include) });
107+
const processedEvent = addRequestData(event, req, { include: formatIncludeOption(include) });
108+
109+
// Transaction events already have the right `transaction` value
110+
if (event.type === 'transaction' || transactionNamingScheme === 'handler') {
111+
return processedEvent;
112+
}
113+
114+
// In all other cases, use the request's associated transaction (if any) to overwrite the event's `transaction`
115+
// value with a high-quality one
116+
const reqWithTransaction = req as { _sentryTransaction?: Transaction };
117+
const transaction = reqWithTransaction._sentryTransaction;
118+
if (transaction) {
119+
// TODO (v8): Remove the nextjs check and just base it on `transactionNamingScheme` for all SDKs. (We have to
120+
// keep it the way it is for the moment, because changing the names of transactions in Sentry has the potential
121+
// to break things like alert rules.)
122+
const shouldIncludeMethodInTransactionName =
123+
getSDKName(hub) === 'sentry.javascript.nextjs'
124+
? transaction.name.startsWith('/api')
125+
: transactionNamingScheme !== 'path';
126+
127+
const [transactionValue] = extractPathForTransaction(req, {
128+
path: true,
129+
method: shouldIncludeMethodInTransactionName,
130+
customRoute: transaction.name,
131+
});
132+
133+
processedEvent.transaction = transactionValue;
134+
}
135+
136+
return processedEvent;
102137
});
103138
}
104139
}
@@ -123,3 +158,15 @@ function formatIncludeOption(
123158
request: requestIncludeKeys.length !== 0 ? requestIncludeKeys : undefined,
124159
};
125160
}
161+
162+
function getSDKName(hub: Hub): string | undefined {
163+
try {
164+
// For a long chain like this, it's fewer bytes to combine a try-catch with assuming everything is there than to
165+
// write out a long chain of `a && a.b && a.b.c && ...`
166+
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
167+
return hub.getClient()!.getOptions()!._metadata!.sdk!.name;
168+
} catch (err) {
169+
// In theory we should never get here
170+
return undefined;
171+
}
172+
}

0 commit comments

Comments
 (0)