@@ -3,16 +3,13 @@ import {
33 SEMANTIC_ATTRIBUTE_SENTRY_OP ,
44 SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN ,
55 SEMANTIC_ATTRIBUTE_SENTRY_SOURCE ,
6- getClient ,
6+ continueTrace ,
7+ getCurrentScope ,
8+ withScope ,
79} from '@sentry/core' ;
8- import { WINDOW } from '@sentry/react' ;
9- import type { StartSpanOptions , TransactionSource } from '@sentry/types' ;
10- import {
11- browserPerformanceTimeOrigin ,
12- logger ,
13- propagationContextFromHeaders ,
14- stripUrlQueryAndFragment ,
15- } from '@sentry/utils' ;
10+ import { WINDOW , startBrowserTracingNavigationSpan , startBrowserTracingPageLoadSpan } from '@sentry/react' ;
11+ import type { Client , TransactionSource } from '@sentry/types' ;
12+ import { browserPerformanceTimeOrigin , logger , stripUrlQueryAndFragment } from '@sentry/utils' ;
1613import type { NEXT_DATA as NextData } from 'next/dist/next-server/lib/utils' ;
1714import RouterImport from 'next/router' ;
1815
@@ -30,8 +27,6 @@ const globalObject = WINDOW as typeof WINDOW & {
3027 } ;
3128} ;
3229
33- type StartSpanCb = ( context : StartSpanOptions ) => void ;
34-
3530/**
3631 * Describes data located in the __NEXT_DATA__ script tag. This tag is present on every page of a Next.js app.
3732 */
@@ -104,73 +99,77 @@ function extractNextDataTagInformation(): NextDataTagInfo {
10499}
105100
106101/**
107- * Instruments the Next.js pages router. Only supported for
108- * client side routing. Works for Next >= 10.
102+ * Instruments the Next.js pages router for pageloads.
103+ * Only supported for client side routing. Works for Next >= 10.
109104 *
110105 * Leverages the SingletonRouter from the `next/router` to
111106 * generate pageload/navigation transactions and parameterize
112107 * transaction names.
113108 */
114- export function pagesRouterInstrumentation (
115- shouldInstrumentPageload : boolean ,
116- shouldInstrumentNavigation : boolean ,
117- startPageloadSpanCallback : StartSpanCb ,
118- startNavigationSpanCallback : StartSpanCb ,
119- ) : void {
109+ export function pagesRouterInstrumentPageLoad ( client : Client ) : void {
120110 const { route, params, sentryTrace, baggage } = extractNextDataTagInformation ( ) ;
121- const { traceId, dsc, parentSpanId, sampled } = propagationContextFromHeaders ( sentryTrace , baggage ) ;
122- let prevLocationName = route || globalObject . location . pathname ;
123-
124- if ( shouldInstrumentPageload ) {
125- const client = getClient ( ) ;
126- startPageloadSpanCallback ( {
127- name : prevLocationName ,
128- // pageload should always start at timeOrigin (and needs to be in s, not ms)
129- startTime : browserPerformanceTimeOrigin ? browserPerformanceTimeOrigin / 1000 : undefined ,
130- traceId,
131- parentSpanId,
132- parentSampled : sampled ,
133- ...( params && client && client . getOptions ( ) . sendDefaultPii && { data : params } ) ,
134- attributes : {
135- [ SEMANTIC_ATTRIBUTE_SENTRY_OP ] : 'pageload' ,
136- [ SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN ] : 'auto.pageload.nextjs.pages_router_instrumentation' ,
137- [ SEMANTIC_ATTRIBUTE_SENTRY_SOURCE ] : route ? 'route' : 'url' ,
138- } ,
139- metadata : {
140- dynamicSamplingContext : dsc ,
141- } ,
142- } ) ;
143- }
111+ const name = route || globalObject . location . pathname ;
112+
113+ // Continue trace updates the _current_ scope, but we want to break out of it again...
114+ // This is a bit hacky, because we want to get the span to use both the correct scope _and_ the correct propagation context
115+ // but wards, we want to reset it to avoid this also applying to other spans
116+ const scope = getCurrentScope ( ) ;
117+ const propagationContextBefore = scope . getPropagationContext ( ) ;
118+
119+ continueTrace ( { sentryTrace, baggage } , ( ) => {
120+ // Ensure we are on the original current scope again, so the span is set as active on it
121+ return withScope ( scope , ( ) => {
122+ startBrowserTracingPageLoadSpan ( client , {
123+ name,
124+ // pageload should always start at timeOrigin (and needs to be in s, not ms)
125+ startTime : browserPerformanceTimeOrigin ? browserPerformanceTimeOrigin / 1000 : undefined ,
144126
145- if ( shouldInstrumentNavigation ) {
146- Router . events . on ( 'routeChangeStart' , ( navigationTarget : string ) => {
147- const strippedNavigationTarget = stripUrlQueryAndFragment ( navigationTarget ) ;
148- const matchedRoute = getNextRouteFromPathname ( strippedNavigationTarget ) ;
149-
150- let newLocation : string ;
151- let spanSource : TransactionSource ;
152-
153- if ( matchedRoute ) {
154- newLocation = matchedRoute ;
155- spanSource = 'route' ;
156- } else {
157- newLocation = strippedNavigationTarget ;
158- spanSource = 'url' ;
159- }
160-
161- startNavigationSpanCallback ( {
162- name : newLocation ,
163127 attributes : {
164- from : prevLocationName ,
165- [ SEMANTIC_ATTRIBUTE_SENTRY_OP ] : 'navigation ' ,
166- [ SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN ] : 'auto.navigation.nextjs.pages_router_instrumentation ',
167- [ SEMANTIC_ATTRIBUTE_SENTRY_SOURCE ] : spanSource ,
128+ [ SEMANTIC_ATTRIBUTE_SENTRY_OP ] : 'pageload' ,
129+ [ SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN ] : 'auto.pageload.nextjs.pages_router_instrumentation ' ,
130+ [ SEMANTIC_ATTRIBUTE_SENTRY_SOURCE ] : route ? 'route' : 'url ',
131+ ... ( params && client . getOptions ( ) . sendDefaultPii && { ... params } ) ,
168132 } ,
169133 } ) ;
134+ } ) ;
135+ } ) ;
136+
137+ scope . setPropagationContext ( propagationContextBefore ) ;
138+ }
170139
171- prevLocationName = newLocation ;
140+ /**
141+ * Instruments the Next.js pages router for navigation.
142+ * Only supported for client side routing. Works for Next >= 10.
143+ *
144+ * Leverages the SingletonRouter from the `next/router` to
145+ * generate pageload/navigation transactions and parameterize
146+ * transaction names.
147+ */
148+ export function pagesRouterInstrumentNavigation ( client : Client ) : void {
149+ Router . events . on ( 'routeChangeStart' , ( navigationTarget : string ) => {
150+ const strippedNavigationTarget = stripUrlQueryAndFragment ( navigationTarget ) ;
151+ const matchedRoute = getNextRouteFromPathname ( strippedNavigationTarget ) ;
152+
153+ let newLocation : string ;
154+ let spanSource : TransactionSource ;
155+
156+ if ( matchedRoute ) {
157+ newLocation = matchedRoute ;
158+ spanSource = 'route' ;
159+ } else {
160+ newLocation = strippedNavigationTarget ;
161+ spanSource = 'url' ;
162+ }
163+
164+ startBrowserTracingNavigationSpan ( client , {
165+ name : newLocation ,
166+ attributes : {
167+ [ SEMANTIC_ATTRIBUTE_SENTRY_OP ] : 'navigation' ,
168+ [ SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN ] : 'auto.navigation.nextjs.pages_router_instrumentation' ,
169+ [ SEMANTIC_ATTRIBUTE_SENTRY_SOURCE ] : spanSource ,
170+ } ,
172171 } ) ;
173- }
172+ } ) ;
174173}
175174
176175function getNextRouteFromPathname ( pathname : string ) : string | undefined {
0 commit comments