From 99a8159e0bfab1cad4819d48acfd981578371794 Mon Sep 17 00:00:00 2001 From: Charly Gomez Date: Fri, 4 Oct 2024 15:44:30 +0200 Subject: [PATCH 1/6] [WIP] use nextjs otel instrumentation for route handler --- .../src/common/wrapRouteHandlerWithSentry.ts | 139 ++++++++++-------- packages/nextjs/src/server/index.ts | 11 +- 2 files changed, 84 insertions(+), 66 deletions(-) diff --git a/packages/nextjs/src/common/wrapRouteHandlerWithSentry.ts b/packages/nextjs/src/common/wrapRouteHandlerWithSentry.ts index bf0d475603f2..2d3565bcc260 100644 --- a/packages/nextjs/src/common/wrapRouteHandlerWithSentry.ts +++ b/packages/nextjs/src/common/wrapRouteHandlerWithSentry.ts @@ -3,8 +3,13 @@ import { SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN, SEMANTIC_ATTRIBUTE_SENTRY_SOURCE, SPAN_STATUS_ERROR, + Scope, captureException, + getActiveSpan, + getCapturedScopesOnSpan, + getRootSpan, handleCallbackErrors, + setCapturedScopesOnSpan, setHttpStatus, startSpan, withIsolationScope, @@ -35,75 +40,85 @@ export function wrapRouteHandlerWithSentry any>( return new Proxy(routeHandler, { apply: (originalFunction, thisArg, args) => { - return escapeNextjsTracing(() => { - const isolationScope = commonObjectToIsolationScope(headers); + const isolationScope = commonObjectToIsolationScope(headers); - const completeHeadersDict: Record = headers ? winterCGHeadersToDict(headers) : {}; + const activeSpan = getActiveSpan(); + if (activeSpan) { + const rootSpan = getRootSpan(activeSpan); + const { scope } = getCapturedScopesOnSpan(rootSpan); + setCapturedScopesOnSpan(rootSpan, scope ?? new Scope(), isolationScope); - isolationScope.setSDKProcessingMetadata({ - request: { - headers: completeHeadersDict, - }, - }); + // We mark the root span as an app route handler span so we can allow-list it in our span processor that would normally filter out all Next.js transactions/spans + rootSpan.setAttribute('sentry.route_handler', true); + } - const incomingPropagationContext = propagationContextFromHeaders( - completeHeadersDict['sentry-trace'], - completeHeadersDict['baggage'], - ); + return originalFunction.apply(thisArg, args); - const propagationContext = commonObjectToPropagationContext(headers, incomingPropagationContext); + // const completeHeadersDict: Record = headers ? winterCGHeadersToDict(headers) : {}; - return withIsolationScope(isolationScope, () => { - return withScope(async scope => { - scope.setTransactionName(`${method} ${parameterizedRoute}`); - scope.setPropagationContext(propagationContext); - try { - return startSpan( - { - name: `${method} ${parameterizedRoute}`, - attributes: { - [SEMANTIC_ATTRIBUTE_SENTRY_SOURCE]: 'route', - [SEMANTIC_ATTRIBUTE_SENTRY_OP]: 'http.server', - [SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN]: 'auto.function.nextjs', - }, - forceTransaction: true, - }, - async span => { - const response: Response = await handleCallbackErrors( - () => originalFunction.apply(thisArg, args), - error => { - // Next.js throws errors when calling `redirect()`. We don't wanna report these. - if (isRedirectNavigationError(error)) { - // Don't do anything - } else if (isNotFoundNavigationError(error) && span) { - span.setStatus({ code: SPAN_STATUS_ERROR, message: 'not_found' }); - } else { - captureException(error, { - mechanism: { - handled: false, - }, - }); - } - }, - ); + // isolationScope.setSDKProcessingMetadata({ + // request: { + // headers: completeHeadersDict, + // }, + // }); - try { - if (span && response.status) { - setHttpStatus(span, response.status); - } - } catch { - // best effort - response may be undefined? - } + // const incomingPropagationContext = propagationContextFromHeaders( + // completeHeadersDict['sentry-trace'], + // completeHeadersDict['baggage'], + // ); - return response; - }, - ); - } finally { - vercelWaitUntil(flushSafelyWithTimeout()); - } - }); - }); - }); + // const propagationContext = commonObjectToPropagationContext(headers, incomingPropagationContext); + + // return withIsolationScope(isolationScope, () => { + // return withScope(async scope => { + // scope.setTransactionName(`${method} ${parameterizedRoute}`); + // scope.setPropagationContext(propagationContext); + // try { + // return startSpan( + // { + // name: `${method} ${parameterizedRoute}`, + // attributes: { + // [SEMANTIC_ATTRIBUTE_SENTRY_SOURCE]: 'route', + // [SEMANTIC_ATTRIBUTE_SENTRY_OP]: 'http.server', + // [SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN]: 'auto.function.nextjs', + // }, + // forceTransaction: true, + // }, + // async span => { + // const response: Response = await handleCallbackErrors( + // () => originalFunction.apply(thisArg, args), + // error => { + // // Next.js throws errors when calling `redirect()`. We don't wanna report these. + // if (isRedirectNavigationError(error)) { + // // Don't do anything + // } else if (isNotFoundNavigationError(error) && span) { + // span.setStatus({ code: SPAN_STATUS_ERROR, message: 'not_found' }); + // } else { + // captureException(error, { + // mechanism: { + // handled: false, + // }, + // }); + // } + // }, + // ); + + // try { + // if (span && response.status) { + // setHttpStatus(span, response.status); + // } + // } catch { + // // best effort - response may be undefined? + // } + + // return response; + // }, + // ); + // } finally { + // vercelWaitUntil(flushSafelyWithTimeout()); + // } + // }); + // }); }, }); } diff --git a/packages/nextjs/src/server/index.ts b/packages/nextjs/src/server/index.ts index 1bfc57b44418..f6ce0c80e53d 100644 --- a/packages/nextjs/src/server/index.ts +++ b/packages/nextjs/src/server/index.ts @@ -215,7 +215,8 @@ export function init(options: NodeOptions): NodeClient | undefined { if ( (event.contexts?.trace?.data?.[SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN] === 'auto.http.otel.http' || event.contexts?.trace?.data?.['next.span_type'] === 'BaseServer.handleRequest') && - event.contexts?.trace?.data?.['sentry.rsc'] !== true + event.contexts?.trace?.data?.['sentry.rsc'] !== true && + event.contexts?.trace?.data?.['sentry.route_handler'] !== true ) { return null; } @@ -295,9 +296,11 @@ export function init(options: NodeOptions): NodeClient | undefined { // Next.js that are actually more or less correct server HTTP spans, so we are backfilling the op here. if ( event.type === 'transaction' && - event.transaction?.match(/^(RSC )?GET /) && - event.contexts?.trace?.data?.['sentry.rsc'] === true && - !event.contexts.trace.op + (event.transaction?.match(/^(RSC )?GET /) || + event.transaction?.match(/^(GET|HEAD|POST|PUT|DELETE|CONNECT|OPTIONS|TRACE|PATCH) \/api$/)) && + (event.contexts?.trace?.data?.['sentry.rsc'] === true || + event.contexts?.trace?.data?.['sentry.route_handler'] === true) && + !event.contexts?.trace?.op ) { event.contexts.trace.data = event.contexts.trace.data || {}; event.contexts.trace.data[SEMANTIC_ATTRIBUTE_SENTRY_OP] = 'http.server'; From f2e4a82b4ea8b8af1ee82e262871a93d41172fe1 Mon Sep 17 00:00:00 2001 From: Charly Gomez Date: Mon, 7 Oct 2024 12:10:17 +0200 Subject: [PATCH 2/6] improve route handler tx name --- .../src/common/wrapRouteHandlerWithSentry.ts | 101 ++---------------- packages/nextjs/src/server/index.ts | 22 +++- 2 files changed, 24 insertions(+), 99 deletions(-) diff --git a/packages/nextjs/src/common/wrapRouteHandlerWithSentry.ts b/packages/nextjs/src/common/wrapRouteHandlerWithSentry.ts index 2d3565bcc260..4b81e2e3ac16 100644 --- a/packages/nextjs/src/common/wrapRouteHandlerWithSentry.ts +++ b/packages/nextjs/src/common/wrapRouteHandlerWithSentry.ts @@ -1,30 +1,8 @@ -import { - SEMANTIC_ATTRIBUTE_SENTRY_OP, - SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN, - SEMANTIC_ATTRIBUTE_SENTRY_SOURCE, - SPAN_STATUS_ERROR, - Scope, - captureException, - getActiveSpan, - getCapturedScopesOnSpan, - getRootSpan, - handleCallbackErrors, - setCapturedScopesOnSpan, - setHttpStatus, - startSpan, - withIsolationScope, - withScope, -} from '@sentry/core'; -import { propagationContextFromHeaders, winterCGHeadersToDict } from '@sentry/utils'; -import { isNotFoundNavigationError, isRedirectNavigationError } from './nextNavigationErrorUtils'; +import { Scope, getActiveSpan, getCapturedScopesOnSpan, getRootSpan, setCapturedScopesOnSpan } from '@sentry/core'; + import type { RouteHandlerContext } from './types'; -import { flushSafelyWithTimeout } from './utils/responseEnd'; -import { - commonObjectToIsolationScope, - commonObjectToPropagationContext, - escapeNextjsTracing, -} from './utils/tracingUtils'; -import { vercelWaitUntil } from './utils/vercelWaitUntil'; + +import { commonObjectToIsolationScope } from './utils/tracingUtils'; /** * Wraps a Next.js App Router Route handler with Sentry error and performance instrumentation. @@ -36,7 +14,7 @@ export function wrapRouteHandlerWithSentry any>( routeHandler: F, context: RouteHandlerContext, ): (...args: Parameters) => ReturnType extends Promise ? ReturnType : Promise> { - const { method, parameterizedRoute, headers } = context; + const { headers } = context; return new Proxy(routeHandler, { apply: (originalFunction, thisArg, args) => { @@ -48,77 +26,12 @@ export function wrapRouteHandlerWithSentry any>( const { scope } = getCapturedScopesOnSpan(rootSpan); setCapturedScopesOnSpan(rootSpan, scope ?? new Scope(), isolationScope); - // We mark the root span as an app route handler span so we can allow-list it in our span processor that would normally filter out all Next.js transactions/spans + // We mark the root span as an app route handler span so we can allow-list it in our span processor + // that would normally filter out all Next.js transactions/spans rootSpan.setAttribute('sentry.route_handler', true); } return originalFunction.apply(thisArg, args); - - // const completeHeadersDict: Record = headers ? winterCGHeadersToDict(headers) : {}; - - // isolationScope.setSDKProcessingMetadata({ - // request: { - // headers: completeHeadersDict, - // }, - // }); - - // const incomingPropagationContext = propagationContextFromHeaders( - // completeHeadersDict['sentry-trace'], - // completeHeadersDict['baggage'], - // ); - - // const propagationContext = commonObjectToPropagationContext(headers, incomingPropagationContext); - - // return withIsolationScope(isolationScope, () => { - // return withScope(async scope => { - // scope.setTransactionName(`${method} ${parameterizedRoute}`); - // scope.setPropagationContext(propagationContext); - // try { - // return startSpan( - // { - // name: `${method} ${parameterizedRoute}`, - // attributes: { - // [SEMANTIC_ATTRIBUTE_SENTRY_SOURCE]: 'route', - // [SEMANTIC_ATTRIBUTE_SENTRY_OP]: 'http.server', - // [SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN]: 'auto.function.nextjs', - // }, - // forceTransaction: true, - // }, - // async span => { - // const response: Response = await handleCallbackErrors( - // () => originalFunction.apply(thisArg, args), - // error => { - // // Next.js throws errors when calling `redirect()`. We don't wanna report these. - // if (isRedirectNavigationError(error)) { - // // Don't do anything - // } else if (isNotFoundNavigationError(error) && span) { - // span.setStatus({ code: SPAN_STATUS_ERROR, message: 'not_found' }); - // } else { - // captureException(error, { - // mechanism: { - // handled: false, - // }, - // }); - // } - // }, - // ); - - // try { - // if (span && response.status) { - // setHttpStatus(span, response.status); - // } - // } catch { - // // best effort - response may be undefined? - // } - - // return response; - // }, - // ); - // } finally { - // vercelWaitUntil(flushSafelyWithTimeout()); - // } - // }); - // }); }, }); } diff --git a/packages/nextjs/src/server/index.ts b/packages/nextjs/src/server/index.ts index f6ce0c80e53d..f837641a0647 100644 --- a/packages/nextjs/src/server/index.ts +++ b/packages/nextjs/src/server/index.ts @@ -211,7 +211,8 @@ export function init(options: NodeOptions): NodeClient | undefined { return null; } - // We only want to use our HTTP integration/instrumentation for app router requests, which are marked with the `sentry.rsc` attribute. + // We only want to use our HTTP integration/instrumentation for app router requests, + // which are marked with the `sentry.rsc` or `sentry.route_handler` attribute. if ( (event.contexts?.trace?.data?.[SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN] === 'auto.http.otel.http' || event.contexts?.trace?.data?.['next.span_type'] === 'BaseServer.handleRequest') && @@ -296,10 +297,8 @@ export function init(options: NodeOptions): NodeClient | undefined { // Next.js that are actually more or less correct server HTTP spans, so we are backfilling the op here. if ( event.type === 'transaction' && - (event.transaction?.match(/^(RSC )?GET /) || - event.transaction?.match(/^(GET|HEAD|POST|PUT|DELETE|CONNECT|OPTIONS|TRACE|PATCH) \/api$/)) && - (event.contexts?.trace?.data?.['sentry.rsc'] === true || - event.contexts?.trace?.data?.['sentry.route_handler'] === true) && + event.transaction?.match(/^(RSC )?GET /) && + event.contexts?.trace?.data?.['sentry.rsc'] === true && !event.contexts?.trace?.op ) { event.contexts.trace.data = event.contexts.trace.data || {}; @@ -307,6 +306,19 @@ export function init(options: NodeOptions): NodeClient | undefined { event.contexts.trace.op = 'http.server'; } + // Enhance route handler transactions + if ( + event.type === 'transaction' && + event.transaction?.match(/^(GET|HEAD|POST|PUT|DELETE|CONNECT|OPTIONS|TRACE|PATCH) \/api/) && + event.contexts?.trace?.data?.['sentry.route_handler'] === true && + !event.contexts.trace.op + ) { + event.contexts.trace.data = event.contexts.trace.data || {}; + event.contexts.trace.data[SEMANTIC_ATTRIBUTE_SENTRY_OP] = 'http.server'; + event.contexts.trace.op = 'http.server'; + event.transaction = event.transaction.replace(/\/route$/, ''); + } + return event; }) satisfies EventProcessor, { id: 'NextjsTransactionEnhancer' }, From d4d921525d3ea467a3cca49799b6760a788665d6 Mon Sep 17 00:00:00 2001 From: Charly Gomez Date: Mon, 7 Oct 2024 13:40:55 +0200 Subject: [PATCH 3/6] update matcher for route handlers --- packages/nextjs/src/server/index.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/nextjs/src/server/index.ts b/packages/nextjs/src/server/index.ts index f837641a0647..450db8c66ffd 100644 --- a/packages/nextjs/src/server/index.ts +++ b/packages/nextjs/src/server/index.ts @@ -309,7 +309,7 @@ export function init(options: NodeOptions): NodeClient | undefined { // Enhance route handler transactions if ( event.type === 'transaction' && - event.transaction?.match(/^(GET|HEAD|POST|PUT|DELETE|CONNECT|OPTIONS|TRACE|PATCH) \/api/) && + event.transaction?.match(/^(GET|HEAD|POST|PUT|DELETE|CONNECT|OPTIONS|TRACE|PATCH) .*\/route\.(ts|js)$/) && event.contexts?.trace?.data?.['sentry.route_handler'] === true && !event.contexts.trace.op ) { From 196ee63273a7884259e21d0dbf0e6cc2d680d0b9 Mon Sep 17 00:00:00 2001 From: Charly Gomez Date: Mon, 7 Oct 2024 14:04:27 +0200 Subject: [PATCH 4/6] remove file extensions from tx matcher --- packages/nextjs/src/server/index.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/nextjs/src/server/index.ts b/packages/nextjs/src/server/index.ts index 450db8c66ffd..d24bb04050bb 100644 --- a/packages/nextjs/src/server/index.ts +++ b/packages/nextjs/src/server/index.ts @@ -309,7 +309,7 @@ export function init(options: NodeOptions): NodeClient | undefined { // Enhance route handler transactions if ( event.type === 'transaction' && - event.transaction?.match(/^(GET|HEAD|POST|PUT|DELETE|CONNECT|OPTIONS|TRACE|PATCH) .*\/route\.(ts|js)$/) && + event.transaction?.match(/^(GET|HEAD|POST|PUT|DELETE|CONNECT|OPTIONS|TRACE|PATCH) .*\/route$/) && event.contexts?.trace?.data?.['sentry.route_handler'] === true && !event.contexts.trace.op ) { From 5b0378544b6fdf572ee2516ed410db4337456fe1 Mon Sep 17 00:00:00 2001 From: Charly Gomez Date: Tue, 8 Oct 2024 14:29:37 +0200 Subject: [PATCH 5/6] return of the scopes --- .../tests/route-handlers.test.ts | 4 +- .../src/common/wrapRouteHandlerWithSentry.ts | 63 ++++++++++++++++--- packages/nextjs/src/server/index.ts | 19 +++--- 3 files changed, 67 insertions(+), 19 deletions(-) diff --git a/dev-packages/e2e-tests/test-applications/nextjs-app-dir/tests/route-handlers.test.ts b/dev-packages/e2e-tests/test-applications/nextjs-app-dir/tests/route-handlers.test.ts index 8f474ed50046..188e24e25ff5 100644 --- a/dev-packages/e2e-tests/test-applications/nextjs-app-dir/tests/route-handlers.test.ts +++ b/dev-packages/e2e-tests/test-applications/nextjs-app-dir/tests/route-handlers.test.ts @@ -54,9 +54,9 @@ test('Should record exceptions and transactions for faulty route handlers', asyn expect(routehandlerError.tags?.['my-isolated-tag']).toBe(true); expect(routehandlerError.tags?.['my-global-scope-isolated-tag']).not.toBeDefined(); - expect(routehandlerTransaction.contexts?.trace?.status).toBe('unknown_error'); + expect(routehandlerTransaction.contexts?.trace?.status).toBe('internal_error'); expect(routehandlerTransaction.contexts?.trace?.op).toBe('http.server'); - expect(routehandlerTransaction.contexts?.trace?.origin).toBe('auto.function.nextjs'); + expect(routehandlerTransaction.contexts?.trace?.origin).toBe('auto'); expect(routehandlerError.exception?.values?.[0].value).toBe('route-handler-error'); diff --git a/packages/nextjs/src/common/wrapRouteHandlerWithSentry.ts b/packages/nextjs/src/common/wrapRouteHandlerWithSentry.ts index 4b81e2e3ac16..ad70c865dedf 100644 --- a/packages/nextjs/src/common/wrapRouteHandlerWithSentry.ts +++ b/packages/nextjs/src/common/wrapRouteHandlerWithSentry.ts @@ -1,8 +1,21 @@ -import { Scope, getActiveSpan, getCapturedScopesOnSpan, getRootSpan, setCapturedScopesOnSpan } from '@sentry/core'; +import { + Scope, + captureException, + getActiveSpan, + getCapturedScopesOnSpan, + getRootSpan, + handleCallbackErrors, + setCapturedScopesOnSpan, + withIsolationScope, + withScope, +} from '@sentry/core'; import type { RouteHandlerContext } from './types'; -import { commonObjectToIsolationScope } from './utils/tracingUtils'; +import { propagationContextFromHeaders, winterCGHeadersToDict } from '@sentry/utils'; + +import { isRedirectNavigationError } from './nextNavigationErrorUtils'; +import { commonObjectToIsolationScope, commonObjectToPropagationContext } from './utils/tracingUtils'; /** * Wraps a Next.js App Router Route handler with Sentry error and performance instrumentation. @@ -14,24 +27,56 @@ export function wrapRouteHandlerWithSentry any>( routeHandler: F, context: RouteHandlerContext, ): (...args: Parameters) => ReturnType extends Promise ? ReturnType : Promise> { - const { headers } = context; + const { method, parameterizedRoute, headers } = context; return new Proxy(routeHandler, { - apply: (originalFunction, thisArg, args) => { + apply: async (originalFunction, thisArg, args) => { const isolationScope = commonObjectToIsolationScope(headers); + const completeHeadersDict: Record = headers ? winterCGHeadersToDict(headers) : {}; + + isolationScope.setSDKProcessingMetadata({ + request: { + headers: completeHeadersDict, + }, + }); + + const incomingPropagationContext = propagationContextFromHeaders( + completeHeadersDict['sentry-trace'], + completeHeadersDict['baggage'], + ); + + const propagationContext = commonObjectToPropagationContext(headers, incomingPropagationContext); + const activeSpan = getActiveSpan(); if (activeSpan) { const rootSpan = getRootSpan(activeSpan); + rootSpan.setAttribute('sentry.route_handler', true); const { scope } = getCapturedScopesOnSpan(rootSpan); setCapturedScopesOnSpan(rootSpan, scope ?? new Scope(), isolationScope); - - // We mark the root span as an app route handler span so we can allow-list it in our span processor - // that would normally filter out all Next.js transactions/spans - rootSpan.setAttribute('sentry.route_handler', true); } - return originalFunction.apply(thisArg, args); + return withIsolationScope(isolationScope, () => { + return withScope(scope => { + scope.setTransactionName(`${method} ${parameterizedRoute}`); + scope.setPropagationContext(propagationContext); + return handleCallbackErrors( + () => originalFunction.apply(thisArg, args), + error => { + // Next.js throws errors when calling `redirect()`. We don't wanna report these. + if (isRedirectNavigationError(error)) { + // Don't do anything + } else { + captureException(error, { + mechanism: { + handled: false, + }, + }); + } + }, + ); + }); + }); }, }); } diff --git a/packages/nextjs/src/server/index.ts b/packages/nextjs/src/server/index.ts index d24bb04050bb..96b5f798d56d 100644 --- a/packages/nextjs/src/server/index.ts +++ b/packages/nextjs/src/server/index.ts @@ -196,7 +196,10 @@ export function init(options: NodeOptions): NodeClient | undefined { // We want to rename these spans because they look like "GET /path/to/route" and we already emit spans that look // like this with our own http instrumentation. if (spanAttributes?.['next.span_type'] === 'BaseServer.handleRequest') { - span.updateName('next server handler'); // This is all lowercase because the spans that Next.js emits by itself generally look like this. + const rootSpan = getRootSpan(span); + if (span !== rootSpan) { + span.updateName('next server handler'); // This is all lowercase because the spans that Next.js emits by itself generally look like this. + } } }); @@ -307,16 +310,16 @@ export function init(options: NodeOptions): NodeClient | undefined { } // Enhance route handler transactions - if ( - event.type === 'transaction' && - event.transaction?.match(/^(GET|HEAD|POST|PUT|DELETE|CONNECT|OPTIONS|TRACE|PATCH) .*\/route$/) && - event.contexts?.trace?.data?.['sentry.route_handler'] === true && - !event.contexts.trace.op - ) { + if (event.type === 'transaction' && event.contexts?.trace?.data?.['sentry.route_handler'] === true) { event.contexts.trace.data = event.contexts.trace.data || {}; event.contexts.trace.data[SEMANTIC_ATTRIBUTE_SENTRY_OP] = 'http.server'; event.contexts.trace.op = 'http.server'; - event.transaction = event.transaction.replace(/\/route$/, ''); + if (typeof event.contexts.trace.data[ATTR_HTTP_ROUTE] === 'string') { + // eslint-disable-next-line deprecation/deprecation + event.transaction = `${event.contexts.trace.data[SEMATTRS_HTTP_METHOD]} ${event.contexts.trace.data[ + ATTR_HTTP_ROUTE + ].replace(/\/route$/, '')}`; + } } return event; From ef7dff95d9e927f10828fc97d52c07fec597640b Mon Sep 17 00:00:00 2001 From: Charly Gomez Date: Tue, 8 Oct 2024 15:41:27 +0200 Subject: [PATCH 6/6] why did I change this --- .../nextjs-app-dir/tests/route-handlers.test.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/dev-packages/e2e-tests/test-applications/nextjs-app-dir/tests/route-handlers.test.ts b/dev-packages/e2e-tests/test-applications/nextjs-app-dir/tests/route-handlers.test.ts index 188e24e25ff5..3e8006ae21f8 100644 --- a/dev-packages/e2e-tests/test-applications/nextjs-app-dir/tests/route-handlers.test.ts +++ b/dev-packages/e2e-tests/test-applications/nextjs-app-dir/tests/route-handlers.test.ts @@ -56,7 +56,7 @@ test('Should record exceptions and transactions for faulty route handlers', asyn expect(routehandlerTransaction.contexts?.trace?.status).toBe('internal_error'); expect(routehandlerTransaction.contexts?.trace?.op).toBe('http.server'); - expect(routehandlerTransaction.contexts?.trace?.origin).toBe('auto'); + expect(routehandlerTransaction.contexts?.trace?.origin).toContain('auto.http.otel.http'); expect(routehandlerError.exception?.values?.[0].value).toBe('route-handler-error');