1
1
import {
2
+ SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN ,
3
+ SEMANTIC_ATTRIBUTE_SENTRY_SOURCE ,
2
4
SPAN_STATUS_ERROR ,
3
5
addTracingExtensions ,
4
6
captureException ,
5
7
getActiveSpan ,
6
8
getRootSpan ,
7
9
handleCallbackErrors ,
8
10
setHttpStatus ,
11
+ startSpan ,
9
12
withIsolationScope ,
10
13
} from '@sentry/core' ;
14
+ import type { Span } from '@sentry/types' ;
11
15
import { winterCGHeadersToDict } from '@sentry/utils' ;
12
16
import { isNotFoundNavigationError , isRedirectNavigationError } from './nextNavigationErrorUtils' ;
13
17
import type { RouteHandlerContext } from './types' ;
14
18
import { platformSupportsStreaming } from './utils/platformSupportsStreaming' ;
15
19
import { flushQueue } from './utils/responseEnd' ;
16
20
import { withIsolationScopeOrReuseFromRootSpan } from './utils/withIsolationScopeOrReuseFromRootSpan' ;
17
21
22
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
23
+ async function addSpanAttributes < F extends ( ...args : any [ ] ) => any > (
24
+ originalFunction : F ,
25
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
26
+ thisArg : any ,
27
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
28
+ args : any [ ] ,
29
+ rootSpan ?: Span ,
30
+ ) : Promise < Response > {
31
+ const response : Response = await handleCallbackErrors (
32
+ ( ) => originalFunction . apply ( thisArg , args ) ,
33
+ error => {
34
+ // Next.js throws errors when calling `redirect()`. We don't wanna report these.
35
+ if ( isRedirectNavigationError ( error ) ) {
36
+ // Don't do anything
37
+ } else if ( isNotFoundNavigationError ( error ) && rootSpan ) {
38
+ rootSpan . setStatus ( { code : SPAN_STATUS_ERROR , message : 'not_found' } ) ;
39
+ } else {
40
+ captureException ( error , {
41
+ mechanism : {
42
+ handled : false ,
43
+ } ,
44
+ } ) ;
45
+ }
46
+ } ,
47
+ ) ;
48
+
49
+ try {
50
+ if ( rootSpan && response . status ) {
51
+ setHttpStatus ( rootSpan , response . status ) ;
52
+ }
53
+ } catch {
54
+ // best effort - response may be undefined?
55
+ }
56
+
57
+ return response ;
58
+ }
59
+
18
60
/**
19
61
* Wraps a Next.js route handler with performance and error instrumentation.
20
62
*/
@@ -25,10 +67,10 @@ export function wrapRouteHandlerWithSentry<F extends (...args: any[]) => any>(
25
67
) : ( ...args : Parameters < F > ) => ReturnType < F > extends Promise < unknown > ? ReturnType < F > : Promise < ReturnType < F > > {
26
68
addTracingExtensions ( ) ;
27
69
28
- const { headers } = context ;
70
+ const { method , parameterizedRoute , headers } = context ;
29
71
30
72
return new Proxy ( routeHandler , {
31
- apply : async ( originalFunction , thisArg , args ) => {
73
+ apply : ( originalFunction , thisArg , args ) => {
32
74
return withIsolationScope ( async isolationScope => {
33
75
isolationScope . setSDKProcessingMetadata ( {
34
76
request : {
@@ -40,33 +82,24 @@ export function wrapRouteHandlerWithSentry<F extends (...args: any[]) => any>(
40
82
const activeSpan = getActiveSpan ( ) ;
41
83
const rootSpan = activeSpan && getRootSpan ( activeSpan ) ;
42
84
43
- const response : Response = await handleCallbackErrors (
44
- ( ) => originalFunction . apply ( thisArg , args ) ,
45
- error => {
46
- // Next.js throws errors when calling `redirect()`. We don't wanna report these.
47
- if ( isRedirectNavigationError ( error ) ) {
48
- // Don't do anything
49
- } else if ( isNotFoundNavigationError ( error ) && rootSpan ) {
50
- rootSpan . setStatus ( { code : SPAN_STATUS_ERROR , message : 'not_found' } ) ;
51
- } else {
52
- captureException ( error , {
53
- mechanism : {
54
- handled : false ,
55
- } ,
56
- } ) ;
57
- }
58
- } ,
59
- ) ;
60
-
61
- try {
62
- if ( rootSpan && response . status ) {
63
- setHttpStatus ( rootSpan , response . status ) ;
64
- }
65
- } catch {
66
- // best effort - response may be undefined?
85
+ if ( rootSpan ) {
86
+ return await addSpanAttributes < F > ( originalFunction , thisArg , args , rootSpan ) ;
87
+ } else {
88
+ /** As our own HTTP integration is disabled (src/server/index.ts) the rootSpan comes from Next.js.
89
+ * In case there is not root span, we start a new one. */
90
+ return await startSpan (
91
+ {
92
+ op : 'http.server' ,
93
+ name : `${ method } ${ parameterizedRoute } ` ,
94
+ forceTransaction : true ,
95
+ attributes : {
96
+ [ SEMANTIC_ATTRIBUTE_SENTRY_SOURCE ] : 'route' ,
97
+ [ SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN ] : 'auto.function.nextjs' ,
98
+ } ,
99
+ } ,
100
+ async span => addSpanAttributes ( originalFunction , thisArg , args , span ) ,
101
+ ) ;
67
102
}
68
-
69
- return response ;
70
103
} finally {
71
104
if ( ! platformSupportsStreaming ( ) || process . env . NEXT_RUNTIME === 'edge' ) {
72
105
// 1. Edge transport requires manual flushing
0 commit comments