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