diff --git a/dev-packages/e2e-tests/test-applications/nextjs-app-dir/tests/middleware.test.ts b/dev-packages/e2e-tests/test-applications/nextjs-app-dir/tests/middleware.test.ts index 5501f9a13b33..a00a29672ed6 100644 --- a/dev-packages/e2e-tests/test-applications/nextjs-app-dir/tests/middleware.test.ts +++ b/dev-packages/e2e-tests/test-applications/nextjs-app-dir/tests/middleware.test.ts @@ -14,6 +14,7 @@ test('Should create a transaction for middleware', async ({ request }) => { expect(middlewareTransaction.contexts?.trace?.status).toBe('ok'); expect(middlewareTransaction.contexts?.trace?.op).toBe('http.server.middleware'); expect(middlewareTransaction.contexts?.runtime?.name).toBe('vercel-edge'); + expect(middlewareTransaction.transaction_info?.source).toBe('url'); // Assert that isolation scope works properly expect(middlewareTransaction.tags?.['my-isolated-tag']).toBe(true); @@ -38,6 +39,7 @@ test('Faulty middlewares', async ({ request }) => { expect(middlewareTransaction.contexts?.trace?.status).toBe('unknown_error'); expect(middlewareTransaction.contexts?.trace?.op).toBe('http.server.middleware'); expect(middlewareTransaction.contexts?.runtime?.name).toBe('vercel-edge'); + expect(middlewareTransaction.transaction_info?.source).toBe('url'); }); await test.step('should record exceptions', async () => { diff --git a/packages/nextjs/src/common/wrapMiddlewareWithSentry.ts b/packages/nextjs/src/common/wrapMiddlewareWithSentry.ts index 9f0903e86984..e8b57c7d2b8b 100644 --- a/packages/nextjs/src/common/wrapMiddlewareWithSentry.ts +++ b/packages/nextjs/src/common/wrapMiddlewareWithSentry.ts @@ -32,17 +32,17 @@ export function wrapMiddlewareWithSentry<H extends EdgeRouteHandler>( const currentScope = getCurrentScope(); let spanName: string; - let spanOrigin: TransactionSource; + let spanSource: TransactionSource; if (req instanceof Request) { isolationScope.setSDKProcessingMetadata({ request: winterCGRequestToRequestData(req), }); spanName = `middleware ${req.method} ${new URL(req.url).pathname}`; - spanOrigin = 'url'; + spanSource = 'url'; } else { spanName = 'middleware'; - spanOrigin = 'component'; + spanSource = 'component'; } currentScope.setTransactionName(spanName); @@ -53,7 +53,7 @@ export function wrapMiddlewareWithSentry<H extends EdgeRouteHandler>( // If there is an active span, it likely means that the automatic Next.js OTEL instrumentation worked and we can // rely on that for parameterization. spanName = 'middleware'; - spanOrigin = 'component'; + spanSource = 'component'; const rootSpan = getRootSpan(activeSpan); if (rootSpan) { @@ -66,7 +66,7 @@ export function wrapMiddlewareWithSentry<H extends EdgeRouteHandler>( name: spanName, op: 'http.server.middleware', attributes: { - [SEMANTIC_ATTRIBUTE_SENTRY_SOURCE]: spanOrigin, + [SEMANTIC_ATTRIBUTE_SENTRY_SOURCE]: spanSource, [SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN]: 'auto.function.nextjs.wrapMiddlewareWithSentry', }, }, diff --git a/packages/nextjs/src/edge/index.ts b/packages/nextjs/src/edge/index.ts index 0a63118ada46..fff4236bf3be 100644 --- a/packages/nextjs/src/edge/index.ts +++ b/packages/nextjs/src/edge/index.ts @@ -1,12 +1,14 @@ import { SEMANTIC_ATTRIBUTE_SENTRY_OP, + SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN, + SEMANTIC_ATTRIBUTE_SENTRY_SOURCE, applySdkMetadata, getRootSpan, registerSpanErrorInstrumentation, spanToJSON, } from '@sentry/core'; -import { GLOBAL_OBJ, vercelWaitUntil } from '@sentry/utils'; +import { GLOBAL_OBJ, stripUrlQueryAndFragment, vercelWaitUntil } from '@sentry/utils'; import type { VercelEdgeOptions } from '@sentry/vercel-edge'; import { getDefaultIntegrations, init as vercelEdgeInit } from '@sentry/vercel-edge'; @@ -52,9 +54,31 @@ export function init(options: VercelEdgeOptions = {}): void { client?.on('spanStart', span => { const spanAttributes = spanToJSON(span).data; + // Mark all spans generated by Next.js as 'auto' + if (spanAttributes?.['next.span_type'] !== undefined) { + span.setAttribute(SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN, 'auto'); + } + // Make sure middleware spans get the right op if (spanAttributes?.['next.span_type'] === 'Middleware.execute') { span.setAttribute(SEMANTIC_ATTRIBUTE_SENTRY_OP, 'http.server.middleware'); + span.setAttribute(SEMANTIC_ATTRIBUTE_SENTRY_SOURCE, 'url'); + } + }); + + // Use the preprocessEvent hook instead of an event processor, so that the users event processors receive the most + // up-to-date value, but also so that the logic that detects changes to the transaction names to set the source to + // "custom", doesn't trigger. + client?.on('preprocessEvent', event => { + // The otel auto inference will clobber the transaction name because the span has an http.target + if ( + event.type === 'transaction' && + event.contexts?.trace?.data?.['next.span_type'] === 'Middleware.execute' && + event.contexts?.trace?.data?.['next.span_name'] + ) { + if (event.transaction) { + event.transaction = stripUrlQueryAndFragment(event.contexts.trace.data['next.span_name']); + } } });