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);