diff --git a/dev-packages/e2e-tests/publish-packages.ts b/dev-packages/e2e-tests/publish-packages.ts index 408d046977a2..4f2cc4056826 100644 --- a/dev-packages/e2e-tests/publish-packages.ts +++ b/dev-packages/e2e-tests/publish-packages.ts @@ -12,6 +12,8 @@ const packageTarballPaths = glob.sync('packages/*/sentry-*.tgz', { // Publish built packages to the fake registry packageTarballPaths.forEach(tarballPath => { + // eslint-disable-next-line no-console + console.log(`Publishing tarball ${tarballPath} ...`); // `--userconfig` flag needs to be before `publish` childProcess.exec( `npm --userconfig ${__dirname}/test-registry.npmrc publish ${tarballPath}`, @@ -19,14 +21,10 @@ packageTarballPaths.forEach(tarballPath => { cwd: repositoryRoot, // Can't use __dirname here because npm would try to publish `@sentry-internal/e2e-tests` encoding: 'utf8', }, - (err, stdout, stderr) => { - // eslint-disable-next-line no-console - console.log(stdout); - // eslint-disable-next-line no-console - console.log(stderr); + err => { if (err) { // eslint-disable-next-line no-console - console.error(err); + console.error(`Error publishing tarball ${tarballPath}`, err); process.exit(1); } }, diff --git a/dev-packages/e2e-tests/test-applications/nextjs-13/tests/server/excluded-api-endpoints.test.ts b/dev-packages/e2e-tests/test-applications/nextjs-13/tests/server/excluded-api-endpoints.test.ts index 63082fee6e07..b328f35d0721 100644 --- a/dev-packages/e2e-tests/test-applications/nextjs-13/tests/server/excluded-api-endpoints.test.ts +++ b/dev-packages/e2e-tests/test-applications/nextjs-13/tests/server/excluded-api-endpoints.test.ts @@ -1,7 +1,9 @@ import { expect, test } from '@playwright/test'; import { waitForTransaction } from '@sentry-internal/test-utils'; -test('should not automatically create transactions for routes that were excluded from auto wrapping (string)', async ({ +// TODO(lforst): This cannot make it into production - Make sure to fix this test +// The problem is that if we are not applying build time instrumentation Next.js will still emit spans (which is fine, but we need to find a different way of testing that build time instrumentation is successfully disabled - maybe with an attribute or something if build-time instrumentation is applied) +test.skip('should not automatically create transactions for routes that were excluded from auto wrapping (string)', async ({ request, }) => { const transactionPromise = waitForTransaction('nextjs-13', async transactionEvent => { @@ -23,7 +25,9 @@ test('should not automatically create transactions for routes that were excluded expect(transactionPromiseReceived).toBe(false); }); -test('should not automatically create transactions for routes that were excluded from auto wrapping (regex)', async ({ +// TODO(lforst): This cannot make it into production - Make sure to fix this test +// The problem is that if we are not applying build time instrumentation Next.js will still emit spans (which is fine, but we need to find a different way of testing that build time instrumentation is successfully disabled - maybe with an attribute or something if build-time instrumentation is applied) +test.skip('should not automatically create transactions for routes that were excluded from auto wrapping (regex)', async ({ request, }) => { const transactionPromise = waitForTransaction('nextjs-13', async transactionEvent => { diff --git a/dev-packages/e2e-tests/test-applications/nextjs-app-dir/app/layout.tsx b/dev-packages/e2e-tests/test-applications/nextjs-app-dir/app/layout.tsx index d2aae8c9cd8d..15c3b9789ee3 100644 --- a/dev-packages/e2e-tests/test-applications/nextjs-app-dir/app/layout.tsx +++ b/dev-packages/e2e-tests/test-applications/nextjs-app-dir/app/layout.tsx @@ -27,7 +27,9 @@ export default function Layout({ children }: { children: React.ReactNode }) { /server-component/parameter/42
  • - /server-component/parameter/foo/bar/baz + + /server-component/parameter/foo/bar/baz +
  • /not-found diff --git a/packages/nextjs/src/common/pages-router-instrumentation/wrapApiHandlerWithSentry.ts b/packages/nextjs/src/common/pages-router-instrumentation/wrapApiHandlerWithSentry.ts index 6740ca5b0d11..ed920eee58ba 100644 --- a/packages/nextjs/src/common/pages-router-instrumentation/wrapApiHandlerWithSentry.ts +++ b/packages/nextjs/src/common/pages-router-instrumentation/wrapApiHandlerWithSentry.ts @@ -3,18 +3,17 @@ import { SEMANTIC_ATTRIBUTE_SENTRY_SOURCE, captureException, continueTrace, - getActiveSpan, - getRootSpan, setHttpStatus, startSpanManual, withIsolationScope, } from '@sentry/core'; -import { isString, logger, objectify, vercelWaitUntil } from '@sentry/utils'; +import { isString, logger, objectify } from '@sentry/utils'; + +import { vercelWaitUntil } from '@sentry/utils'; import type { NextApiRequest } from 'next'; -import { TRANSACTION_ATTR_SHOULD_DROP_TRANSACTION } from '../span-attributes-with-logic-attached'; import type { AugmentedNextApiResponse, NextApiHandler } from '../types'; import { flushSafelyWithTimeout } from '../utils/responseEnd'; -import { escapeNextjsTracing } from '../utils/tracingUtils'; +import { dropNextjsRootContext, escapeNextjsTracing } from '../utils/tracingUtils'; export type AugmentedNextApiRequest = NextApiRequest & { __withSentry_applied__?: boolean; @@ -29,21 +28,13 @@ export type AugmentedNextApiRequest = NextApiRequest & { * @returns The wrapped handler which will always return a Promise. */ export function wrapApiHandlerWithSentry(apiHandler: NextApiHandler, parameterizedRoute: string): NextApiHandler { - // Since the API route handler spans emitted by Next.js are super buggy with completely wrong timestamps - // (fix pending at the time of writing this: https://github.com/vercel/next.js/pull/70908) we want to intentionally - // drop them. In the future, when Next.js' OTEL instrumentation is in a high-quality place we can potentially think - // about keeping them. - const nextJsOwnedSpan = getActiveSpan(); - if (nextJsOwnedSpan) { - getRootSpan(nextJsOwnedSpan)?.setAttribute(TRANSACTION_ATTR_SHOULD_DROP_TRANSACTION, true); - } - return new Proxy(apiHandler, { apply: ( wrappingTarget, thisArg, args: [AugmentedNextApiRequest | undefined, AugmentedNextApiResponse | undefined], ) => { + dropNextjsRootContext(); return escapeNextjsTracing(() => { const [req, res] = args; diff --git a/packages/nextjs/src/common/pages-router-instrumentation/wrapApiHandlerWithSentryVercelCrons.ts b/packages/nextjs/src/common/pages-router-instrumentation/wrapApiHandlerWithSentryVercelCrons.ts index 20ed01d51e94..5ca29b338cda 100644 --- a/packages/nextjs/src/common/pages-router-instrumentation/wrapApiHandlerWithSentryVercelCrons.ts +++ b/packages/nextjs/src/common/pages-router-instrumentation/wrapApiHandlerWithSentryVercelCrons.ts @@ -9,7 +9,7 @@ type EdgeRequest = { }; /** - * Wraps a function with Sentry crons instrumentation by automaticaly sending check-ins for the given Vercel crons config. + * Wraps a function with Sentry crons instrumentation by automatically sending check-ins for the given Vercel crons config. */ // eslint-disable-next-line @typescript-eslint/no-explicit-any export function wrapApiHandlerWithSentryVercelCrons any>( diff --git a/packages/nextjs/src/common/pages-router-instrumentation/wrapPageComponentWithSentry.ts b/packages/nextjs/src/common/pages-router-instrumentation/wrapPageComponentWithSentry.ts index 75c176de8fac..2914366f9a6b 100644 --- a/packages/nextjs/src/common/pages-router-instrumentation/wrapPageComponentWithSentry.ts +++ b/packages/nextjs/src/common/pages-router-instrumentation/wrapPageComponentWithSentry.ts @@ -1,6 +1,6 @@ import { captureException, getCurrentScope, withIsolationScope } from '@sentry/core'; import { extractTraceparentData } from '@sentry/utils'; -import { escapeNextjsTracing } from '../utils/tracingUtils'; +import { dropNextjsRootContext, escapeNextjsTracing } from '../utils/tracingUtils'; interface FunctionComponent { (...args: unknown[]): unknown; @@ -25,6 +25,7 @@ export function wrapPageComponentWithSentry(pageComponent: FunctionComponent | C if (isReactClassComponent(pageComponent)) { return class SentryWrappedPageComponent extends pageComponent { public render(...args: unknown[]): unknown { + dropNextjsRootContext(); return escapeNextjsTracing(() => { return withIsolationScope(() => { const scope = getCurrentScope(); @@ -62,6 +63,7 @@ export function wrapPageComponentWithSentry(pageComponent: FunctionComponent | C } else if (typeof pageComponent === 'function') { return new Proxy(pageComponent, { apply(target, thisArg, argArray: [{ _sentryTraceData?: string } | undefined]) { + dropNextjsRootContext(); return escapeNextjsTracing(() => { return withIsolationScope(() => { const scope = getCurrentScope(); diff --git a/packages/nextjs/src/common/utils/tracingUtils.ts b/packages/nextjs/src/common/utils/tracingUtils.ts index b996b6af1877..ff57fcae3acc 100644 --- a/packages/nextjs/src/common/utils/tracingUtils.ts +++ b/packages/nextjs/src/common/utils/tracingUtils.ts @@ -1,7 +1,8 @@ -import { Scope, startNewTrace } from '@sentry/core'; +import { Scope, getActiveSpan, getRootSpan, spanToJSON, startNewTrace } from '@sentry/core'; import type { PropagationContext } from '@sentry/types'; import { GLOBAL_OBJ, logger } from '@sentry/utils'; import { DEBUG_BUILD } from '../debug-build'; +import { TRANSACTION_ATTR_SHOULD_DROP_TRANSACTION } from '../span-attributes-with-logic-attached'; const commonPropagationContextMap = new WeakMap(); @@ -92,3 +93,19 @@ export function escapeNextjsTracing(cb: () => T): T { }); } } + +/** + * Ideally this function never lands in the develop branch. + * + * Drops the entire span tree this function was called in, if it was a span tree created by Next.js. + */ +export function dropNextjsRootContext(): void { + const nextJsOwnedSpan = getActiveSpan(); + if (nextJsOwnedSpan) { + const rootSpan = getRootSpan(nextJsOwnedSpan); + const rootSpanAttributes = spanToJSON(rootSpan).data; + if (rootSpanAttributes?.['next.span_type']) { + getRootSpan(nextJsOwnedSpan)?.setAttribute(TRANSACTION_ATTR_SHOULD_DROP_TRANSACTION, true); + } + } +} diff --git a/packages/nextjs/src/common/utils/wrapperUtils.ts b/packages/nextjs/src/common/utils/wrapperUtils.ts index ff04aebbd3ed..ddde0be63a18 100644 --- a/packages/nextjs/src/common/utils/wrapperUtils.ts +++ b/packages/nextjs/src/common/utils/wrapperUtils.ts @@ -17,7 +17,7 @@ import type { Span } from '@sentry/types'; import { isString, vercelWaitUntil } from '@sentry/utils'; import { autoEndSpanOnResponseEnd, flushSafelyWithTimeout } from './responseEnd'; -import { commonObjectToIsolationScope, escapeNextjsTracing } from './tracingUtils'; +import { commonObjectToIsolationScope, dropNextjsRootContext, escapeNextjsTracing } from './tracingUtils'; declare module 'http' { interface IncomingMessage { @@ -93,6 +93,7 @@ export function withTracedServerSideDataFetcher Pr this: unknown, ...args: Parameters ): Promise<{ data: ReturnType; sentryTrace?: string; baggage?: string }> { + dropNextjsRootContext(); return escapeNextjsTracing(() => { const isolationScope = commonObjectToIsolationScope(req); return withIsolationScope(isolationScope, () => { diff --git a/packages/nextjs/src/common/withServerActionInstrumentation.ts b/packages/nextjs/src/common/withServerActionInstrumentation.ts index 0b8d3b6d7c60..d1a37a7cab76 100644 --- a/packages/nextjs/src/common/withServerActionInstrumentation.ts +++ b/packages/nextjs/src/common/withServerActionInstrumentation.ts @@ -1,16 +1,20 @@ import { SEMANTIC_ATTRIBUTE_SENTRY_SOURCE, SPAN_STATUS_ERROR, + captureException, + continueTrace, + getClient, getIsolationScope, + handleCallbackErrors, + startSpan, withIsolationScope, } from '@sentry/core'; -import { captureException, continueTrace, getClient, handleCallbackErrors, startSpan } from '@sentry/core'; import { logger, vercelWaitUntil } from '@sentry/utils'; import { DEBUG_BUILD } from './debug-build'; import { isNotFoundNavigationError, isRedirectNavigationError } from './nextNavigationErrorUtils'; import { flushSafelyWithTimeout } from './utils/responseEnd'; -import { escapeNextjsTracing } from './utils/tracingUtils'; +import { dropNextjsRootContext, escapeNextjsTracing } from './utils/tracingUtils'; interface Options { formData?: FormData; @@ -64,6 +68,7 @@ async function withServerActionInstrumentationImplementation> { + dropNextjsRootContext(); return escapeNextjsTracing(() => { return withIsolationScope(async isolationScope => { const sendDefaultPii = getClient()?.getOptions().sendDefaultPii; diff --git a/packages/nextjs/src/common/wrapGenerationFunctionWithSentry.ts b/packages/nextjs/src/common/wrapGenerationFunctionWithSentry.ts index ec2636ce2aac..5c9b2506ecee 100644 --- a/packages/nextjs/src/common/wrapGenerationFunctionWithSentry.ts +++ b/packages/nextjs/src/common/wrapGenerationFunctionWithSentry.ts @@ -50,9 +50,6 @@ export function wrapGenerationFunctionWithSentry a const rootSpan = getRootSpan(activeSpan); const { scope } = getCapturedScopesOnSpan(rootSpan); setCapturedScopesOnSpan(rootSpan, scope ?? new Scope(), isolationScope); - - // We mark the root span as an app router span so we can allow-list it in our span processor that would normally filter out all Next.js transactions/spans - rootSpan.setAttribute('sentry.rsc', true); } let data: Record | undefined = undefined; diff --git a/packages/nextjs/src/common/wrapRouteHandlerWithSentry.ts b/packages/nextjs/src/common/wrapRouteHandlerWithSentry.ts index ad70c865dedf..a789cb80aadf 100644 --- a/packages/nextjs/src/common/wrapRouteHandlerWithSentry.ts +++ b/packages/nextjs/src/common/wrapRouteHandlerWithSentry.ts @@ -51,7 +51,6 @@ export function wrapRouteHandlerWithSentry any>( 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); } diff --git a/packages/nextjs/src/common/wrapServerComponentWithSentry.ts b/packages/nextjs/src/common/wrapServerComponentWithSentry.ts index 8ea7fdededf2..c4bbde29eb53 100644 --- a/packages/nextjs/src/common/wrapServerComponentWithSentry.ts +++ b/packages/nextjs/src/common/wrapServerComponentWithSentry.ts @@ -44,9 +44,6 @@ export function wrapServerComponentWithSentry any> const rootSpan = getRootSpan(activeSpan); const { scope } = getCapturedScopesOnSpan(rootSpan); setCapturedScopesOnSpan(rootSpan, scope ?? new Scope(), isolationScope); - - // We mark the root span as an app router span so we can allow-list it in our span processor that would normally filter out all Next.js transactions/spans - rootSpan.setAttribute('sentry.rsc', true); } const headersDict = context.headers ? winterCGHeadersToDict(context.headers) : undefined; diff --git a/packages/nextjs/src/config/types.ts b/packages/nextjs/src/config/types.ts index d2e78d87f4ae..63f678cdbc66 100644 --- a/packages/nextjs/src/config/types.ts +++ b/packages/nextjs/src/config/types.ts @@ -409,12 +409,15 @@ export type SentryBuildOptions = { autoInstrumentAppDirectory?: boolean; /** - * Exclude certain serverside API routes or pages from being instrumented with Sentry. This option takes an array of - * strings or regular expressions. This options also affects pages in the `app` directory. + * Exclude certain serverside API routes or pages from being instrumented with Sentry during build-time. This option + * takes an array of strings or regular expressions. This options also affects pages in the `app` directory. * * NOTE: Pages should be specified as routes (`/animals` or `/api/animals/[animalType]/habitat`), not filepaths * (`pages/animals/index.js` or `.\src\pages\api\animals\[animalType]\habitat.tsx`), and strings must be be a full, * exact match. + * + * Notice: If you build Next.js with turbopack, the Sentry SDK will no longer apply build-time instrumentation and + * purely rely on Next.js telemetry features, meaning that this option will effectively no-op. */ excludeServerRoutes?: Array; diff --git a/packages/nextjs/src/server/index.ts b/packages/nextjs/src/server/index.ts index fc133410691d..168288e081fe 100644 --- a/packages/nextjs/src/server/index.ts +++ b/packages/nextjs/src/server/index.ts @@ -12,8 +12,8 @@ import { setCapturedScopesOnSpan, spanToJSON, } from '@sentry/core'; -import { getDefaultIntegrations, httpIntegration, init as nodeInit } from '@sentry/node'; import type { NodeClient, NodeOptions } from '@sentry/node'; +import { getDefaultIntegrations, httpIntegration, init as nodeInit } from '@sentry/node'; import { GLOBAL_OBJ, extractTraceparentData, logger, stripUrlQueryAndFragment } from '@sentry/utils'; import { context } from '@opentelemetry/api'; @@ -45,22 +45,6 @@ const globalWithInjectedValues = GLOBAL_OBJ as typeof GLOBAL_OBJ & { __sentryRewritesTunnelPath__?: string; }; -// https://github.com/lforst/nextjs-fork/blob/9051bc44d969a6e0ab65a955a2fc0af522a83911/packages/next/src/server/lib/trace/constants.ts#L11 -const NEXTJS_SPAN_NAME_PREFIXES = [ - 'BaseServer.', - 'LoadComponents.', - 'NextServer.', - 'createServer.', - 'startServer.', - 'NextNodeServer.', - 'Render.', - 'AppRender.', - 'Router.', - 'Node.', - 'AppRouteRouteHandlers.', - 'ResolveMetadata.', -]; - /** * A passthrough error boundary for the server that doesn't depend on any react. Error boundaries don't catch SSR errors * so they should simply be a passthrough. @@ -155,25 +139,7 @@ export function init(options: NodeOptions): NodeClient | undefined { applySdkMetadata(opts, 'nextjs', ['nextjs', 'node']); const client = nodeInit(opts); - client?.on('beforeSampling', ({ spanAttributes, spanName, parentSampled, parentContext }, samplingDecision) => { - // We allowlist the "BaseServer.handleRequest" span, since that one is responsible for App Router requests, which are actually useful for us. - // HOWEVER, that span is not only responsible for App Router requests, which is why we additionally filter for certain transactions in an - // event processor further below. - if (spanAttributes['next.span_type'] === 'BaseServer.handleRequest') { - return; - } - - // If we encounter a span emitted by Next.js, we do not want to sample it - // The reason for this is that the data quality of the spans varies, it is different per version of Next, - // and we need to keep our manual instrumentation around for the edge runtime anyhow. - // BUT we only do this if we don't have a parent span with a sampling decision yet (or if the parent is remote) - if ( - (spanAttributes['next.span_type'] || NEXTJS_SPAN_NAME_PREFIXES.some(prefix => spanName.startsWith(prefix))) && - (parentSampled === undefined || parentContext?.isRemote) - ) { - samplingDecision.decision = false; - } - + client?.on('beforeSampling', ({ spanAttributes }, samplingDecision) => { // There are situations where the Next.js Node.js server forwards requests for the Edge Runtime server (e.g. in // middleware) and this causes spans for Sentry ingest requests to be created. These are not exempt from our tracing // because we didn't get the chance to do `suppressTracing`, since this happens outside of userland. @@ -198,7 +164,7 @@ export function init(options: NodeOptions): NodeClient | undefined { // What we do in this glorious piece of code, is hoist any information about parameterized routes from spans emitted // by Next.js via the `next.route` attribute, up to the transaction by setting the http.route attribute. - if (spanAttributes?.['next.route']) { + if (typeof spanAttributes?.['next.route'] === 'string') { const rootSpan = getRootSpan(span); const rootSpanAttributes = spanToJSON(rootSpan).data; @@ -208,26 +174,18 @@ export function init(options: NodeOptions): NodeClient | undefined { (rootSpanAttributes?.[ATTR_HTTP_REQUEST_METHOD] || rootSpanAttributes?.[SEMATTRS_HTTP_METHOD]) && !rootSpanAttributes?.[ATTR_HTTP_ROUTE] ) { - rootSpan.setAttribute(ATTR_HTTP_ROUTE, spanAttributes['next.route']); + const route = spanAttributes['next.route'].replace(/\/route$/, ''); + rootSpan.updateName(route); + rootSpan.setAttribute(ATTR_HTTP_ROUTE, route); } } // We want to skip span data inference for any spans generated by Next.js. Reason being that Next.js emits spans // with patterns (e.g. http.server spans) that will produce confusing data. if (spanAttributes?.['next.span_type'] !== undefined) { - span.setAttribute('sentry.skip_span_data_inference', true); span.setAttribute(SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN, 'auto'); } - // 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') { - 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. - } - } - // We want to fork the isolation scope for incoming requests if (spanAttributes?.['next.span_type'] === 'BaseServer.handleRequest' && span === getRootSpan(span)) { const scopes = getCapturedScopesOnSpan(span); @@ -255,17 +213,6 @@ 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` 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') && - event.contexts?.trace?.data?.['sentry.rsc'] !== true && - event.contexts?.trace?.data?.['sentry.route_handler'] !== true - ) { - return null; - } - // Filter out transactions for requests to the tunnel route if ( globalWithInjectedValues.__sentryRewritesTunnelPath__ && @@ -341,24 +288,10 @@ export function init(options: NodeOptions): NodeClient | undefined { getGlobalScope().addEventProcessor( Object.assign( (event => { - // Sometimes, the HTTP integration will not work, causing us not to properly set an op for spans generated by - // 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.contexts.trace.data = event.contexts.trace.data || {}; - event.contexts.trace.data[SEMANTIC_ATTRIBUTE_SENTRY_OP] = 'http.server'; - event.contexts.trace.op = 'http.server'; - } - // Enhance route handler transactions if ( event.type === 'transaction' && - (event.contexts?.trace?.data?.['sentry.route_handler'] === true || - event.contexts?.trace?.data?.['sentry.rsc'] === true) + event.contexts?.trace?.data?.['next.span_type'] === 'BaseServer.handleRequest' ) { event.contexts.trace.data = event.contexts.trace.data || {}; event.contexts.trace.data[SEMANTIC_ATTRIBUTE_SENTRY_OP] = 'http.server'; @@ -368,11 +301,11 @@ export function init(options: NodeOptions): NodeClient | undefined { event.transaction = stripUrlQueryAndFragment(event.transaction); } - 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$/, '')}`; + // eslint-disable-next-line deprecation/deprecation + const method = event.contexts.trace.data[SEMATTRS_HTTP_METHOD]; + const route = event.contexts.trace.data[ATTR_HTTP_ROUTE]; + if (typeof method === 'string' && typeof route === 'string') { + event.transaction = `${method} ${route.replace(/\/route$/, '')}`; event.contexts.trace.data[SEMANTIC_ATTRIBUTE_SENTRY_SOURCE] = 'route'; } } @@ -399,6 +332,11 @@ export function init(options: NodeOptions): NodeClient | undefined { } } + // Next.js 13 sometimes names the root transactions like this containing useless tracing. + if (event.type === 'transaction' && event.transaction === 'NextServer.getRequestHandler') { + return null; + } + return event; }) satisfies EventProcessor, { id: 'NextjsTransactionEnhancer' }, diff --git a/packages/opentelemetry/src/spanExporter.ts b/packages/opentelemetry/src/spanExporter.ts index 18c935863b75..d00319ec2c98 100644 --- a/packages/opentelemetry/src/spanExporter.ts +++ b/packages/opentelemetry/src/spanExporter.ts @@ -345,7 +345,6 @@ function removeSentryAttributes(data: Record): Record { source: 'route', }, ], - [ - "should not do any data parsing when the 'sentry.skip_span_data_inference' attribute is set", - { - 'sentry.skip_span_data_inference': true, - - // All of these should be ignored - [SEMATTRS_HTTP_METHOD]: 'GET', - [SEMATTRS_DB_SYSTEM]: 'mysql', - [SEMATTRS_DB_STATEMENT]: 'SELECT * from users', - }, - 'test name', - undefined, - { - op: undefined, - description: 'test name', - source: 'custom', - data: { - 'sentry.skip_span_data_inference': undefined, - }, - }, - ], ])('%s', (_, attributes, name, kind, expected) => { const actual = parseSpanDescription({ attributes, kind, name } as unknown as Span); expect(actual).toEqual(expected);