From fe38c48f26b1d1ab1e8131e1b5e7e9646dd13c74 Mon Sep 17 00:00:00 2001 From: Lukas Stracke Date: Fri, 11 Sep 2026 11:24:32 +0200 Subject: [PATCH 1/3] feat(nextjs): Emit low cardinality `function` span names for server components The `resolve segment modules` span Next.js emits per page/layout carries `sentry.op: 'function'`, so its name has to follow the conventions rule for that op (`code.function.name`). With span streaming it is now `Page` or `Layout`; with `traceLifecycle: 'static'` the descriptive name is unchanged. `sentry.description` carries the old text in both lifecycles, because `resolve page server component ""` cannot be expressed as a conventions name rule. The route the name no longer carries stays on `http.route`, next to the existing `sentry.nextjs.ssr.function.*` attributes. Co-Authored-By: Claude Opus 5 (1M context) --- .../tests/server-components.test.ts | 114 ++++++++++++---- .../tests/server-components.test.ts | 114 ++++++++++++---- .../tests/server-components.test.ts | 126 ++++++++++++++---- .../nextjs-16/tests/server-components.test.ts | 116 ++++++++++++---- .../connected-servercomponent-trace.test.ts | 116 ++++++++++++---- .../tests/server-components.test.ts | 10 +- .../nextjs/src/common/utils/tracingUtils.ts | 35 ++++- .../test/common/utils/tracingUtils.test.ts | 125 +++++++++++++++++ 8 files changed, 630 insertions(+), 126 deletions(-) create mode 100644 packages/nextjs/test/common/utils/tracingUtils.test.ts diff --git a/dev-packages/e2e-tests/test-applications/nextjs-16-bun/tests/server-components.test.ts b/dev-packages/e2e-tests/test-applications/nextjs-16-bun/tests/server-components.test.ts index 0bc599f836f2..c674845c4234 100644 --- a/dev-packages/e2e-tests/test-applications/nextjs-16-bun/tests/server-components.test.ts +++ b/dev-packages/e2e-tests/test-applications/nextjs-16-bun/tests/server-components.test.ts @@ -1,5 +1,9 @@ import { expect, test } from '@playwright/test'; -import { collectSpanNamesUntilSegment, collectStreamedSpansUntilSegment } from '@sentry-internal/test-utils'; +import { collectStreamedSpansUntilSegment, getSpanOp } from '@sentry-internal/test-utils'; + +// Next.js emits these spans itself. The SDK attaches no op, description or function name to +// them, so asserting `undefined` pins that they stay untouched. +const nextjsSpan = { op: undefined, description: undefined, codeFunctionName: undefined }; test('Sends a span for a request to app router with URL', async ({ page }) => { const spansPromise = collectStreamedSpansUntilSegment( @@ -43,39 +47,103 @@ test('Sends a span for a request to app router with URL', async ({ page }) => { test('Will create spans for every server component and metadata generation functions when visiting a page', async ({ page, }) => { - const spanNamesPromise = collectSpanNamesUntilSegment('nextjs-16-bun', 'GET /nested-layout'); + const spansPromise = collectStreamedSpansUntilSegment('nextjs-16-bun', 'GET /nested-layout'); await page.goto('/nested-layout'); - const spanNames = await spanNamesPromise; + const fullSpans = await spansPromise; + const spans = fullSpans.map(span => ({ + name: span.name, + op: getSpanOp(span), + description: span.attributes['sentry.description']?.value, + codeFunctionName: span.attributes['code.function.name']?.value, + })); - expect(spanNames).toContainEqual('render route (app) /nested-layout'); - expect(spanNames).toContainEqual('build component tree'); - expect(spanNames).toContainEqual('resolve root layout server component'); - expect(spanNames).toContainEqual('resolve layout server component "(nested-layout)"'); - expect(spanNames).toContainEqual('resolve layout server component "nested-layout"'); - expect(spanNames).toContainEqual('resolve page server component "/nested-layout"'); - expect(spanNames).toContainEqual('generateMetadata /(nested-layout)/nested-layout/page'); - expect(spanNames).toContainEqual('start response'); + expect(spans).toContainEqual({ ...nextjsSpan, name: 'render route (app) /nested-layout' }); + expect(spans).toContainEqual({ ...nextjsSpan, name: 'build component tree' }); + // Server component spans: the name is the low-cardinality `code.function.name`, and the + // segment each one resolved for is on the description. + expect(spans).toContainEqual({ + name: 'Layout', + op: 'function', + description: 'resolve root layout server component', + codeFunctionName: 'Layout', + }); + expect(spans).toContainEqual({ + name: 'Layout', + op: 'function', + description: 'resolve layout server component "(nested-layout)"', + codeFunctionName: 'Layout', + }); + expect(spans).toContainEqual({ + name: 'Layout', + op: 'function', + description: 'resolve layout server component "nested-layout"', + codeFunctionName: 'Layout', + }); + expect(spans).toContainEqual({ + name: 'Page', + op: 'function', + description: 'resolve page server component "/nested-layout"', + codeFunctionName: 'Page', + }); + expect(spans).toContainEqual({ ...nextjsSpan, name: 'generateMetadata /(nested-layout)/nested-layout/page' }); + expect(spans).toContainEqual({ ...nextjsSpan, name: 'start response' }); }); test('Will create spans for every server component and metadata generation functions when visiting a dynamic page', async ({ page, }) => { - const spanNamesPromise = collectSpanNamesUntilSegment('nextjs-16-bun', 'GET /nested-layout/[dynamic]'); + const spansPromise = collectStreamedSpansUntilSegment('nextjs-16-bun', 'GET /nested-layout/[dynamic]'); await page.goto('/nested-layout/123'); - const spanNames = await spanNamesPromise; + const fullSpans = await spansPromise; + const spans = fullSpans.map(span => ({ + name: span.name, + op: getSpanOp(span), + description: span.attributes['sentry.description']?.value, + codeFunctionName: span.attributes['code.function.name']?.value, + })); - expect(spanNames).toContainEqual('resolve page components'); - expect(spanNames).toContainEqual('render route (app) /nested-layout/[dynamic]'); - expect(spanNames).toContainEqual('build component tree'); - expect(spanNames).toContainEqual('resolve root layout server component'); - expect(spanNames).toContainEqual('resolve layout server component "(nested-layout)"'); - expect(spanNames).toContainEqual('resolve layout server component "nested-layout"'); - expect(spanNames).toContainEqual('resolve layout server component "[dynamic]"'); - expect(spanNames).toContainEqual('resolve page server component "/nested-layout/[dynamic]"'); - expect(spanNames).toContainEqual('generateMetadata /(nested-layout)/nested-layout/[dynamic]/page'); - expect(spanNames).toContainEqual('start response'); + expect(spans).toContainEqual({ ...nextjsSpan, name: 'resolve page components' }); + expect(spans).toContainEqual({ ...nextjsSpan, name: 'render route (app) /nested-layout/[dynamic]' }); + expect(spans).toContainEqual({ ...nextjsSpan, name: 'build component tree' }); + // Server component spans: the name is the low-cardinality `code.function.name`, and the + // segment each one resolved for is on the description. + expect(spans).toContainEqual({ + name: 'Layout', + op: 'function', + description: 'resolve root layout server component', + codeFunctionName: 'Layout', + }); + expect(spans).toContainEqual({ + name: 'Layout', + op: 'function', + description: 'resolve layout server component "(nested-layout)"', + codeFunctionName: 'Layout', + }); + expect(spans).toContainEqual({ + name: 'Layout', + op: 'function', + description: 'resolve layout server component "nested-layout"', + codeFunctionName: 'Layout', + }); + expect(spans).toContainEqual({ + name: 'Layout', + op: 'function', + description: 'resolve layout server component "[dynamic]"', + codeFunctionName: 'Layout', + }); + expect(spans).toContainEqual({ + name: 'Page', + op: 'function', + description: 'resolve page server component "/nested-layout/[dynamic]"', + codeFunctionName: 'Page', + }); + expect(spans).toContainEqual({ + ...nextjsSpan, + name: 'generateMetadata /(nested-layout)/nested-layout/[dynamic]/page', + }); + expect(spans).toContainEqual({ ...nextjsSpan, name: 'start response' }); }); diff --git a/dev-packages/e2e-tests/test-applications/nextjs-16-cf-workers/tests/server-components.test.ts b/dev-packages/e2e-tests/test-applications/nextjs-16-cf-workers/tests/server-components.test.ts index 98d242896465..7a34986062dc 100644 --- a/dev-packages/e2e-tests/test-applications/nextjs-16-cf-workers/tests/server-components.test.ts +++ b/dev-packages/e2e-tests/test-applications/nextjs-16-cf-workers/tests/server-components.test.ts @@ -1,5 +1,9 @@ import { expect, test } from '@playwright/test'; -import { collectSpanNamesUntilSegment, collectStreamedSpansUntilSegment } from '@sentry-internal/test-utils'; +import { collectStreamedSpansUntilSegment, getSpanOp } from '@sentry-internal/test-utils'; + +// Next.js emits these spans itself. The SDK attaches no op, description or function name to +// them, so asserting `undefined` pins that they stay untouched. +const nextjsSpan = { op: undefined, description: undefined, codeFunctionName: undefined }; // TODO: Server component tests need SDK adjustments for Cloudflare Workers test.skip('Sends a span for a request to app router with URL', async ({ page }) => { @@ -45,40 +49,104 @@ test.skip('Sends a span for a request to app router with URL', async ({ page }) test.skip('Will create spans for every server component and metadata generation functions when visiting a page', async ({ page, }) => { - const spanNamesPromise = collectSpanNamesUntilSegment('nextjs-16-cf-workers', 'GET /nested-layout'); + const spansPromise = collectStreamedSpansUntilSegment('nextjs-16-cf-workers', 'GET /nested-layout'); await page.goto('/nested-layout'); - const spanNames = await spanNamesPromise; + const fullSpans = await spansPromise; + const spans = fullSpans.map(span => ({ + name: span.name, + op: getSpanOp(span), + description: span.attributes['sentry.description']?.value, + codeFunctionName: span.attributes['code.function.name']?.value, + })); - expect(spanNames).toContainEqual('render route (app) /nested-layout'); - expect(spanNames).toContainEqual('build component tree'); - expect(spanNames).toContainEqual('resolve root layout server component'); - expect(spanNames).toContainEqual('resolve layout server component "(nested-layout)"'); - expect(spanNames).toContainEqual('resolve layout server component "nested-layout"'); - expect(spanNames).toContainEqual('resolve page server component "/nested-layout"'); - expect(spanNames).toContainEqual('generateMetadata /(nested-layout)/nested-layout/page'); - expect(spanNames).toContainEqual('start response'); + expect(spans).toContainEqual({ ...nextjsSpan, name: 'render route (app) /nested-layout' }); + expect(spans).toContainEqual({ ...nextjsSpan, name: 'build component tree' }); + // Server component spans: the name is the low-cardinality `code.function.name`, and the + // segment each one resolved for is on the description. + expect(spans).toContainEqual({ + name: 'Layout', + op: 'function', + description: 'resolve root layout server component', + codeFunctionName: 'Layout', + }); + expect(spans).toContainEqual({ + name: 'Layout', + op: 'function', + description: 'resolve layout server component "(nested-layout)"', + codeFunctionName: 'Layout', + }); + expect(spans).toContainEqual({ + name: 'Layout', + op: 'function', + description: 'resolve layout server component "nested-layout"', + codeFunctionName: 'Layout', + }); + expect(spans).toContainEqual({ + name: 'Page', + op: 'function', + description: 'resolve page server component "/nested-layout"', + codeFunctionName: 'Page', + }); + expect(spans).toContainEqual({ ...nextjsSpan, name: 'generateMetadata /(nested-layout)/nested-layout/page' }); + expect(spans).toContainEqual({ ...nextjsSpan, name: 'start response' }); }); // TODO: Server component span tests need SDK adjustments for Cloudflare Workers test.skip('Will create spans for every server component and metadata generation functions when visiting a dynamic page', async ({ page, }) => { - const spanNamesPromise = collectSpanNamesUntilSegment('nextjs-16-cf-workers', 'GET /nested-layout/[dynamic]'); + const spansPromise = collectStreamedSpansUntilSegment('nextjs-16-cf-workers', 'GET /nested-layout/[dynamic]'); await page.goto('/nested-layout/123'); - const spanNames = await spanNamesPromise; + const fullSpans = await spansPromise; + const spans = fullSpans.map(span => ({ + name: span.name, + op: getSpanOp(span), + description: span.attributes['sentry.description']?.value, + codeFunctionName: span.attributes['code.function.name']?.value, + })); - expect(spanNames).toContainEqual('resolve page components'); - expect(spanNames).toContainEqual('render route (app) /nested-layout/[dynamic]'); - expect(spanNames).toContainEqual('build component tree'); - expect(spanNames).toContainEqual('resolve root layout server component'); - expect(spanNames).toContainEqual('resolve layout server component "(nested-layout)"'); - expect(spanNames).toContainEqual('resolve layout server component "nested-layout"'); - expect(spanNames).toContainEqual('resolve layout server component "[dynamic]"'); - expect(spanNames).toContainEqual('resolve page server component "/nested-layout/[dynamic]"'); - expect(spanNames).toContainEqual('generateMetadata /(nested-layout)/nested-layout/[dynamic]/page'); - expect(spanNames).toContainEqual('start response'); + expect(spans).toContainEqual({ ...nextjsSpan, name: 'resolve page components' }); + expect(spans).toContainEqual({ ...nextjsSpan, name: 'render route (app) /nested-layout/[dynamic]' }); + expect(spans).toContainEqual({ ...nextjsSpan, name: 'build component tree' }); + // Server component spans: the name is the low-cardinality `code.function.name`, and the + // segment each one resolved for is on the description. + expect(spans).toContainEqual({ + name: 'Layout', + op: 'function', + description: 'resolve root layout server component', + codeFunctionName: 'Layout', + }); + expect(spans).toContainEqual({ + name: 'Layout', + op: 'function', + description: 'resolve layout server component "(nested-layout)"', + codeFunctionName: 'Layout', + }); + expect(spans).toContainEqual({ + name: 'Layout', + op: 'function', + description: 'resolve layout server component "nested-layout"', + codeFunctionName: 'Layout', + }); + expect(spans).toContainEqual({ + name: 'Layout', + op: 'function', + description: 'resolve layout server component "[dynamic]"', + codeFunctionName: 'Layout', + }); + expect(spans).toContainEqual({ + name: 'Page', + op: 'function', + description: 'resolve page server component "/nested-layout/[dynamic]"', + codeFunctionName: 'Page', + }); + expect(spans).toContainEqual({ + ...nextjsSpan, + name: 'generateMetadata /(nested-layout)/nested-layout/[dynamic]/page', + }); + expect(spans).toContainEqual({ ...nextjsSpan, name: 'start response' }); }); diff --git a/dev-packages/e2e-tests/test-applications/nextjs-16-streaming/tests/server-components.test.ts b/dev-packages/e2e-tests/test-applications/nextjs-16-streaming/tests/server-components.test.ts index 75973b85c105..2b8427fb2e78 100644 --- a/dev-packages/e2e-tests/test-applications/nextjs-16-streaming/tests/server-components.test.ts +++ b/dev-packages/e2e-tests/test-applications/nextjs-16-streaming/tests/server-components.test.ts @@ -1,7 +1,11 @@ import { expect, test } from '@playwright/test'; -import { collectSpanNamesUntilSegment, getSpanOp, waitForStreamedSpan } from '@sentry-internal/test-utils'; +import { collectStreamedSpansUntilSegment, getSpanOp, waitForStreamedSpan } from '@sentry-internal/test-utils'; import { isDevMode } from './isDevMode'; +// Next.js emits these spans itself. The SDK attaches no op, description or function name to +// them, so asserting `undefined` pins that they stay untouched. +const nextjsSpan = { op: undefined, description: undefined, codeFunctionName: undefined }; + test('Sends a streamed span for a request to app router with URL', async ({ page }) => { test.skip(isDevMode, 'Turbopack intermittently returns 404 for nested dynamic routes in dev mode'); @@ -22,20 +26,58 @@ test('Will create streamed spans for every server component and metadata generat }) => { test.skip(isDevMode, 'Turbopack intermittently returns 404 for nested dynamic routes in dev mode'); - const spanNamesPromise = collectSpanNamesUntilSegment('nextjs-16-streaming', 'GET /nested-layout'); + const spansPromise = collectStreamedSpansUntilSegment('nextjs-16-streaming', 'GET /nested-layout'); await page.goto('/nested-layout'); - const spanNames = await spanNamesPromise; + const fullSpans = await spansPromise; + const spans = fullSpans.map(span => ({ + name: span.name, + op: getSpanOp(span), + description: span.attributes['sentry.description']?.value, + codeFunctionName: span.attributes['code.function.name']?.value, + })); - expect(spanNames).toContainEqual('render route (app) /nested-layout'); - expect(spanNames).toContainEqual('build component tree'); - expect(spanNames).toContainEqual('resolve root layout server component'); - expect(spanNames).toContainEqual('resolve layout server component "(nested-layout)"'); - expect(spanNames).toContainEqual('resolve layout server component "nested-layout"'); - expect(spanNames).toContainEqual('resolve page server component "/nested-layout"'); - expect(spanNames).toContainEqual('generateMetadata /(nested-layout)/nested-layout/page'); - expect(spanNames).toContainEqual('start response'); + expect(spans).toContainEqual({ ...nextjsSpan, name: 'render route (app) /nested-layout' }); + expect(spans).toContainEqual({ ...nextjsSpan, name: 'build component tree' }); + // Server component spans: the name is the low-cardinality `code.function.name`, and the + // segment each one resolved for is on the description. + expect(spans).toContainEqual({ + name: 'Layout', + op: 'function', + description: 'resolve root layout server component', + codeFunctionName: 'Layout', + }); + expect(spans).toContainEqual({ + name: 'Layout', + op: 'function', + description: 'resolve layout server component "(nested-layout)"', + codeFunctionName: 'Layout', + }); + expect(spans).toContainEqual({ + name: 'Layout', + op: 'function', + description: 'resolve layout server component "nested-layout"', + codeFunctionName: 'Layout', + }); + expect(spans).toContainEqual({ + name: 'Page', + op: 'function', + description: 'resolve page server component "/nested-layout"', + codeFunctionName: 'Page', + }); + expect(spans).toContainEqual({ ...nextjsSpan, name: 'generateMetadata /(nested-layout)/nested-layout/page' }); + expect(spans).toContainEqual({ ...nextjsSpan, name: 'start response' }); + + // The route detail that the low-cardinality name no longer carries stays on attributes. + const pageSpan = fullSpans.find( + span => span.attributes['sentry.description']?.value === 'resolve page server component "/nested-layout"', + )!; + expect(pageSpan.attributes).toMatchObject({ + 'sentry.nextjs.ssr.function.type': { value: 'Page', type: 'string' }, + 'sentry.nextjs.ssr.function.route': { value: '/nested-layout', type: 'string' }, + 'http.route': { value: '/nested-layout', type: 'string' }, + }); }); test('Will create streamed spans for every server component and metadata generation functions when visiting a dynamic page', async ({ @@ -43,20 +85,56 @@ test('Will create streamed spans for every server component and metadata generat }) => { test.skip(isDevMode, 'Turbopack intermittently returns 404 for nested dynamic routes in dev mode'); - const spanNamesPromise = collectSpanNamesUntilSegment('nextjs-16-streaming', 'GET /nested-layout/[dynamic]'); + const spansPromise = collectStreamedSpansUntilSegment('nextjs-16-streaming', 'GET /nested-layout/[dynamic]'); await page.goto('/nested-layout/123'); - const spanNames = await spanNamesPromise; - - expect(spanNames).toContainEqual('resolve page components'); - expect(spanNames).toContainEqual('render route (app) /nested-layout/[dynamic]'); - expect(spanNames).toContainEqual('build component tree'); - expect(spanNames).toContainEqual('resolve root layout server component'); - expect(spanNames).toContainEqual('resolve layout server component "(nested-layout)"'); - expect(spanNames).toContainEqual('resolve layout server component "nested-layout"'); - expect(spanNames).toContainEqual('resolve layout server component "[dynamic]"'); - expect(spanNames).toContainEqual('resolve page server component "/nested-layout/[dynamic]"'); - expect(spanNames).toContainEqual('generateMetadata /(nested-layout)/nested-layout/[dynamic]/page'); - expect(spanNames).toContainEqual('start response'); + const fullSpans = await spansPromise; + const spans = fullSpans.map(span => ({ + name: span.name, + op: getSpanOp(span), + description: span.attributes['sentry.description']?.value, + codeFunctionName: span.attributes['code.function.name']?.value, + })); + + expect(spans).toContainEqual({ ...nextjsSpan, name: 'resolve page components' }); + expect(spans).toContainEqual({ ...nextjsSpan, name: 'render route (app) /nested-layout/[dynamic]' }); + expect(spans).toContainEqual({ ...nextjsSpan, name: 'build component tree' }); + // Server component spans: the name is the low-cardinality `code.function.name`, and the + // segment each one resolved for is on the description. + expect(spans).toContainEqual({ + name: 'Layout', + op: 'function', + description: 'resolve root layout server component', + codeFunctionName: 'Layout', + }); + expect(spans).toContainEqual({ + name: 'Layout', + op: 'function', + description: 'resolve layout server component "(nested-layout)"', + codeFunctionName: 'Layout', + }); + expect(spans).toContainEqual({ + name: 'Layout', + op: 'function', + description: 'resolve layout server component "nested-layout"', + codeFunctionName: 'Layout', + }); + expect(spans).toContainEqual({ + name: 'Layout', + op: 'function', + description: 'resolve layout server component "[dynamic]"', + codeFunctionName: 'Layout', + }); + expect(spans).toContainEqual({ + name: 'Page', + op: 'function', + description: 'resolve page server component "/nested-layout/[dynamic]"', + codeFunctionName: 'Page', + }); + expect(spans).toContainEqual({ + ...nextjsSpan, + name: 'generateMetadata /(nested-layout)/nested-layout/[dynamic]/page', + }); + expect(spans).toContainEqual({ ...nextjsSpan, name: 'start response' }); }); diff --git a/dev-packages/e2e-tests/test-applications/nextjs-16/tests/server-components.test.ts b/dev-packages/e2e-tests/test-applications/nextjs-16/tests/server-components.test.ts index bb3380a9e774..b271cfd1ac52 100644 --- a/dev-packages/e2e-tests/test-applications/nextjs-16/tests/server-components.test.ts +++ b/dev-packages/e2e-tests/test-applications/nextjs-16/tests/server-components.test.ts @@ -1,7 +1,11 @@ import { expect, test } from '@playwright/test'; -import { collectSpanNamesUntilSegment, collectStreamedSpansUntilSegment } from '@sentry-internal/test-utils'; +import { collectStreamedSpansUntilSegment, getSpanOp } from '@sentry-internal/test-utils'; import { isTurbopackDevMode } from './isDevMode'; +// Next.js emits these spans itself. The SDK attaches no op, description or function name to +// them, so asserting `undefined` pins that they stay untouched. +const nextjsSpan = { op: undefined, description: undefined, codeFunctionName: undefined }; + test('Sends a span for a request to app router with URL', async ({ page }) => { test.skip(isTurbopackDevMode, 'Turbopack intermittently returns 404 for nested dynamic routes in dev mode'); @@ -46,20 +50,48 @@ test('Sends a span for a request to app router with URL', async ({ page }) => { test('Will create spans for every server component and metadata generation functions when visiting a page', async ({ page, }) => { - const spanNamesPromise = collectSpanNamesUntilSegment('nextjs-16', 'GET /nested-layout'); + const spansPromise = collectStreamedSpansUntilSegment('nextjs-16', 'GET /nested-layout'); await page.goto('/nested-layout'); - const spanNames = await spanNamesPromise; + const fullSpans = await spansPromise; + const spans = fullSpans.map(span => ({ + name: span.name, + op: getSpanOp(span), + description: span.attributes['sentry.description']?.value, + codeFunctionName: span.attributes['code.function.name']?.value, + })); - expect(spanNames).toContainEqual('render route (app) /nested-layout'); - expect(spanNames).toContainEqual('build component tree'); - expect(spanNames).toContainEqual('resolve root layout server component'); - expect(spanNames).toContainEqual('resolve layout server component "(nested-layout)"'); - expect(spanNames).toContainEqual('resolve layout server component "nested-layout"'); - expect(spanNames).toContainEqual('resolve page server component "/nested-layout"'); - expect(spanNames).toContainEqual('generateMetadata /(nested-layout)/nested-layout/page'); - expect(spanNames).toContainEqual('start response'); + expect(spans).toContainEqual({ ...nextjsSpan, name: 'render route (app) /nested-layout' }); + expect(spans).toContainEqual({ ...nextjsSpan, name: 'build component tree' }); + // Server component spans: the name is the low-cardinality `code.function.name`, and the + // segment each one resolved for is on the description. + expect(spans).toContainEqual({ + name: 'Layout', + op: 'function', + description: 'resolve root layout server component', + codeFunctionName: 'Layout', + }); + expect(spans).toContainEqual({ + name: 'Layout', + op: 'function', + description: 'resolve layout server component "(nested-layout)"', + codeFunctionName: 'Layout', + }); + expect(spans).toContainEqual({ + name: 'Layout', + op: 'function', + description: 'resolve layout server component "nested-layout"', + codeFunctionName: 'Layout', + }); + expect(spans).toContainEqual({ + name: 'Page', + op: 'function', + description: 'resolve page server component "/nested-layout"', + codeFunctionName: 'Page', + }); + expect(spans).toContainEqual({ ...nextjsSpan, name: 'generateMetadata /(nested-layout)/nested-layout/page' }); + expect(spans).toContainEqual({ ...nextjsSpan, name: 'start response' }); }); test('Will create spans for every server component and metadata generation functions when visiting a dynamic page', async ({ @@ -67,20 +99,56 @@ test('Will create spans for every server component and metadata generation funct }) => { test.skip(isTurbopackDevMode, 'Turbopack intermittently returns 404 for dynamic routes in dev mode'); - const spanNamesPromise = collectSpanNamesUntilSegment('nextjs-16', 'GET /nested-layout/[dynamic]'); + const spansPromise = collectStreamedSpansUntilSegment('nextjs-16', 'GET /nested-layout/[dynamic]'); await page.goto('/nested-layout/123'); - const spanNames = await spanNamesPromise; - - expect(spanNames).toContainEqual('resolve page components'); - expect(spanNames).toContainEqual('render route (app) /nested-layout/[dynamic]'); - expect(spanNames).toContainEqual('build component tree'); - expect(spanNames).toContainEqual('resolve root layout server component'); - expect(spanNames).toContainEqual('resolve layout server component "(nested-layout)"'); - expect(spanNames).toContainEqual('resolve layout server component "nested-layout"'); - expect(spanNames).toContainEqual('resolve layout server component "[dynamic]"'); - expect(spanNames).toContainEqual('resolve page server component "/nested-layout/[dynamic]"'); - expect(spanNames).toContainEqual('generateMetadata /(nested-layout)/nested-layout/[dynamic]/page'); - expect(spanNames).toContainEqual('start response'); + const fullSpans = await spansPromise; + const spans = fullSpans.map(span => ({ + name: span.name, + op: getSpanOp(span), + description: span.attributes['sentry.description']?.value, + codeFunctionName: span.attributes['code.function.name']?.value, + })); + + expect(spans).toContainEqual({ ...nextjsSpan, name: 'resolve page components' }); + expect(spans).toContainEqual({ ...nextjsSpan, name: 'render route (app) /nested-layout/[dynamic]' }); + expect(spans).toContainEqual({ ...nextjsSpan, name: 'build component tree' }); + // Server component spans: the name is the low-cardinality `code.function.name`, and the + // segment each one resolved for is on the description. + expect(spans).toContainEqual({ + name: 'Layout', + op: 'function', + description: 'resolve root layout server component', + codeFunctionName: 'Layout', + }); + expect(spans).toContainEqual({ + name: 'Layout', + op: 'function', + description: 'resolve layout server component "(nested-layout)"', + codeFunctionName: 'Layout', + }); + expect(spans).toContainEqual({ + name: 'Layout', + op: 'function', + description: 'resolve layout server component "nested-layout"', + codeFunctionName: 'Layout', + }); + expect(spans).toContainEqual({ + name: 'Layout', + op: 'function', + description: 'resolve layout server component "[dynamic]"', + codeFunctionName: 'Layout', + }); + expect(spans).toContainEqual({ + name: 'Page', + op: 'function', + description: 'resolve page server component "/nested-layout/[dynamic]"', + codeFunctionName: 'Page', + }); + expect(spans).toContainEqual({ + ...nextjsSpan, + name: 'generateMetadata /(nested-layout)/nested-layout/[dynamic]/page', + }); + expect(spans).toContainEqual({ ...nextjsSpan, name: 'start response' }); }); diff --git a/dev-packages/e2e-tests/test-applications/nextjs-app-dir/tests/connected-servercomponent-trace.test.ts b/dev-packages/e2e-tests/test-applications/nextjs-app-dir/tests/connected-servercomponent-trace.test.ts index fb438ba98825..049f47737043 100644 --- a/dev-packages/e2e-tests/test-applications/nextjs-app-dir/tests/connected-servercomponent-trace.test.ts +++ b/dev-packages/e2e-tests/test-applications/nextjs-app-dir/tests/connected-servercomponent-trace.test.ts @@ -1,45 +1,113 @@ import { expect, test } from '@playwright/test'; -import { collectSpanNamesUntilSegment } from '@sentry-internal/test-utils'; +import { collectStreamedSpansUntilSegment, getSpanOp } from '@sentry-internal/test-utils'; + +// Next.js emits these spans itself. The SDK attaches no op, description or function name to +// them, so asserting `undefined` pins that they stay untouched. +const nextjsSpan = { op: undefined, description: undefined, codeFunctionName: undefined }; test('Will create spans for every server component and metadata generation functions when visiting a page', async ({ page, }) => { - const spanNamesPromise = collectSpanNamesUntilSegment('nextjs-app-dir', 'GET /nested-layout'); + const spansPromise = collectStreamedSpansUntilSegment('nextjs-app-dir', 'GET /nested-layout'); await page.goto('/nested-layout'); - const spanNames = await spanNamesPromise; + const fullSpans = await spansPromise; + const spans = fullSpans.map(span => ({ + name: span.name, + op: getSpanOp(span), + description: span.attributes['sentry.description']?.value, + codeFunctionName: span.attributes['code.function.name']?.value, + })); - expect(spanNames).toContainEqual('render route (app) /nested-layout'); - expect(spanNames).toContainEqual('generateMetadata /(nested-layout)/nested-layout/page'); + expect(spans).toContainEqual({ ...nextjsSpan, name: 'render route (app) /nested-layout' }); + expect(spans).toContainEqual({ ...nextjsSpan, name: 'generateMetadata /(nested-layout)/nested-layout/page' }); - expect(spanNames).toContainEqual('resolve page components'); - expect(spanNames).toContainEqual('build component tree'); - expect(spanNames).toContainEqual('resolve root layout server component'); - expect(spanNames).toContainEqual('resolve layout server component "(nested-layout)"'); - expect(spanNames).toContainEqual('resolve layout server component "nested-layout"'); - expect(spanNames).toContainEqual('resolve page server component "/nested-layout"'); - expect(spanNames).toContainEqual('start response'); + expect(spans).toContainEqual({ ...nextjsSpan, name: 'resolve page components' }); + expect(spans).toContainEqual({ ...nextjsSpan, name: 'build component tree' }); + // Server component spans: the name is the low-cardinality `code.function.name`, and the + // segment each one resolved for is on the description. + expect(spans).toContainEqual({ + name: 'Layout', + op: 'function', + description: 'resolve root layout server component', + codeFunctionName: 'Layout', + }); + expect(spans).toContainEqual({ + name: 'Layout', + op: 'function', + description: 'resolve layout server component "(nested-layout)"', + codeFunctionName: 'Layout', + }); + expect(spans).toContainEqual({ + name: 'Layout', + op: 'function', + description: 'resolve layout server component "nested-layout"', + codeFunctionName: 'Layout', + }); + expect(spans).toContainEqual({ + name: 'Page', + op: 'function', + description: 'resolve page server component "/nested-layout"', + codeFunctionName: 'Page', + }); + expect(spans).toContainEqual({ ...nextjsSpan, name: 'start response' }); }); test('Will create spans for every server component and metadata generation functions when visiting a dynamic page', async ({ page, }) => { - const spanNamesPromise = collectSpanNamesUntilSegment('nextjs-app-dir', 'GET /nested-layout/[dynamic]'); + const spansPromise = collectStreamedSpansUntilSegment('nextjs-app-dir', 'GET /nested-layout/[dynamic]'); await page.goto('/nested-layout/123'); - const spanNames = await spanNamesPromise; + const fullSpans = await spansPromise; + const spans = fullSpans.map(span => ({ + name: span.name, + op: getSpanOp(span), + description: span.attributes['sentry.description']?.value, + codeFunctionName: span.attributes['code.function.name']?.value, + })); - expect(spanNames).toContainEqual('render route (app) /nested-layout/[dynamic]'); - expect(spanNames).toContainEqual('generateMetadata /(nested-layout)/nested-layout/[dynamic]/page'); + expect(spans).toContainEqual({ ...nextjsSpan, name: 'render route (app) /nested-layout/[dynamic]' }); + expect(spans).toContainEqual({ + ...nextjsSpan, + name: 'generateMetadata /(nested-layout)/nested-layout/[dynamic]/page', + }); - expect(spanNames).toContainEqual('resolve page components'); - expect(spanNames).toContainEqual('build component tree'); - expect(spanNames).toContainEqual('resolve root layout server component'); - expect(spanNames).toContainEqual('resolve layout server component "(nested-layout)"'); - expect(spanNames).toContainEqual('resolve layout server component "nested-layout"'); - expect(spanNames).toContainEqual('resolve layout server component "[dynamic]"'); - expect(spanNames).toContainEqual('resolve page server component "/nested-layout/[dynamic]"'); - expect(spanNames).toContainEqual('start response'); + expect(spans).toContainEqual({ ...nextjsSpan, name: 'resolve page components' }); + expect(spans).toContainEqual({ ...nextjsSpan, name: 'build component tree' }); + // Server component spans: the name is the low-cardinality `code.function.name`, and the + // segment each one resolved for is on the description. + expect(spans).toContainEqual({ + name: 'Layout', + op: 'function', + description: 'resolve root layout server component', + codeFunctionName: 'Layout', + }); + expect(spans).toContainEqual({ + name: 'Layout', + op: 'function', + description: 'resolve layout server component "(nested-layout)"', + codeFunctionName: 'Layout', + }); + expect(spans).toContainEqual({ + name: 'Layout', + op: 'function', + description: 'resolve layout server component "nested-layout"', + codeFunctionName: 'Layout', + }); + expect(spans).toContainEqual({ + name: 'Layout', + op: 'function', + description: 'resolve layout server component "[dynamic]"', + codeFunctionName: 'Layout', + }); + expect(spans).toContainEqual({ + name: 'Page', + op: 'function', + description: 'resolve page server component "/nested-layout/[dynamic]"', + codeFunctionName: 'Page', + }); + expect(spans).toContainEqual({ ...nextjsSpan, name: 'start response' }); }); diff --git a/dev-packages/e2e-tests/test-applications/nextjs-app-dir/tests/server-components.test.ts b/dev-packages/e2e-tests/test-applications/nextjs-app-dir/tests/server-components.test.ts index 89c473c98b2d..5467f376e916 100644 --- a/dev-packages/e2e-tests/test-applications/nextjs-app-dir/tests/server-components.test.ts +++ b/dev-packages/e2e-tests/test-applications/nextjs-app-dir/tests/server-components.test.ts @@ -70,11 +70,14 @@ test('Should set a "not_found" status on a server component span when notFound() // Page server component span should have the right name and attributes expect(spans).toContainEqual( expect.objectContaining({ - name: 'resolve page server component "/server-component/not-found"', + name: 'Page', attributes: expect.objectContaining({ 'sentry.op': { value: 'function', type: 'string' }, + 'sentry.description': { value: 'resolve page server component "/server-component/not-found"', type: 'string' }, + 'code.function.name': { value: 'Page', type: 'string' }, 'sentry.nextjs.ssr.function.type': { value: 'Page', type: 'string' }, 'sentry.nextjs.ssr.function.route': { value: '/server-component/not-found', type: 'string' }, + 'http.route': { value: '/server-component/not-found', type: 'string' }, }), }), ); @@ -111,11 +114,14 @@ test('Should capture an error and spans for a app router page', async ({ page }) // The page server component span should have the right name and attributes expect(spans).toContainEqual( expect.objectContaining({ - name: 'resolve page server component "/server-component/faulty"', + name: 'Page', attributes: expect.objectContaining({ 'sentry.op': { value: 'function', type: 'string' }, + 'sentry.description': { value: 'resolve page server component "/server-component/faulty"', type: 'string' }, + 'code.function.name': { value: 'Page', type: 'string' }, 'sentry.nextjs.ssr.function.type': { value: 'Page', type: 'string' }, 'sentry.nextjs.ssr.function.route': { value: '/server-component/faulty', type: 'string' }, + 'http.route': { value: '/server-component/faulty', type: 'string' }, }), }), ); diff --git a/packages/nextjs/src/common/utils/tracingUtils.ts b/packages/nextjs/src/common/utils/tracingUtils.ts index eb59229175a3..eb188a4ff54e 100644 --- a/packages/nextjs/src/common/utils/tracingUtils.ts +++ b/packages/nextjs/src/common/utils/tracingUtils.ts @@ -1,7 +1,20 @@ -import { HTTP_ROUTE, SENTRY_OP } from '@sentry/conventions/attributes'; +import { + CODE_FUNCTION_NAME, + HTTP_ROUTE, + SENTRY_DESCRIPTION, + SENTRY_NEXTJS_SSR_FUNCTION_ROUTE, + SENTRY_NEXTJS_SSR_FUNCTION_TYPE, + SENTRY_OP, +} from '@sentry/conventions/attributes'; import { FUNCTION } from '@sentry/conventions/op'; import type { PropagationContext, RawAttributes, Span } from '@sentry/core'; -import { isObjectLike, Scope, INTERNAL_setSegmentNameSourceIfSegment } from '@sentry/core'; +import { + isObjectLike, + Scope, + INTERNAL_setSegmentNameSourceIfSegment, + getClient, + hasSpanStreamingEnabled, +} from '@sentry/core'; import { ATTR_NEXT_SEGMENT, ATTR_NEXT_SPAN_NAME, ATTR_NEXT_SPAN_TYPE } from '../nextSpanAttributes'; const commonPropagationContextMap = new WeakMap(); @@ -105,12 +118,22 @@ export function maybeEnhanceServerComponentSpanName( const segment = spanAttributes[ATTR_NEXT_SEGMENT] as string; const route = rootSpanAttributes[HTTP_ROUTE]; - const enhancedName = getEnhancedResolveSegmentSpanName({ segment, route: typeof route === 'string' ? route : '' }); - activeSpan.updateName(enhancedName); + + const enhancedName = segment === PAGE_SEGMENT ? 'Page' : 'Layout'; + const enhancedDescription = getEnhancedResolveSegmentSpanName({ + segment, + route: typeof route === 'string' ? route : '', + }); + const client = getClient(); + + activeSpan.updateName(client && hasSpanStreamingEnabled(client) ? enhancedName : enhancedDescription); activeSpan.setAttributes({ - 'sentry.nextjs.ssr.function.type': segment === PAGE_SEGMENT ? 'Page' : 'Layout', - 'sentry.nextjs.ssr.function.route': route as string | undefined, + [SENTRY_NEXTJS_SSR_FUNCTION_TYPE]: segment === PAGE_SEGMENT ? 'Page' : 'Layout', + [SENTRY_NEXTJS_SSR_FUNCTION_ROUTE]: route as string | undefined, [SENTRY_OP]: FUNCTION, + [SENTRY_DESCRIPTION]: enhancedDescription, + [CODE_FUNCTION_NAME]: enhancedName, + [HTTP_ROUTE]: route as string | undefined, }); // Usually a child of the request root span, in which case this no-ops and the root keeps the name // source it got from `handleOnSpanStart` / `enhanceHandleRequestRootSpan`. diff --git a/packages/nextjs/test/common/utils/tracingUtils.test.ts b/packages/nextjs/test/common/utils/tracingUtils.test.ts new file mode 100644 index 000000000000..3ba482855d81 --- /dev/null +++ b/packages/nextjs/test/common/utils/tracingUtils.test.ts @@ -0,0 +1,125 @@ +import { HTTP_ROUTE } from '@sentry/conventions/attributes'; +import type { Client, Span } from '@sentry/core'; +import * as SentryCore from '@sentry/core'; +import { afterEach, describe, expect, it, vi } from 'vitest'; +import { ATTR_NEXT_SEGMENT, ATTR_NEXT_SPAN_NAME, ATTR_NEXT_SPAN_TYPE } from '../../../src/common/nextSpanAttributes'; +import { maybeEnhanceServerComponentSpanName } from '../../../src/common/utils/tracingUtils'; + +function mockClient(traceLifecycle: 'stream' | 'static'): void { + vi.spyOn(SentryCore, 'getClient').mockReturnValue({ + getOptions: () => ({ traceLifecycle }), + } as unknown as Client); +} + +function mockSpan() { + const span = { + name: undefined as string | undefined, + attributes: {} as Record, + updateName(name: string) { + span.name = name; + }, + setAttributes(attributes: Record) { + Object.assign(span.attributes, attributes); + }, + setAttribute(key: string, value: unknown) { + span.attributes[key] = value; + }, + }; + return span; +} + +function resolveSegmentAttributes(segment: string) { + return { + [ATTR_NEXT_SPAN_TYPE]: 'NextNodeServer.getLayoutOrPageModule', + [ATTR_NEXT_SPAN_NAME]: 'resolve segment modules', + [ATTR_NEXT_SEGMENT]: segment, + }; +} + +// `null` means the root span carries no `http.route` yet. +function enhance(segment: string, route: string | null = '/nested-layout/[dynamic]') { + const span = mockSpan(); + maybeEnhanceServerComponentSpanName( + span as unknown as Span, + resolveSegmentAttributes(segment) as never, + (route === null ? {} : { [HTTP_ROUTE]: route }) as never, + ); + return span; +} + +describe('maybeEnhanceServerComponentSpanName', () => { + afterEach(() => { + vi.restoreAllMocks(); + }); + + it('does nothing for spans that are not resolve segment spans', () => { + mockClient('stream'); + const span = mockSpan(); + + maybeEnhanceServerComponentSpanName( + span as unknown as Span, + { [ATTR_NEXT_SPAN_TYPE]: 'BaseServer.handleRequest' } as never, + {} as never, + ); + + expect(span.name).toBeUndefined(); + expect(span.attributes).toEqual({}); + }); + + describe('with span streaming enabled', () => { + it.each([ + ['__PAGE__', 'Page', 'resolve page server component "/nested-layout/[dynamic]"'], + ['', 'Layout', 'resolve root layout server component'], + ['[dynamic]', 'Layout', 'resolve layout server component "[dynamic]"'], + ])('names the %s segment span %s', (segment, expectedName, expectedDescription) => { + mockClient('stream'); + + const span = enhance(segment); + + expect(span.name).toBe(expectedName); + expect(span.attributes).toMatchObject({ + 'sentry.description': expectedDescription, + 'code.function.name': expectedName, + 'sentry.op': 'function', + }); + }); + }); + + describe('with span streaming disabled', () => { + it.each([ + ['__PAGE__', 'resolve page server component "/nested-layout/[dynamic]"'], + ['', 'resolve root layout server component'], + ['[dynamic]', 'resolve layout server component "[dynamic]"'], + ])('keeps the descriptive name for the %s segment span', (segment, expectedName) => { + mockClient('static'); + + const span = enhance(segment); + + expect(span.name).toBe(expectedName); + // The description is set in both lifecycles, so it always matches what the static name was. + expect(span.attributes['sentry.description']).toBe(expectedName); + }); + }); + + it('keeps the route and the `Page`/`Layout` distinction on attributes', () => { + mockClient('stream'); + + expect(enhance('__PAGE__').attributes).toMatchObject({ + 'sentry.nextjs.ssr.function.type': 'Page', + 'sentry.nextjs.ssr.function.route': '/nested-layout/[dynamic]', + 'http.route': '/nested-layout/[dynamic]', + }); + expect(enhance('[dynamic]').attributes).toMatchObject({ + 'sentry.nextjs.ssr.function.type': 'Layout', + 'http.route': '/nested-layout/[dynamic]', + }); + }); + + it('falls back to an empty route when the root span has none', () => { + mockClient('static'); + const span = enhance('__PAGE__', null); + + expect(span.name).toBe('resolve page server component ""'); + expect(span.attributes['http.route']).toBeUndefined(); + }); +}); From 801c2e79ebde22370ff8f9b75dc5e3161362ad34 Mon Sep 17 00:00:00 2001 From: Lukas Stracke Date: Fri, 11 Sep 2026 12:59:24 +0200 Subject: [PATCH 2/3] avoid getClient call --- packages/nextjs/src/common/utils/tracingUtils.ts | 6 +++--- packages/nextjs/src/server/handleOnSpanStart.ts | 2 +- packages/nextjs/test/common/utils/tracingUtils.test.ts | 2 ++ 3 files changed, 6 insertions(+), 4 deletions(-) diff --git a/packages/nextjs/src/common/utils/tracingUtils.ts b/packages/nextjs/src/common/utils/tracingUtils.ts index eb188a4ff54e..a9c734d169e2 100644 --- a/packages/nextjs/src/common/utils/tracingUtils.ts +++ b/packages/nextjs/src/common/utils/tracingUtils.ts @@ -7,7 +7,7 @@ import { SENTRY_OP, } from '@sentry/conventions/attributes'; import { FUNCTION } from '@sentry/conventions/op'; -import type { PropagationContext, RawAttributes, Span } from '@sentry/core'; +import type { Client, PropagationContext, RawAttributes, Span } from '@sentry/core'; import { isObjectLike, Scope, @@ -111,6 +111,7 @@ export function maybeEnhanceServerComponentSpanName( activeSpan: Span, spanAttributes: RawAttributes>, rootSpanAttributes: RawAttributes>, + client: Client, ): void { if (!isResolveSegmentSpan(spanAttributes)) { return; @@ -124,9 +125,8 @@ export function maybeEnhanceServerComponentSpanName( segment, route: typeof route === 'string' ? route : '', }); - const client = getClient(); - activeSpan.updateName(client && hasSpanStreamingEnabled(client) ? enhancedName : enhancedDescription); + activeSpan.updateName(hasSpanStreamingEnabled(client) ? enhancedName : enhancedDescription); activeSpan.setAttributes({ [SENTRY_NEXTJS_SSR_FUNCTION_TYPE]: segment === PAGE_SEGMENT ? 'Page' : 'Layout', [SENTRY_NEXTJS_SSR_FUNCTION_ROUTE]: route as string | undefined, diff --git a/packages/nextjs/src/server/handleOnSpanStart.ts b/packages/nextjs/src/server/handleOnSpanStart.ts index 9d2becd663a8..5a7f3ea59772 100644 --- a/packages/nextjs/src/server/handleOnSpanStart.ts +++ b/packages/nextjs/src/server/handleOnSpanStart.ts @@ -112,7 +112,7 @@ export function handleOnSpanStart(span: Span, client: Client): void { maybeForkIsolationScopeForRootSpan(span, spanAttributes); - maybeEnhanceServerComponentSpanName(span, spanAttributes, rootSpanAttributes); + maybeEnhanceServerComponentSpanName(span, spanAttributes, rootSpanAttributes, client); // Enrich outgoing http.client spans targeting the Vercel Queues API (producer) maybeEnrichQueueProducerSpan(span); diff --git a/packages/nextjs/test/common/utils/tracingUtils.test.ts b/packages/nextjs/test/common/utils/tracingUtils.test.ts index 3ba482855d81..8e897ad20ecd 100644 --- a/packages/nextjs/test/common/utils/tracingUtils.test.ts +++ b/packages/nextjs/test/common/utils/tracingUtils.test.ts @@ -43,6 +43,7 @@ function enhance(segment: string, route: string | null = '/nested-layout/[dynami span as unknown as Span, resolveSegmentAttributes(segment) as never, (route === null ? {} : { [HTTP_ROUTE]: route }) as never, + SentryCore.getClient()!, ); return span; } @@ -60,6 +61,7 @@ describe('maybeEnhanceServerComponentSpanName', () => { span as unknown as Span, { [ATTR_NEXT_SPAN_TYPE]: 'BaseServer.handleRequest' } as never, {} as never, + SentryCore.getClient()!, ); expect(span.name).toBeUndefined(); From f12009e6c134c2fb9240fd0f8cfc86c697d9ff06 Mon Sep 17 00:00:00 2001 From: Lukas Stracke Date: Fri, 11 Sep 2026 16:53:34 +0200 Subject: [PATCH 3/3] fix(nextjs): Remove unused getClient import The previous commit passed the client in as a parameter instead of calling getClient(), but left the now-unused import behind, which broke the Lint CI check. Co-Authored-By: Claude Opus 5 (1M context) --- packages/nextjs/src/common/utils/tracingUtils.ts | 8 +------- 1 file changed, 1 insertion(+), 7 deletions(-) diff --git a/packages/nextjs/src/common/utils/tracingUtils.ts b/packages/nextjs/src/common/utils/tracingUtils.ts index a9c734d169e2..778c19ce0780 100644 --- a/packages/nextjs/src/common/utils/tracingUtils.ts +++ b/packages/nextjs/src/common/utils/tracingUtils.ts @@ -8,13 +8,7 @@ import { } from '@sentry/conventions/attributes'; import { FUNCTION } from '@sentry/conventions/op'; import type { Client, PropagationContext, RawAttributes, Span } from '@sentry/core'; -import { - isObjectLike, - Scope, - INTERNAL_setSegmentNameSourceIfSegment, - getClient, - hasSpanStreamingEnabled, -} from '@sentry/core'; +import { isObjectLike, Scope, INTERNAL_setSegmentNameSourceIfSegment, hasSpanStreamingEnabled } from '@sentry/core'; import { ATTR_NEXT_SEGMENT, ATTR_NEXT_SPAN_NAME, ATTR_NEXT_SPAN_TYPE } from '../nextSpanAttributes'; const commonPropagationContextMap = new WeakMap();