44import { context } from '@opentelemetry/api' ;
55import {
66 applySdkMetadata ,
7- type EventProcessor ,
87 getCapturedScopesOnSpan ,
98 getCurrentScope ,
109 getGlobalScope ,
@@ -17,7 +16,6 @@ import {
1716 SEMANTIC_ATTRIBUTE_SENTRY_SOURCE ,
1817 setCapturedScopesOnSpan ,
1918 spanToJSON ,
20- stripUrlQueryAndFragment ,
2119} from '@sentry/core' ;
2220import { getScopesFromContext } from '@sentry/opentelemetry' ;
2321import type { VercelEdgeOptions } from '@sentry/vercel-edge' ;
@@ -31,6 +29,7 @@ import { isBuild } from '../common/utils/isBuild';
3129import { flushSafelyWithTimeout , isCloudflareWaitUntilAvailable , waitUntil } from '../common/utils/responseEnd' ;
3230import { setUrlProcessingMetadata } from '../common/utils/setUrlProcessingMetadata' ;
3331import { distDirRewriteFramesIntegration } from './distDirRewriteFramesIntegration' ;
32+ import { enhanceMiddlewareRootSpan } from './enhanceMiddlewareRootSpan' ;
3433
3534export * from '@sentry/vercel-edge' ;
3635export * from '../common' ;
@@ -85,6 +84,12 @@ export function init(options: VercelEdgeOptions = {}): void {
8584 ...( isRunningOnCloudflare && { runtime : { name : 'cloudflare' } } ) ,
8685 } ;
8786
87+ const nextjsIgnoreSpans : NonNullable < VercelEdgeOptions [ 'ignoreSpans' ] > = [
88+ // (set in `dropMiddlewareTunnelRequests` during `spanStart`)
89+ { attributes : { [ TRANSACTION_ATTR_SHOULD_DROP_TRANSACTION ] : true } } ,
90+ ] ;
91+ opts . ignoreSpans = [ ...( opts . ignoreSpans || [ ] ) , ...nextjsIgnoreSpans ] ;
92+
8893 // Use appropriate SDK metadata based on the runtime environment
8994 if ( isRunningOnCloudflare ) {
9095 applySdkMetadata ( opts , 'nextjs' , [ 'nextjs' , 'cloudflare' ] ) ;
@@ -137,61 +142,47 @@ export function init(options: VercelEdgeOptions = {}): void {
137142 // Use the preprocessEvent hook instead of an event processor, so that the users event processors receive the most
138143 // up-to-date value, but also so that the logic that detects changes to the transaction names to set the source to
139144 // "custom", doesn't trigger.
145+ // This handles the legacy (non-streamed) path where the segment span is emitted as a transaction event;
146+ // `enhanceMiddlewareRootSpan` is adapted to operate on the event's trace context, which is the segment span's data.
147+ // Span streaming bypasses event processors entirely - see the `processSegmentSpan` hook below for that path.
140148 client ?. on ( 'preprocessEvent' , event => {
141- // The otel auto inference will clobber the transaction name because the span has an http.target
142- if (
143- event . type === 'transaction' &&
144- event . contexts ?. trace ?. data ?. [ 'next.span_type' ] === 'Middleware.execute' &&
145- event . contexts ?. trace ?. data ?. [ 'next.span_name' ]
146- ) {
147- if ( event . transaction ) {
148- // Older nextjs versions pass the full url appended to the middleware name, which results in high cardinality transaction names.
149- // We want to remove the url from the name here.
150- const spanName = event . contexts . trace . data [ 'next.span_name' ] ;
151-
152- if ( typeof spanName === 'string' ) {
153- const match = spanName . match ( / ^ m i d d l e w a r e ( G E T | P O S T | P U T | D E L E T E | P A T C H | H E A D | O P T I O N S ) / ) ;
154- if ( match ) {
155- const normalizedName = `middleware ${ match [ 1 ] } ` ;
156- event . transaction = normalizedName ;
157- } else {
158- event . transaction = stripUrlQueryAndFragment ( event . contexts . trace . data [ 'next.span_name' ] ) ;
159- }
160- }
161- }
149+ if ( event . type === 'transaction' && event . contexts ?. trace ?. data ) {
150+ enhanceMiddlewareRootSpan ( {
151+ attributes : event . contexts . trace . data ,
152+ getName : ( ) => event . transaction ,
153+ setName : name => {
154+ event . transaction = name ;
155+ } ,
156+ } ) ;
162157 }
163158
164159 setUrlProcessingMetadata ( event ) ;
165160 } ) ;
166161
162+ // Streamed-span counterpart of the `preprocessEvent` hook above. Streamed segment spans never become
163+ // transaction events, so the same enhancement has to be applied here directly on the span JSON.
164+ client ?. on ( 'processSegmentSpan' , span => {
165+ const attributes = ( span . attributes ??= { } ) ;
166+ enhanceMiddlewareRootSpan ( {
167+ attributes,
168+ getName : ( ) => span . name ,
169+ setName : name => {
170+ span . name = name ;
171+ } ,
172+ } ) ;
173+ } ) ;
174+
167175 client ?. on ( 'spanEnd' , span => {
168176 if ( span === getRootSpan ( span ) ) {
169177 waitUntil ( flushSafelyWithTimeout ( ) ) ;
170178 }
171179 } ) ;
172180
173- getGlobalScope ( ) . addEventProcessor (
174- Object . assign (
175- ( event => {
176- // Filter transactions that we explicitly want to drop.
177- if ( event . type === 'transaction' ) {
178- if ( event . contexts ?. trace ?. data ?. [ TRANSACTION_ATTR_SHOULD_DROP_TRANSACTION ] ) {
179- return null ;
180- }
181-
182- return event ;
183- } else {
184- return event ;
185- }
186- } ) satisfies EventProcessor ,
187- { id : 'NextLowQualityTransactionsFilter' } ,
188- ) ,
189- ) ;
190-
191181 try {
192182 // @ts -expect-error `process.turbopack` is a magic string that will be replaced by Next.js
193183 if ( process . turbopack ) {
194184 getGlobalScope ( ) . setTag ( 'turbopack' , true ) ;
185+ getGlobalScope ( ) . setAttribute ( 'turbopack' , true ) ;
195186 }
196187 } catch {
197188 // Noop
0 commit comments