From 78734c4c06f61b8dce64a4b52416101d293ccdf8 Mon Sep 17 00:00:00 2001 From: JJ Kasper Date: Tue, 30 Sep 2025 16:26:56 -0700 Subject: [PATCH] Update otel test assertions and pages span_name --- .../route-modules/pages/pages-handler.ts | 4 +- packages/next/src/server/web/adapter.ts | 2 +- .../instrumentation/opentelemetry.test.ts | 482 ++++++++---------- 3 files changed, 203 insertions(+), 285 deletions(-) diff --git a/packages/next/src/server/route-modules/pages/pages-handler.ts b/packages/next/src/server/route-modules/pages/pages-handler.ts index a80d8cd43233b9..8c9a1a80a3e7f4 100644 --- a/packages/next/src/server/route-modules/pages/pages-handler.ts +++ b/packages/next/src/server/route-modules/pages/pages-handler.ts @@ -400,7 +400,7 @@ export const getHandler = ({ }) span.updateName(name) } else { - span.updateName(`${method} ${req.url}`) + span.updateName(`${method} ${srcPage}`) } }) } catch (err: unknown) { @@ -722,7 +722,7 @@ export const getHandler = ({ tracer.trace( BaseServerSpan.handleRequest, { - spanName: `${method} ${req.url}`, + spanName: `${method} ${srcPage}`, kind: SpanKind.SERVER, attributes: { 'http.method': method, diff --git a/packages/next/src/server/web/adapter.ts b/packages/next/src/server/web/adapter.ts index bd9537d398eb90..3522944b2af46e 100644 --- a/packages/next/src/server/web/adapter.ts +++ b/packages/next/src/server/web/adapter.ts @@ -252,7 +252,7 @@ export async function adapter( return getTracer().trace( MiddlewareSpan.execute, { - spanName: `middleware ${request.method} ${request.nextUrl.pathname}`, + spanName: `middleware ${request.method}`, attributes: { 'http.target': request.nextUrl.pathname, 'http.method': request.method, diff --git a/test/e2e/opentelemetry/instrumentation/opentelemetry.test.ts b/test/e2e/opentelemetry/instrumentation/opentelemetry.test.ts index 4063ff50791b5f..9d8f55b8e932f3 100644 --- a/test/e2e/opentelemetry/instrumentation/opentelemetry.test.ts +++ b/test/e2e/opentelemetry/instrumentation/opentelemetry.test.ts @@ -225,137 +225,115 @@ describe('opentelemetry', () => { it('should handle RSC with fetch on edge', async () => { await next.fetch('/app/param/rsc-fetch/edge', env.fetchInit) - await expectTrace(getCollector(), [ - { - traceId: env.span.traceId, - parentId: env.span.rootParentId, - runtime: 'edge', - name: 'GET /app/[param]/rsc-fetch/edge', - kind: 1, - attributes: { - 'next.span_name': 'GET /app/[param]/rsc-fetch/edge', - 'next.span_type': 'BaseServer.handleRequest', - 'http.method': 'GET', - 'http.target': '/app/param/rsc-fetch/edge?param=param', - 'http.status_code': 200, - 'next.route': '/app/[param]/rsc-fetch/edge', - 'http.route': '/app/[param]/rsc-fetch/edge', - }, - status: { code: 0 }, - spans: [ - { - name: 'render route (app) /app/[param]/rsc-fetch/edge', - kind: 0, - attributes: { - 'next.span_name': - 'render route (app) /app/[param]/rsc-fetch/edge', - 'next.span_type': 'AppRender.getBodyResult', - 'next.route': '/app/[param]/rsc-fetch/edge', - }, - status: { code: 0 }, - spans: [ - { - name: 'build component tree', - kind: 0, - attributes: { - 'next.span_name': 'build component tree', - 'next.span_type': - 'NextNodeServer.createComponentTree', - }, - status: { code: 0 }, - spans: [ - { - name: 'resolve segment modules', - kind: 0, - attributes: { - 'next.span_name': 'resolve segment modules', - 'next.span_type': - 'NextNodeServer.getLayoutOrPageModule', - 'next.segment': '__PAGE__', - }, - status: { code: 0 }, + await expectTrace( + getCollector(), + [ + { + traceId: env.span.traceId, + parentId: env.span.rootParentId, + runtime: 'edge', + name: 'GET /app/[param]/rsc-fetch/edge', + kind: 1, + attributes: { + 'next.span_name': 'GET /app/[param]/rsc-fetch/edge', + 'next.span_type': 'BaseServer.handleRequest', + 'http.method': 'GET', + 'http.target': '/app/param/rsc-fetch/edge?param=param', + 'http.status_code': 200, + 'next.route': '/app/[param]/rsc-fetch/edge', + 'http.route': '/app/[param]/rsc-fetch/edge', + }, + status: { code: 0 }, + spans: [ + { + name: 'render route (app) /app/[param]/rsc-fetch/edge', + kind: 0, + attributes: { + 'next.span_name': + 'render route (app) /app/[param]/rsc-fetch/edge', + 'next.span_type': 'AppRender.getBodyResult', + 'next.route': '/app/[param]/rsc-fetch/edge', + }, + status: { code: 0 }, + spans: [ + { + name: 'build component tree', + kind: 0, + attributes: { + 'next.span_name': 'build component tree', + 'next.span_type': + 'NextNodeServer.createComponentTree', }, - { - name: 'resolve segment modules', - kind: 0, - attributes: { - 'next.span_name': 'resolve segment modules', - 'next.span_type': - 'NextNodeServer.getLayoutOrPageModule', - 'next.segment': '[param]', + status: { code: 0 }, + spans: [ + { + name: 'resolve segment modules', + kind: 0, + attributes: { + 'next.span_name': 'resolve segment modules', + 'next.span_type': + 'NextNodeServer.getLayoutOrPageModule', + 'next.segment': '__PAGE__', + }, + status: { code: 0 }, }, - status: { code: 0 }, + { + name: 'resolve segment modules', + kind: 0, + attributes: { + 'next.span_name': 'resolve segment modules', + 'next.span_type': + 'NextNodeServer.getLayoutOrPageModule', + 'next.segment': '[param]', + }, + status: { code: 0 }, + }, + ], + }, + { + name: 'fetch GET https://example.vercel.sh/', + kind: 2, + attributes: { + 'next.span_name': + 'fetch GET https://example.vercel.sh/', + 'next.span_type': 'AppRender.fetch', + 'http.url': 'https://example.vercel.sh/', + 'http.method': 'GET', + 'net.peer.name': 'example.vercel.sh', }, - ], - }, - { - name: 'fetch GET https://example.vercel.sh/', - kind: 2, - attributes: { - 'next.span_name': - 'fetch GET https://example.vercel.sh/', - 'next.span_type': 'AppRender.fetch', - 'http.url': 'https://example.vercel.sh/', - 'http.method': 'GET', - 'net.peer.name': 'example.vercel.sh', + status: { code: 0 }, }, - status: { code: 0 }, - }, - { - name: 'generateMetadata /app/[param]/layout', - kind: 0, - attributes: { - 'next.span_name': - 'generateMetadata /app/[param]/layout', - 'next.span_type': 'ResolveMetadata.generateMetadata', - 'next.page': '/app/[param]/layout', + { + name: 'generateMetadata /app/[param]/layout', + kind: 0, + attributes: { + 'next.span_name': + 'generateMetadata /app/[param]/layout', + 'next.span_type': + 'ResolveMetadata.generateMetadata', + 'next.page': '/app/[param]/layout', + }, + status: { code: 0 }, }, - status: { code: 0 }, - }, - { - name: 'generateMetadata /app/[param]/rsc-fetch/edge/page', - kind: 0, - attributes: { - 'next.span_name': - 'generateMetadata /app/[param]/rsc-fetch/edge/page', - 'next.span_type': 'ResolveMetadata.generateMetadata', - 'next.page': '/app/[param]/rsc-fetch/edge/page', + { + name: 'generateMetadata /app/[param]/rsc-fetch/edge/page', + kind: 0, + attributes: { + 'next.span_name': + 'generateMetadata /app/[param]/rsc-fetch/edge/page', + 'next.span_type': + 'ResolveMetadata.generateMetadata', + 'next.page': '/app/[param]/rsc-fetch/edge/page', + }, + status: { code: 0 }, }, - status: { code: 0 }, - }, - ], - }, - ], - }, - - // TODO: what is this trace? What's the value in it? - { - traceId: env.span.traceId, - parentId: env.span.rootParentId, - runtime: 'nodejs', - name: 'GET', - kind: 1, - attributes: { - 'next.span_name': 'GET', - 'next.span_type': 'BaseServer.handleRequest', - 'http.method': 'GET', - 'http.target': '/app/param/rsc-fetch/edge', - 'http.status_code': 200, - }, - status: { code: 0 }, - spans: [ - { - name: 'start response', - kind: 0, - attributes: { - 'next.span_name': 'start response', - 'next.span_type': 'NextNodeServer.startResponse', + ], }, - status: { code: 0 }, - }, - ], - }, - ]) + ], + }, + ], + true + ) }) it('should handle RSC with fetch in RSC mode', async () => { @@ -447,50 +425,26 @@ describe('opentelemetry', () => { it('should handle route handlers in app router on edge', async () => { await next.fetch('/api/app/param/data/edge', env.fetchInit) - await expectTrace(getCollector(), [ - { - runtime: 'edge', - traceId: env.span.traceId, - parentId: env.span.rootParentId, - name: 'executing api route (app) /api/app/[param]/data/edge', - attributes: { - 'next.route': '/api/app/[param]/data/edge', - 'next.span_name': - 'executing api route (app) /api/app/[param]/data/edge', - 'next.span_type': 'AppRouteRouteHandlers.runHandler', - }, - kind: 0, - status: { code: 0 }, - }, - - // TODO: what is this trace? What's the value in it? - { - runtime: 'nodejs', - traceId: env.span.traceId, - parentId: env.span.rootParentId, - name: 'GET', - kind: 1, - attributes: { - 'next.span_name': 'GET', - 'next.span_type': 'BaseServer.handleRequest', - 'http.method': 'GET', - 'http.target': '/api/app/param/data/edge', - 'http.status_code': 200, - }, - status: { code: 0 }, - spans: [ - { - name: 'start response', - kind: 0, - attributes: { - 'next.span_name': 'start response', - 'next.span_type': 'NextNodeServer.startResponse', - }, - status: { code: 0 }, + await expectTrace( + getCollector(), + [ + { + runtime: 'edge', + traceId: env.span.traceId, + parentId: env.span.rootParentId, + name: 'executing api route (app) /api/app/[param]/data/edge', + attributes: { + 'next.route': '/api/app/[param]/data/edge', + 'next.span_name': + 'executing api route (app) /api/app/[param]/data/edge', + 'next.span_type': 'AppRouteRouteHandlers.runHandler', }, - ], - }, - ]) + kind: 0, + status: { code: 0 }, + }, + ], + true + ) }) it('should trace middleware', async () => { @@ -501,11 +455,11 @@ describe('opentelemetry', () => { runtime: 'edge', traceId: env.span.traceId, parentId: env.span.rootParentId, - name: 'middleware GET /behind-middleware', + name: 'middleware GET', attributes: { 'http.method': 'GET', 'http.target': '/behind-middleware', - 'next.span_name': 'middleware GET /behind-middleware', + 'next.span_name': 'middleware GET', 'next.span_type': 'Middleware.execute', }, status: { code: 0 }, @@ -781,79 +735,55 @@ describe('opentelemetry', () => { env.fetchInit ) - await expectTrace(getCollector(), [ - { - runtime: 'edge', - traceId: env.span.traceId, - parentId: env.span.rootParentId, - name: 'GET /pages/[param]/edge/getServerSideProps', - kind: 1, - attributes: { - 'next.span_name': - 'GET /pages/[param]/edge/getServerSideProps', - 'next.span_type': 'BaseServer.handleRequest', - 'http.method': 'GET', - 'http.target': - '/pages/param/edge/getServerSideProps?param=param', - 'http.status_code': 200, - 'next.route': '/pages/[param]/edge/getServerSideProps', - 'http.route': '/pages/[param]/edge/getServerSideProps', - }, - status: { code: 0 }, - spans: [ - { - name: 'getServerSideProps /pages/[param]/edge/getServerSideProps', - kind: 0, - attributes: { - 'next.span_name': - 'getServerSideProps /pages/[param]/edge/getServerSideProps', - 'next.span_type': 'Render.getServerSideProps', - 'next.route': '/pages/[param]/edge/getServerSideProps', - }, - status: { code: 0 }, + await expectTrace( + getCollector(), + [ + { + runtime: 'edge', + traceId: env.span.traceId, + parentId: env.span.rootParentId, + name: 'GET /pages/[param]/edge/getServerSideProps', + kind: 1, + attributes: { + 'next.span_name': + 'GET /pages/[param]/edge/getServerSideProps', + 'next.span_type': 'BaseServer.handleRequest', + 'http.method': 'GET', + 'http.target': + '/pages/param/edge/getServerSideProps?param=param', + 'http.status_code': 200, + 'next.route': '/pages/[param]/edge/getServerSideProps', + 'http.route': '/pages/[param]/edge/getServerSideProps', }, - { - name: 'render route (pages) /pages/[param]/edge/getServerSideProps', - kind: 0, - attributes: { - 'next.span_name': - 'render route (pages) /pages/[param]/edge/getServerSideProps', - 'next.span_type': 'Render.renderDocument', - 'next.route': '/pages/[param]/edge/getServerSideProps', + status: { code: 0 }, + spans: [ + { + name: 'getServerSideProps /pages/[param]/edge/getServerSideProps', + kind: 0, + attributes: { + 'next.span_name': + 'getServerSideProps /pages/[param]/edge/getServerSideProps', + 'next.span_type': 'Render.getServerSideProps', + 'next.route': '/pages/[param]/edge/getServerSideProps', + }, + status: { code: 0 }, }, - status: { code: 0 }, - }, - ], - }, - - // TODO: what is this trace? What's the value in it? - { - runtime: 'nodejs', - traceId: env.span.traceId, - parentId: env.span.rootParentId, - name: 'GET', - kind: 1, - attributes: { - 'next.span_name': 'GET', - 'next.span_type': 'BaseServer.handleRequest', - 'http.method': 'GET', - 'http.target': '/pages/param/edge/getServerSideProps', - 'http.status_code': 200, - }, - status: { code: 0 }, - spans: [ - { - name: 'start response', - kind: 0, - attributes: { - 'next.span_name': 'start response', - 'next.span_type': 'NextNodeServer.startResponse', + { + name: 'render route (pages) /pages/[param]/edge/getServerSideProps', + kind: 0, + attributes: { + 'next.span_name': + 'render route (pages) /pages/[param]/edge/getServerSideProps', + 'next.span_type': 'Render.renderDocument', + 'next.route': '/pages/[param]/edge/getServerSideProps', + }, + status: { code: 0 }, }, - status: { code: 0 }, - }, - ], - }, - ]) + ], + }, + ], + true + ) }) it('should handle getServerSideProps exceptions', async () => { @@ -1080,49 +1010,25 @@ describe('opentelemetry', () => { it('should handle api routes in pages on edge', async () => { await next.fetch('/api/pages/param/edge', env.fetchInit) - await expectTrace(getCollector(), [ - { - runtime: 'edge', - traceId: env.span.traceId, - parentId: env.span.rootParentId, - name: 'executing api route (pages) /api/pages/[param]/edge', - attributes: { - 'next.span_name': - 'executing api route (pages) /api/pages/[param]/edge', - 'next.span_type': 'Node.runHandler', - }, - kind: 0, - status: { code: 0 }, - }, - - // TODO: what is this trace? What's the value in it? - { - runtime: 'nodejs', - traceId: env.span.traceId, - parentId: env.span.rootParentId, - name: 'GET', - kind: 1, - attributes: { - 'next.span_name': 'GET', - 'next.span_type': 'BaseServer.handleRequest', - 'http.method': 'GET', - 'http.target': '/api/pages/param/edge', - 'http.status_code': 200, - }, - status: { code: 0 }, - spans: [ - { - name: 'start response', - kind: 0, - attributes: { - 'next.span_name': 'start response', - 'next.span_type': 'NextNodeServer.startResponse', - }, - status: { code: 0 }, + await expectTrace( + getCollector(), + [ + { + runtime: 'edge', + traceId: env.span.traceId, + parentId: env.span.rootParentId, + name: 'executing api route (pages) /api/pages/[param]/edge', + attributes: { + 'next.span_name': + 'executing api route (pages) /api/pages/[param]/edge', + 'next.span_type': 'Node.runHandler', }, - ], - }, - ]) + kind: 0, + status: { code: 0 }, + }, + ], + true + ) }) }) } @@ -1214,7 +1120,11 @@ describe('opentelemetry with disabled fetch tracing', () => { type HierSavedSpan = SavedSpan & { spans?: HierSavedSpan[] } type SpanMatch = Omit, 'spans'> & { spans?: SpanMatch[] } -async function expectTrace(collector: Collector, match: SpanMatch[]) { +async function expectTrace( + collector: Collector, + match: SpanMatch[], + edgeOnly?: boolean +) { await check(async () => { const traces = collector.getSpans() @@ -1224,6 +1134,14 @@ async function expectTrace(collector: Collector, match: SpanMatch[]) { spans: [], })) for (const span of spansForTree) { + // for edge runtime with `next start` the nodejs runtime spans + // will not be updated as the sandbox can't update spans across + // node -> edge boundary, these spans also are not present when + // deployed as next-server is not invoking edge functions there + if (span.runtime === 'nodejs' && edgeOnly) { + continue + } + const parent = !span.parentId || span.parentId === EXTERNAL.spanId ? null