1
1
import type { IncomingMessage , ServerResponse } from 'http' ;
2
2
import {
3
+ SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN ,
3
4
SEMANTIC_ATTRIBUTE_SENTRY_SOURCE ,
4
5
captureException ,
5
- getActiveSpan ,
6
- getActiveTransaction ,
7
- getCurrentScope ,
8
- runWithAsyncContext ,
9
- startTransaction ,
6
+ continueTrace ,
7
+ startInactiveSpan ,
8
+ startSpan ,
9
+ startSpanManual ,
10
+ withActiveSpan ,
11
+ withIsolationScope ,
10
12
} 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' ;
13
15
14
16
import { platformSupportsStreaming } from './platformSupportsStreaming' ;
15
- import { autoEndTransactionOnResponseEnd , flushQueue } from './responseEnd' ;
17
+ import { autoEndSpanOnResponseEnd , flushQueue } from './responseEnd' ;
16
18
17
19
declare module 'http' {
18
20
interface IncomingMessage {
19
- _sentryTransaction ?: Transaction ;
21
+ _sentrySpan ?: Span ;
20
22
}
21
23
}
22
24
23
25
/**
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 `.
26
28
*
27
29
* @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.
29
31
*/
30
- export function getTransactionFromRequest ( req : IncomingMessage ) : Transaction | undefined {
31
- return req . _sentryTransaction ;
32
+ export function getSpanFromRequest ( req : IncomingMessage ) : Span | undefined {
33
+ return req . _sentrySpan ;
32
34
}
33
35
34
- function setTransactionOnRequest ( transaction : Transaction , req : IncomingMessage ) : void {
35
- req . _sentryTransaction = transaction ;
36
+ function setSpanOnRequest ( transaction : Span , req : IncomingMessage ) : void {
37
+ req . _sentrySpan = transaction ;
36
38
}
37
39
38
40
/**
@@ -85,99 +87,68 @@ export function withTracedServerSideDataFetcher<F extends (...args: any[]) => Pr
85
87
} ,
86
88
) : ( ...params : Parameters < F > ) => Promise < ReturnType < F > > {
87
89
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
+ } ) ;
92
94
93
95
const sentryTrace =
94
96
req . headers && isString ( req . headers [ 'sentry-trace' ] ) ? req . headers [ 'sentry-trace' ] : undefined ;
95
97
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 ) ;
102
98
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 (
109
120
{
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' ,
119
126
} ,
120
127
} ,
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
+ } ,
122
143
) ;
144
+ } ;
123
145
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 ) ;
133
148
} 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 ( ) ;
179
150
}
180
- }
151
+ } ) ;
181
152
} ) ;
182
153
} ;
183
154
}
@@ -199,43 +170,30 @@ export async function callDataFetcherTraced<F extends (...args: any[]) => Promis
199
170
) : Promise < ReturnType < F > > {
200
171
const { parameterizedRoute, dataFetchingMethodName } = options ;
201
172
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' ) ;
216
184
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
+ ) ;
241
199
}
0 commit comments