Skip to content

Commit 300bba4

Browse files
authored
ref(nextjs): Remove internally used deprecated APIs (#10453)
1 parent baf5f90 commit 300bba4

12 files changed

+336
-426
lines changed

packages/nextjs/src/common/utils/commonObjectTracing.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,10 @@ const commonMap = new WeakMap<object, PropagationContext>();
44

55
/**
66
* Takes a shared (garbage collectable) object between resources, e.g. a headers object shared between Next.js server components and returns a common propagation context.
7+
*
8+
* @param commonObject The shared object.
9+
* @param propagationContext The propagation context that should be shared between all the resources if no propagation context was registered yet.
10+
* @returns the shared propagation context.
711
*/
812
export function commonObjectToPropagationContext(
913
commonObject: unknown,

packages/nextjs/src/common/utils/responseEnd.ts

Lines changed: 10 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,13 @@
11
import type { ServerResponse } from 'http';
22
import { flush, setHttpStatus } from '@sentry/core';
3-
import type { Transaction } from '@sentry/types';
3+
import type { Span } from '@sentry/types';
44
import { fill, logger } from '@sentry/utils';
55

66
import { DEBUG_BUILD } from '../debug-build';
77
import type { ResponseEndMethod, WrappedResponseEndMethod } from '../types';
88

99
/**
10-
* Wrap `res.end()` so that it closes the transaction and flushes events before letting the request finish.
10+
* Wrap `res.end()` so that it ends the span and flushes events before letting the request finish.
1111
*
1212
* Note: This wraps a sync method with an async method. While in general that's not a great idea in terms of keeping
1313
* things in the right order, in this case it's safe, because the native `.end()` actually *is* (effectively) async, and
@@ -20,13 +20,13 @@ import type { ResponseEndMethod, WrappedResponseEndMethod } from '../types';
2020
* `end` doesn't delay data getting to the end user. See
2121
* https://nodejs.org/api/http.html#responseenddata-encoding-callback.
2222
*
23-
* @param transaction The transaction tracing request handling
23+
* @param span The span tracking the request
2424
* @param res: The request's corresponding response
2525
*/
26-
export function autoEndTransactionOnResponseEnd(transaction: Transaction, res: ServerResponse): void {
26+
export function autoEndSpanOnResponseEnd(span: Span, res: ServerResponse): void {
2727
const wrapEndMethod = (origEnd: ResponseEndMethod): WrappedResponseEndMethod => {
2828
return function sentryWrappedEnd(this: ServerResponse, ...args: unknown[]) {
29-
finishTransaction(transaction, this);
29+
finishSpan(span, this);
3030
return origEnd.call(this, ...args);
3131
};
3232
};
@@ -38,11 +38,11 @@ export function autoEndTransactionOnResponseEnd(transaction: Transaction, res: S
3838
}
3939
}
4040

41-
/** Finish the given response's transaction and set HTTP status data */
42-
export function finishTransaction(transaction: Transaction | undefined, res: ServerResponse): void {
43-
if (transaction) {
44-
setHttpStatus(transaction, res.statusCode);
45-
transaction.end();
41+
/** Finish the given response's span and set HTTP status data */
42+
export function finishSpan(span: Span | undefined, res: ServerResponse): void {
43+
if (span) {
44+
setHttpStatus(span, res.statusCode);
45+
span.end();
4646
}
4747
}
4848

Lines changed: 93 additions & 135 deletions
Original file line numberDiff line numberDiff line change
@@ -1,38 +1,40 @@
11
import type { IncomingMessage, ServerResponse } from 'http';
22
import {
3+
SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN,
34
SEMANTIC_ATTRIBUTE_SENTRY_SOURCE,
45
captureException,
5-
getActiveSpan,
6-
getActiveTransaction,
7-
getCurrentScope,
8-
runWithAsyncContext,
9-
startTransaction,
6+
continueTrace,
7+
startInactiveSpan,
8+
startSpan,
9+
startSpanManual,
10+
withActiveSpan,
11+
withIsolationScope,
1012
} from '@sentry/core';
11-
import type { Span, Transaction } from '@sentry/types';
12-
import { isString, tracingContextFromHeaders } from '@sentry/utils';
13+
import type { Span } from '@sentry/types';
14+
import { isString } from '@sentry/utils';
1315

1416
import { platformSupportsStreaming } from './platformSupportsStreaming';
15-
import { autoEndTransactionOnResponseEnd, flushQueue } from './responseEnd';
17+
import { autoEndSpanOnResponseEnd, flushQueue } from './responseEnd';
1618

1719
declare module 'http' {
1820
interface IncomingMessage {
19-
_sentryTransaction?: Transaction;
21+
_sentrySpan?: Span;
2022
}
2123
}
2224

2325
/**
24-
* Grabs a transaction off a Next.js datafetcher request object, if it was previously put there via
25-
* `setTransactionOnRequest`.
26+
* Grabs a span off a Next.js datafetcher request object, if it was previously put there via
27+
* `setSpanOnRequest`.
2628
*
2729
* @param req The Next.js datafetcher request object
28-
* @returns the Transaction on the request object if there is one, or `undefined` if the request object didn't have one.
30+
* @returns the span on the request object if there is one, or `undefined` if the request object didn't have one.
2931
*/
30-
export function getTransactionFromRequest(req: IncomingMessage): Transaction | undefined {
31-
return req._sentryTransaction;
32+
export function getSpanFromRequest(req: IncomingMessage): Span | undefined {
33+
return req._sentrySpan;
3234
}
3335

34-
function setTransactionOnRequest(transaction: Transaction, req: IncomingMessage): void {
35-
req._sentryTransaction = transaction;
36+
function setSpanOnRequest(transaction: Span, req: IncomingMessage): void {
37+
req._sentrySpan = transaction;
3638
}
3739

3840
/**
@@ -85,99 +87,68 @@ export function withTracedServerSideDataFetcher<F extends (...args: any[]) => Pr
8587
},
8688
): (...params: Parameters<F>) => Promise<ReturnType<F>> {
8789
return async function (this: unknown, ...args: Parameters<F>): Promise<ReturnType<F>> {
88-
return runWithAsyncContext(async () => {
89-
const scope = getCurrentScope();
90-
const previousSpan: Span | undefined = getTransactionFromRequest(req) ?? getActiveSpan();
91-
let dataFetcherSpan;
90+
return withIsolationScope(async isolationScope => {
91+
isolationScope.setSDKProcessingMetadata({
92+
request: req,
93+
});
9294

9395
const sentryTrace =
9496
req.headers && isString(req.headers['sentry-trace']) ? req.headers['sentry-trace'] : undefined;
9597
const baggage = req.headers?.baggage;
96-
// eslint-disable-next-line deprecation/deprecation
97-
const { traceparentData, dynamicSamplingContext, propagationContext } = tracingContextFromHeaders(
98-
sentryTrace,
99-
baggage,
100-
);
101-
scope.setPropagationContext(propagationContext);
10298

103-
if (platformSupportsStreaming()) {
104-
let spanToContinue: Span;
105-
if (previousSpan === undefined) {
106-
// TODO: Refactor this to use `startSpan()`
107-
// eslint-disable-next-line deprecation/deprecation
108-
const newTransaction = startTransaction(
99+
return continueTrace({ sentryTrace, baggage }, () => {
100+
let requestSpan: Span | undefined = getSpanFromRequest(req);
101+
if (!requestSpan) {
102+
// TODO(v8): Simplify these checks when startInactiveSpan always returns a span
103+
requestSpan = startInactiveSpan({
104+
name: options.requestedRouteName,
105+
op: 'http.server',
106+
attributes: {
107+
[SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN]: 'auto.function.nextjs',
108+
[SEMANTIC_ATTRIBUTE_SENTRY_SOURCE]: 'route',
109+
},
110+
});
111+
if (requestSpan) {
112+
requestSpan.setStatus('ok');
113+
setSpanOnRequest(requestSpan, req);
114+
autoEndSpanOnResponseEnd(requestSpan, res);
115+
}
116+
}
117+
118+
const withActiveSpanCallback = (): Promise<ReturnType<F>> => {
119+
return startSpanManual(
109120
{
110-
op: 'http.server',
111-
name: options.requestedRouteName,
112-
origin: 'auto.function.nextjs',
113-
...traceparentData,
114-
status: 'ok',
115-
metadata: {
116-
request: req,
117-
source: 'route',
118-
dynamicSamplingContext: traceparentData && !dynamicSamplingContext ? {} : dynamicSamplingContext,
121+
op: 'function.nextjs',
122+
name: `${options.dataFetchingMethodName} (${options.dataFetcherRouteName})`,
123+
attributes: {
124+
[SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN]: 'auto.function.nextjs',
125+
[SEMANTIC_ATTRIBUTE_SENTRY_SOURCE]: 'route',
119126
},
120127
},
121-
{ request: req },
128+
async dataFetcherSpan => {
129+
dataFetcherSpan?.setStatus('ok');
130+
try {
131+
return await origDataFetcher.apply(this, args);
132+
} catch (e) {
133+
dataFetcherSpan?.setStatus('internal_error');
134+
requestSpan?.setStatus('internal_error');
135+
throw e;
136+
} finally {
137+
dataFetcherSpan?.end();
138+
if (!platformSupportsStreaming()) {
139+
await flushQueue();
140+
}
141+
}
142+
},
122143
);
144+
};
123145

124-
if (platformSupportsStreaming()) {
125-
// On platforms that don't support streaming, doing things after res.end() is unreliable.
126-
autoEndTransactionOnResponseEnd(newTransaction, res);
127-
}
128-
129-
// Link the transaction and the request together, so that when we would normally only have access to one, it's
130-
// still possible to grab the other.
131-
setTransactionOnRequest(newTransaction, req);
132-
spanToContinue = newTransaction;
146+
if (requestSpan) {
147+
return withActiveSpan(requestSpan, withActiveSpanCallback);
133148
} else {
134-
spanToContinue = previousSpan;
135-
}
136-
137-
// eslint-disable-next-line deprecation/deprecation
138-
dataFetcherSpan = spanToContinue.startChild({
139-
op: 'function.nextjs',
140-
description: `${options.dataFetchingMethodName} (${options.dataFetcherRouteName})`,
141-
origin: 'auto.function.nextjs',
142-
status: 'ok',
143-
});
144-
} else {
145-
// TODO: Refactor this to use `startSpan()`
146-
// eslint-disable-next-line deprecation/deprecation
147-
dataFetcherSpan = startTransaction({
148-
op: 'function.nextjs',
149-
name: `${options.dataFetchingMethodName} (${options.dataFetcherRouteName})`,
150-
origin: 'auto.function.nextjs',
151-
...traceparentData,
152-
status: 'ok',
153-
metadata: {
154-
request: req,
155-
source: 'route',
156-
dynamicSamplingContext: traceparentData && !dynamicSamplingContext ? {} : dynamicSamplingContext,
157-
},
158-
});
159-
}
160-
161-
// eslint-disable-next-line deprecation/deprecation
162-
scope.setSpan(dataFetcherSpan);
163-
scope.setSDKProcessingMetadata({ request: req });
164-
165-
try {
166-
return await origDataFetcher.apply(this, args);
167-
} catch (e) {
168-
// Since we finish the span before the error can bubble up and trigger the handlers in `registerErrorInstrumentation`
169-
// that set the transaction status, we need to manually set the status of the span & transaction
170-
dataFetcherSpan.setStatus('internal_error');
171-
previousSpan?.setStatus('internal_error');
172-
throw e;
173-
} finally {
174-
dataFetcherSpan.end();
175-
// eslint-disable-next-line deprecation/deprecation
176-
scope.setSpan(previousSpan);
177-
if (!platformSupportsStreaming()) {
178-
await flushQueue();
149+
return withActiveSpanCallback();
179150
}
180-
}
151+
});
181152
});
182153
};
183154
}
@@ -199,43 +170,30 @@ export async function callDataFetcherTraced<F extends (...args: any[]) => Promis
199170
): Promise<ReturnType<F>> {
200171
const { parameterizedRoute, dataFetchingMethodName } = options;
201172

202-
// eslint-disable-next-line deprecation/deprecation
203-
const transaction = getActiveTransaction();
204-
205-
if (!transaction) {
206-
return origFunction(...origFunctionArgs);
207-
}
208-
209-
// TODO: Make sure that the given route matches the name of the active transaction (to prevent background data
210-
// fetching from switching the name to a completely other route) -- We'll probably switch to creating a transaction
211-
// right here so making that check will probabably not even be necessary.
212-
// Logic will be: If there is no active transaction, start one with correct name and source. If there is an active
213-
// transaction, create a child span with correct name and source.
214-
transaction.updateName(parameterizedRoute);
215-
transaction.setAttribute(SEMANTIC_ATTRIBUTE_SENTRY_SOURCE, 'route');
173+
return startSpan(
174+
{
175+
op: 'function.nextjs',
176+
name: `${dataFetchingMethodName} (${parameterizedRoute})`,
177+
attributes: {
178+
[SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN]: 'auto.function.nextjs',
179+
[SEMANTIC_ATTRIBUTE_SENTRY_SOURCE]: 'route',
180+
},
181+
},
182+
async dataFetcherSpan => {
183+
dataFetcherSpan?.setStatus('ok');
216184

217-
// Capture the route, since pre-loading, revalidation, etc might mean that this span may happen during another
218-
// route's transaction
219-
// eslint-disable-next-line deprecation/deprecation
220-
const span = transaction.startChild({
221-
op: 'function.nextjs',
222-
origin: 'auto.function.nextjs',
223-
description: `${dataFetchingMethodName} (${parameterizedRoute})`,
224-
status: 'ok',
225-
});
226-
227-
try {
228-
return await origFunction(...origFunctionArgs);
229-
} catch (err) {
230-
// Since we finish the span before the error can bubble up and trigger the handlers in `registerErrorInstrumentation`
231-
// that set the transaction status, we need to manually set the status of the span & transaction
232-
transaction.setStatus('internal_error');
233-
span.setStatus('internal_error');
234-
span.end();
235-
236-
// TODO Copy more robust error handling over from `withSentry`
237-
captureException(err, { mechanism: { handled: false } });
238-
239-
throw err;
240-
}
185+
try {
186+
return await origFunction(...origFunctionArgs);
187+
} catch (e) {
188+
dataFetcherSpan?.setStatus('internal_error');
189+
captureException(e, { mechanism: { handled: false } });
190+
throw e;
191+
} finally {
192+
dataFetcherSpan?.end();
193+
if (!platformSupportsStreaming()) {
194+
await flushQueue();
195+
}
196+
}
197+
},
198+
);
241199
}

0 commit comments

Comments
 (0)