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,13 @@ export function init(options: VercelEdgeOptions = {}): void {
8584 ...( isRunningOnCloudflare && { runtime : { name : 'cloudflare' } } ) ,
8685 } ;
8786
87+ const nextjsIgnoreSpans : NonNullable < VercelEdgeOptions [ 'ignoreSpans' ] > = [
88+ // Spans flagged via TRANSACTION_ATTR_SHOULD_DROP_TRANSACTION
89+ // (set in `dropMiddlewareTunnelRequests` during `spanStart`)
90+ { attributes : { [ TRANSACTION_ATTR_SHOULD_DROP_TRANSACTION ] : true } } ,
91+ ] ;
92+ opts . ignoreSpans = [ ...( opts . ignoreSpans || [ ] ) , ...nextjsIgnoreSpans ] ;
93+
8894 // Use appropriate SDK metadata based on the runtime environment
8995 if ( isRunningOnCloudflare ) {
9096 applySdkMetadata ( opts , 'nextjs' , [ 'nextjs' , 'cloudflare' ] ) ;
@@ -137,57 +143,42 @@ export function init(options: VercelEdgeOptions = {}): void {
137143 // Use the preprocessEvent hook instead of an event processor, so that the users event processors receive the most
138144 // up-to-date value, but also so that the logic that detects changes to the transaction names to set the source to
139145 // "custom", doesn't trigger.
146+ // This handles the legacy (non-streamed) path where the segment span is emitted as a transaction event;
147+ // `enhanceMiddlewareRootSpan` is adapted to operate on the event's trace context, which is the segment span's data.
148+ // Span streaming bypasses event processors entirely - see the `processSegmentSpan` hook below for that path.
140149 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- }
150+ if ( event . type === 'transaction' && event . contexts ?. trace ?. data ) {
151+ enhanceMiddlewareRootSpan ( {
152+ attributes : event . contexts . trace . data ,
153+ getName : ( ) => event . transaction ,
154+ setName : name => {
155+ event . transaction = name ;
156+ } ,
157+ } ) ;
162158 }
163159
164160 setUrlProcessingMetadata ( event ) ;
165161 } ) ;
166162
163+ // Streamed-span counterpart of the `preprocessEvent` hook above. Streamed segment spans never become
164+ // transaction events, so the same enhancement has to be applied here directly on the span JSON.
165+ client ?. on ( 'processSegmentSpan' , span => {
166+ const attributes = ( span . attributes ??= { } ) ;
167+ enhanceMiddlewareRootSpan ( {
168+ attributes,
169+ getName : ( ) => span . name ,
170+ setName : name => {
171+ span . name = name ;
172+ } ,
173+ } ) ;
174+ } ) ;
175+
167176 client ?. on ( 'spanEnd' , span => {
168177 if ( span === getRootSpan ( span ) ) {
169178 waitUntil ( flushSafelyWithTimeout ( ) ) ;
170179 }
171180 } ) ;
172181
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-
191182 try {
192183 // @ts -expect-error `process.turbopack` is a magic string that will be replaced by Next.js
193184 if ( process . turbopack ) {
0 commit comments