11import type { IncomingMessage , ServerResponse } from 'http' ;
22import {
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
1416import { platformSupportsStreaming } from './platformSupportsStreaming' ;
15- import { autoEndTransactionOnResponseEnd , flushQueue } from './responseEnd' ;
17+ import { autoEndSpanOnResponseEnd , flushQueue } from './responseEnd' ;
1618
1719declare 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