From f325c16536ef92193f5b6a71eac25d8490694788 Mon Sep 17 00:00:00 2001 From: Jan Kaifer Date: Tue, 21 Mar 2023 09:53:36 +0100 Subject: [PATCH 01/29] Add rendering in SSR as a span --- packages/next/src/server/lib/trace/constants.ts | 1 + packages/next/src/server/render.tsx | 3 +++ test/e2e/opentelemetry/opentelemetry.test.ts | 12 ++++++++++++ 3 files changed, 16 insertions(+) diff --git a/packages/next/src/server/lib/trace/constants.ts b/packages/next/src/server/lib/trace/constants.ts index 312a2223f2240..bf80fa82fd403 100644 --- a/packages/next/src/server/lib/trace/constants.ts +++ b/packages/next/src/server/lib/trace/constants.ts @@ -105,6 +105,7 @@ export const NextVanillaSpanAllowlist = [ BaseServerSpan.renderToResponse, RenderSpan.getServerSideProps, AppRenderSpan.fetch, + RenderSpan.renderDocument, ] export { diff --git a/packages/next/src/server/render.tsx b/packages/next/src/server/render.tsx index 4782c7fa9bce7..d576375565f0e 100644 --- a/packages/next/src/server/render.tsx +++ b/packages/next/src/server/render.tsx @@ -1332,6 +1332,9 @@ export async function renderToHTML( const documentResult = await getTracer().trace( RenderSpan.renderDocument, + { + spanName: `Render page ${renderOpts.pathname}`, + }, async () => renderDocument() ) if (!documentResult) { diff --git a/test/e2e/opentelemetry/opentelemetry.test.ts b/test/e2e/opentelemetry/opentelemetry.test.ts index b7557201bbcd4..b142893a20c55 100644 --- a/test/e2e/opentelemetry/opentelemetry.test.ts +++ b/test/e2e/opentelemetry/opentelemetry.test.ts @@ -283,6 +283,18 @@ createNextDescribe( "code": 0, }, }, + Object { + "attributes": Object { + "next.span_name": "Render page /pages/getServerSideProps", + "next.span_type": "Render.renderDocument", + }, + "kind": 0, + "name": "Render page /pages/getServerSideProps", + "parentId": "[parent-id]", + "status": Object { + "code": 0, + }, + }, Object { "attributes": Object { "next.pathname": "/pages/getServerSideProps", From c937f597d4eea1f95eca908cd440866ef0b4fe95 Mon Sep 17 00:00:00 2001 From: Jan Kaifer Date: Tue, 21 Mar 2023 09:54:14 +0100 Subject: [PATCH 02/29] reorder spans to be sorted by their span type --- test/e2e/opentelemetry/opentelemetry.test.ts | 130 ++++++++++--------- 1 file changed, 67 insertions(+), 63 deletions(-) diff --git a/test/e2e/opentelemetry/opentelemetry.test.ts b/test/e2e/opentelemetry/opentelemetry.test.ts index b142893a20c55..e3914b67ef1e9 100644 --- a/test/e2e/opentelemetry/opentelemetry.test.ts +++ b/test/e2e/opentelemetry/opentelemetry.test.ts @@ -66,7 +66,11 @@ createNextDescribe( } const sanitizeSpans = (spans: SavedSpan[]) => spans - .sort((a, b) => (a.name ?? '').localeCompare(b.name ?? '')) + .sort((a, b) => + (a.attributes?.span_type ?? '').localeCompare( + b.attributes?.span_type ?? '' + ) + ) .map(sanitizeSpan) const getSanitizedTraces = async (numberOfRootTraces: number) => { @@ -93,15 +97,13 @@ createNextDescribe( Array [ Object { "attributes": Object { - "http.method": "GET", - "http.status_code": 200, - "http.target": "/pages", - "next.span_name": "GET /pages", - "next.span_type": "BaseServer.handleRequest", + "next.route": "/pages", + "next.span_name": "resolving page into components", + "next.span_type": "NextNodeServer.findPageComponents", }, - "kind": 1, - "name": "GET /pages", - "parentId": undefined, + "kind": 0, + "name": "resolving page into components", + "parentId": "[parent-id]", "status": Object { "code": 0, }, @@ -121,13 +123,15 @@ createNextDescribe( }, Object { "attributes": Object { - "next.route": "/pages", - "next.span_name": "resolving page into components", - "next.span_type": "NextNodeServer.findPageComponents", + "http.method": "GET", + "http.status_code": 200, + "http.target": "/pages", + "next.span_name": "GET /pages", + "next.span_type": "BaseServer.handleRequest", }, - "kind": 0, - "name": "resolving page into components", - "parentId": "[parent-id]", + "kind": 1, + "name": "GET /pages", + "parentId": undefined, "status": Object { "code": 0, }, @@ -143,15 +147,13 @@ createNextDescribe( Array [ Object { "attributes": Object { - "http.method": "GET", - "http.status_code": 200, - "http.target": "/pages/params/stuff", - "next.span_name": "GET /pages/params/stuff", - "next.span_type": "BaseServer.handleRequest", + "next.route": "/pages/params/[param]", + "next.span_name": "resolving page into components", + "next.span_type": "NextNodeServer.findPageComponents", }, - "kind": 1, - "name": "GET /pages/params/stuff", - "parentId": undefined, + "kind": 0, + "name": "resolving page into components", + "parentId": "[parent-id]", "status": Object { "code": 0, }, @@ -171,13 +173,15 @@ createNextDescribe( }, Object { "attributes": Object { - "next.route": "/pages/params/[param]", - "next.span_name": "resolving page into components", - "next.span_type": "NextNodeServer.findPageComponents", + "http.method": "GET", + "http.status_code": 200, + "http.target": "/pages/params/stuff", + "next.span_name": "GET /pages/params/stuff", + "next.span_type": "BaseServer.handleRequest", }, - "kind": 0, - "name": "resolving page into components", - "parentId": "[parent-id]", + "kind": 1, + "name": "GET /pages/params/stuff", + "parentId": undefined, "status": Object { "code": 0, }, @@ -193,14 +197,12 @@ createNextDescribe( Array [ Object { "attributes": Object { - "http.method": "GET", - "http.url": "https://vercel.com/", - "net.peer.name": "vercel.com", - "next.span_name": "fetch GET https://vercel.com/", - "next.span_type": "AppRender.fetch", + "next.route": "/app/rsc-fetch/page", + "next.span_name": "resolving page into components", + "next.span_type": "NextNodeServer.findPageComponents", }, - "kind": 2, - "name": "fetch GET https://vercel.com/", + "kind": 0, + "name": "resolving page into components", "parentId": "[parent-id]", "status": Object { "code": 0, @@ -209,14 +211,14 @@ createNextDescribe( Object { "attributes": Object { "http.method": "GET", - "http.status_code": 200, - "http.target": "/app/rsc-fetch", - "next.span_name": "GET /app/rsc-fetch", - "next.span_type": "BaseServer.handleRequest", + "http.url": "https://vercel.com/", + "net.peer.name": "vercel.com", + "next.span_name": "fetch GET https://vercel.com/", + "next.span_type": "AppRender.fetch", }, - "kind": 1, - "name": "GET /app/rsc-fetch", - "parentId": undefined, + "kind": 2, + "name": "fetch GET https://vercel.com/", + "parentId": "[parent-id]", "status": Object { "code": 0, }, @@ -236,13 +238,15 @@ createNextDescribe( }, Object { "attributes": Object { - "next.route": "/app/rsc-fetch/page", - "next.span_name": "resolving page into components", - "next.span_type": "NextNodeServer.findPageComponents", + "http.method": "GET", + "http.status_code": 200, + "http.target": "/app/rsc-fetch", + "next.span_name": "GET /app/rsc-fetch", + "next.span_type": "BaseServer.handleRequest", }, - "kind": 0, - "name": "resolving page into components", - "parentId": "[parent-id]", + "kind": 1, + "name": "GET /app/rsc-fetch", + "parentId": undefined, "status": Object { "code": 0, }, @@ -258,15 +262,13 @@ createNextDescribe( Array [ Object { "attributes": Object { - "http.method": "GET", - "http.status_code": 200, - "http.target": "/pages/getServerSideProps", - "next.span_name": "GET /pages/getServerSideProps", - "next.span_type": "BaseServer.handleRequest", + "next.route": "/pages/getServerSideProps", + "next.span_name": "resolving page into components", + "next.span_type": "NextNodeServer.findPageComponents", }, - "kind": 1, - "name": "GET /pages/getServerSideProps", - "parentId": undefined, + "kind": 0, + "name": "resolving page into components", + "parentId": "[parent-id]", "status": Object { "code": 0, }, @@ -310,13 +312,15 @@ createNextDescribe( }, Object { "attributes": Object { - "next.route": "/pages/getServerSideProps", - "next.span_name": "resolving page into components", - "next.span_type": "NextNodeServer.findPageComponents", + "http.method": "GET", + "http.status_code": 200, + "http.target": "/pages/getServerSideProps", + "next.span_name": "GET /pages/getServerSideProps", + "next.span_type": "BaseServer.handleRequest", }, - "kind": 0, - "name": "resolving page into components", - "parentId": "[parent-id]", + "kind": 1, + "name": "GET /pages/getServerSideProps", + "parentId": undefined, "status": Object { "code": 0, }, From ea5b1c565b6c2acd410fda9f41d0abbc4300356b Mon Sep 17 00:00:00 2001 From: Jan Kaifer Date: Tue, 21 Mar 2023 10:04:41 +0100 Subject: [PATCH 03/29] fix ordering of spans in tests --- test/e2e/opentelemetry/opentelemetry.test.ts | 148 +++++++++---------- 1 file changed, 74 insertions(+), 74 deletions(-) diff --git a/test/e2e/opentelemetry/opentelemetry.test.ts b/test/e2e/opentelemetry/opentelemetry.test.ts index e3914b67ef1e9..1206abdc38535 100644 --- a/test/e2e/opentelemetry/opentelemetry.test.ts +++ b/test/e2e/opentelemetry/opentelemetry.test.ts @@ -67,8 +67,8 @@ createNextDescribe( const sanitizeSpans = (spans: SavedSpan[]) => spans .sort((a, b) => - (a.attributes?.span_type ?? '').localeCompare( - b.attributes?.span_type ?? '' + (a.attributes?.['next.span_type'] ?? '').localeCompare( + b.attributes?.['next.span_type'] ?? '' ) ) .map(sanitizeSpan) @@ -97,13 +97,15 @@ createNextDescribe( Array [ Object { "attributes": Object { - "next.route": "/pages", - "next.span_name": "resolving page into components", - "next.span_type": "NextNodeServer.findPageComponents", + "http.method": "GET", + "http.status_code": 200, + "http.target": "/pages", + "next.span_name": "GET /pages", + "next.span_type": "BaseServer.handleRequest", }, - "kind": 0, - "name": "resolving page into components", - "parentId": "[parent-id]", + "kind": 1, + "name": "GET /pages", + "parentId": undefined, "status": Object { "code": 0, }, @@ -123,15 +125,13 @@ createNextDescribe( }, Object { "attributes": Object { - "http.method": "GET", - "http.status_code": 200, - "http.target": "/pages", - "next.span_name": "GET /pages", - "next.span_type": "BaseServer.handleRequest", + "next.route": "/pages", + "next.span_name": "resolving page into components", + "next.span_type": "NextNodeServer.findPageComponents", }, - "kind": 1, - "name": "GET /pages", - "parentId": undefined, + "kind": 0, + "name": "resolving page into components", + "parentId": "[parent-id]", "status": Object { "code": 0, }, @@ -147,13 +147,15 @@ createNextDescribe( Array [ Object { "attributes": Object { - "next.route": "/pages/params/[param]", - "next.span_name": "resolving page into components", - "next.span_type": "NextNodeServer.findPageComponents", + "http.method": "GET", + "http.status_code": 200, + "http.target": "/pages/params/stuff", + "next.span_name": "GET /pages/params/stuff", + "next.span_type": "BaseServer.handleRequest", }, - "kind": 0, - "name": "resolving page into components", - "parentId": "[parent-id]", + "kind": 1, + "name": "GET /pages/params/stuff", + "parentId": undefined, "status": Object { "code": 0, }, @@ -173,15 +175,13 @@ createNextDescribe( }, Object { "attributes": Object { - "http.method": "GET", - "http.status_code": 200, - "http.target": "/pages/params/stuff", - "next.span_name": "GET /pages/params/stuff", - "next.span_type": "BaseServer.handleRequest", + "next.route": "/pages/params/[param]", + "next.span_name": "resolving page into components", + "next.span_type": "NextNodeServer.findPageComponents", }, - "kind": 1, - "name": "GET /pages/params/stuff", - "parentId": undefined, + "kind": 0, + "name": "resolving page into components", + "parentId": "[parent-id]", "status": Object { "code": 0, }, @@ -197,12 +197,14 @@ createNextDescribe( Array [ Object { "attributes": Object { - "next.route": "/app/rsc-fetch/page", - "next.span_name": "resolving page into components", - "next.span_type": "NextNodeServer.findPageComponents", + "http.method": "GET", + "http.url": "https://vercel.com/", + "net.peer.name": "vercel.com", + "next.span_name": "fetch GET https://vercel.com/", + "next.span_type": "AppRender.fetch", }, - "kind": 0, - "name": "resolving page into components", + "kind": 2, + "name": "fetch GET https://vercel.com/", "parentId": "[parent-id]", "status": Object { "code": 0, @@ -211,14 +213,14 @@ createNextDescribe( Object { "attributes": Object { "http.method": "GET", - "http.url": "https://vercel.com/", - "net.peer.name": "vercel.com", - "next.span_name": "fetch GET https://vercel.com/", - "next.span_type": "AppRender.fetch", + "http.status_code": 200, + "http.target": "/app/rsc-fetch", + "next.span_name": "GET /app/rsc-fetch", + "next.span_type": "BaseServer.handleRequest", }, - "kind": 2, - "name": "fetch GET https://vercel.com/", - "parentId": "[parent-id]", + "kind": 1, + "name": "GET /app/rsc-fetch", + "parentId": undefined, "status": Object { "code": 0, }, @@ -238,15 +240,13 @@ createNextDescribe( }, Object { "attributes": Object { - "http.method": "GET", - "http.status_code": 200, - "http.target": "/app/rsc-fetch", - "next.span_name": "GET /app/rsc-fetch", - "next.span_type": "BaseServer.handleRequest", + "next.route": "/app/rsc-fetch/page", + "next.span_name": "resolving page into components", + "next.span_type": "NextNodeServer.findPageComponents", }, - "kind": 1, - "name": "GET /app/rsc-fetch", - "parentId": undefined, + "kind": 0, + "name": "resolving page into components", + "parentId": "[parent-id]", "status": Object { "code": 0, }, @@ -262,24 +262,27 @@ createNextDescribe( Array [ Object { "attributes": Object { - "next.route": "/pages/getServerSideProps", - "next.span_name": "resolving page into components", - "next.span_type": "NextNodeServer.findPageComponents", + "http.method": "GET", + "http.status_code": 200, + "http.target": "/pages/getServerSideProps", + "next.span_name": "GET /pages/getServerSideProps", + "next.span_type": "BaseServer.handleRequest", }, - "kind": 0, - "name": "resolving page into components", - "parentId": "[parent-id]", + "kind": 1, + "name": "GET /pages/getServerSideProps", + "parentId": undefined, "status": Object { "code": 0, }, }, Object { "attributes": Object { - "next.span_name": "getServerSideProps /pages/getServerSideProps", - "next.span_type": "Render.getServerSideProps", + "next.pathname": "/pages/getServerSideProps", + "next.span_name": "rendering page", + "next.span_type": "BaseServer.renderToResponse", }, "kind": 0, - "name": "getServerSideProps /pages/getServerSideProps", + "name": "rendering page", "parentId": "[parent-id]", "status": Object { "code": 0, @@ -287,11 +290,12 @@ createNextDescribe( }, Object { "attributes": Object { - "next.span_name": "Render page /pages/getServerSideProps", - "next.span_type": "Render.renderDocument", + "next.route": "/pages/getServerSideProps", + "next.span_name": "resolving page into components", + "next.span_type": "NextNodeServer.findPageComponents", }, "kind": 0, - "name": "Render page /pages/getServerSideProps", + "name": "resolving page into components", "parentId": "[parent-id]", "status": Object { "code": 0, @@ -299,12 +303,11 @@ createNextDescribe( }, Object { "attributes": Object { - "next.pathname": "/pages/getServerSideProps", - "next.span_name": "rendering page", - "next.span_type": "BaseServer.renderToResponse", + "next.span_name": "getServerSideProps /pages/getServerSideProps", + "next.span_type": "Render.getServerSideProps", }, "kind": 0, - "name": "rendering page", + "name": "getServerSideProps /pages/getServerSideProps", "parentId": "[parent-id]", "status": Object { "code": 0, @@ -312,15 +315,12 @@ createNextDescribe( }, Object { "attributes": Object { - "http.method": "GET", - "http.status_code": 200, - "http.target": "/pages/getServerSideProps", - "next.span_name": "GET /pages/getServerSideProps", - "next.span_type": "BaseServer.handleRequest", + "next.span_name": "Render page /pages/getServerSideProps", + "next.span_type": "Render.renderDocument", }, - "kind": 1, - "name": "GET /pages/getServerSideProps", - "parentId": undefined, + "kind": 0, + "name": "Render page /pages/getServerSideProps", + "parentId": "[parent-id]", "status": Object { "code": 0, }, From 250030b1e2d515f56f975523485e5f0ffb4d7b65 Mon Sep 17 00:00:00 2001 From: Jan Kaifer Date: Tue, 21 Mar 2023 10:43:03 +0100 Subject: [PATCH 04/29] Switch rendering to show only when user code is ran --- .../next/src/server/lib/trace/constants.ts | 1 - packages/next/src/server/render.tsx | 2 +- test/e2e/opentelemetry/opentelemetry.test.ts | 101 +++++++++--------- .../pages/static/[param]/getStaticProps.tsx | 17 +++ 4 files changed, 67 insertions(+), 54 deletions(-) create mode 100644 test/e2e/opentelemetry/pages/pages/static/[param]/getStaticProps.tsx diff --git a/packages/next/src/server/lib/trace/constants.ts b/packages/next/src/server/lib/trace/constants.ts index bf80fa82fd403..dbfe97b4491b8 100644 --- a/packages/next/src/server/lib/trace/constants.ts +++ b/packages/next/src/server/lib/trace/constants.ts @@ -102,7 +102,6 @@ type SpanTypes = export const NextVanillaSpanAllowlist = [ BaseServerSpan.handleRequest, NextNodeServerSpan.findPageComponents, - BaseServerSpan.renderToResponse, RenderSpan.getServerSideProps, AppRenderSpan.fetch, RenderSpan.renderDocument, diff --git a/packages/next/src/server/render.tsx b/packages/next/src/server/render.tsx index d576375565f0e..7ec1bd2d43d5b 100644 --- a/packages/next/src/server/render.tsx +++ b/packages/next/src/server/render.tsx @@ -1333,7 +1333,7 @@ export async function renderToHTML( const documentResult = await getTracer().trace( RenderSpan.renderDocument, { - spanName: `Render page ${renderOpts.pathname}`, + spanName: `render page ${renderOpts.pathname}`, }, async () => renderDocument() ) diff --git a/test/e2e/opentelemetry/opentelemetry.test.ts b/test/e2e/opentelemetry/opentelemetry.test.ts index 1206abdc38535..5ca9f44636ad7 100644 --- a/test/e2e/opentelemetry/opentelemetry.test.ts +++ b/test/e2e/opentelemetry/opentelemetry.test.ts @@ -110,19 +110,6 @@ createNextDescribe( "code": 0, }, }, - Object { - "attributes": Object { - "next.pathname": "/pages", - "next.span_name": "rendering page", - "next.span_type": "BaseServer.renderToResponse", - }, - "kind": 0, - "name": "rendering page", - "parentId": "[parent-id]", - "status": Object { - "code": 0, - }, - }, Object { "attributes": Object { "next.route": "/pages", @@ -160,19 +147,6 @@ createNextDescribe( "code": 0, }, }, - Object { - "attributes": Object { - "next.pathname": "/pages/params/stuff", - "next.span_name": "rendering page", - "next.span_type": "BaseServer.renderToResponse", - }, - "kind": 0, - "name": "rendering page", - "parentId": "[parent-id]", - "status": Object { - "code": 0, - }, - }, Object { "attributes": Object { "next.route": "/pages/params/[param]", @@ -225,19 +199,6 @@ createNextDescribe( "code": 0, }, }, - Object { - "attributes": Object { - "next.pathname": "/app/rsc-fetch", - "next.span_name": "rendering page", - "next.span_type": "BaseServer.renderToResponse", - }, - "kind": 0, - "name": "rendering page", - "parentId": "[parent-id]", - "status": Object { - "code": 0, - }, - }, Object { "attributes": Object { "next.route": "/app/rsc-fetch/page", @@ -277,12 +238,12 @@ createNextDescribe( }, Object { "attributes": Object { - "next.pathname": "/pages/getServerSideProps", - "next.span_name": "rendering page", - "next.span_type": "BaseServer.renderToResponse", + "next.route": "/pages/getServerSideProps", + "next.span_name": "resolving page into components", + "next.span_type": "NextNodeServer.findPageComponents", }, "kind": 0, - "name": "rendering page", + "name": "resolving page into components", "parentId": "[parent-id]", "status": Object { "code": 0, @@ -290,12 +251,11 @@ createNextDescribe( }, Object { "attributes": Object { - "next.route": "/pages/getServerSideProps", - "next.span_name": "resolving page into components", - "next.span_type": "NextNodeServer.findPageComponents", + "next.span_name": "getServerSideProps /pages/getServerSideProps", + "next.span_type": "Render.getServerSideProps", }, "kind": 0, - "name": "resolving page into components", + "name": "getServerSideProps /pages/getServerSideProps", "parentId": "[parent-id]", "status": Object { "code": 0, @@ -303,11 +263,48 @@ createNextDescribe( }, Object { "attributes": Object { - "next.span_name": "getServerSideProps /pages/getServerSideProps", - "next.span_type": "Render.getServerSideProps", + "next.span_name": "render page /pages/getServerSideProps", + "next.span_type": "Render.renderDocument", }, "kind": 0, - "name": "getServerSideProps /pages/getServerSideProps", + "name": "render page /pages/getServerSideProps", + "parentId": "[parent-id]", + "status": Object { + "code": 0, + }, + }, + ] + `) + }) + + it("should handle getStaticProps when fallback: 'blocking'", async () => { + await next.fetch('/pages/static/param/getStaticProps') + + expect(await getSanitizedTraces(1)).toMatchInlineSnapshot(` + Array [ + Object { + "attributes": Object { + "http.method": "GET", + "http.status_code": 200, + "http.target": "/pages/static/param/getStaticProps", + "next.span_name": "GET /pages/static/param/getStaticProps", + "next.span_type": "BaseServer.handleRequest", + }, + "kind": 1, + "name": "GET /pages/static/param/getStaticProps", + "parentId": undefined, + "status": Object { + "code": 0, + }, + }, + Object { + "attributes": Object { + "next.route": "/pages/static/[param]/getStaticProps", + "next.span_name": "resolving page into components", + "next.span_type": "NextNodeServer.findPageComponents", + }, + "kind": 0, + "name": "resolving page into components", "parentId": "[parent-id]", "status": Object { "code": 0, @@ -315,11 +312,11 @@ createNextDescribe( }, Object { "attributes": Object { - "next.span_name": "Render page /pages/getServerSideProps", + "next.span_name": "render page /pages/static/[param]/getStaticProps", "next.span_type": "Render.renderDocument", }, "kind": 0, - "name": "Render page /pages/getServerSideProps", + "name": "render page /pages/static/[param]/getStaticProps", "parentId": "[parent-id]", "status": Object { "code": 0, diff --git a/test/e2e/opentelemetry/pages/pages/static/[param]/getStaticProps.tsx b/test/e2e/opentelemetry/pages/pages/static/[param]/getStaticProps.tsx new file mode 100644 index 0000000000000..0429df7d29a91 --- /dev/null +++ b/test/e2e/opentelemetry/pages/pages/static/[param]/getStaticProps.tsx @@ -0,0 +1,17 @@ +export default function Page() { + return
Page
+} + +export function getStaticProps() { + return { + props: {}, + } +} + +// We want to make sure that we are running getStaticProps on server and thus we are creating a traces for it. +export async function getStaticPaths() { + return { + paths: [], + fallback: 'blocking', + } +} From 8e9395db2232acf94b480ca25331a5853f09059d Mon Sep 17 00:00:00 2001 From: Jan Kaifer Date: Tue, 21 Mar 2023 14:31:27 +0100 Subject: [PATCH 05/29] add proper rendering span for app directory --- packages/next/src/server/app-render/index.tsx | 6 +++++- .../next/src/server/lib/trace/constants.ts | 1 + packages/next/src/server/render.tsx | 2 +- .../opentelemetry/app/app/rsc-fetch/page.tsx | 2 +- test/e2e/opentelemetry/opentelemetry.test.ts | 20 +++++++++++++++---- 5 files changed, 24 insertions(+), 7 deletions(-) diff --git a/packages/next/src/server/app-render/index.tsx b/packages/next/src/server/app-render/index.tsx index a694a49688f7a..f3d3feb6f2c3d 100644 --- a/packages/next/src/server/app-render/index.tsx +++ b/packages/next/src/server/app-render/index.tsx @@ -1157,6 +1157,10 @@ export async function renderToHTMLOrFlight( const bodyResult = getTracer().wrap( AppRenderSpan.getBodyResult, + { + spanName: `render page (app) ${pathname}`, + }, + async ({ asNotFound, }: { @@ -1315,7 +1319,7 @@ export async function renderToHTMLOrFlight( } ) - // For action requests, we handle them differently with a sepcial render result. + // For action requests, we handle them differently with a special render result. let actionId = req.headers[ACTION.toLowerCase()] as string const isFormAction = req.method === 'POST' && diff --git a/packages/next/src/server/lib/trace/constants.ts b/packages/next/src/server/lib/trace/constants.ts index dbfe97b4491b8..f3d99f454bc86 100644 --- a/packages/next/src/server/lib/trace/constants.ts +++ b/packages/next/src/server/lib/trace/constants.ts @@ -104,6 +104,7 @@ export const NextVanillaSpanAllowlist = [ NextNodeServerSpan.findPageComponents, RenderSpan.getServerSideProps, AppRenderSpan.fetch, + AppRenderSpan.getBodyResult, RenderSpan.renderDocument, ] diff --git a/packages/next/src/server/render.tsx b/packages/next/src/server/render.tsx index 7ec1bd2d43d5b..0b98bf458df6e 100644 --- a/packages/next/src/server/render.tsx +++ b/packages/next/src/server/render.tsx @@ -1333,7 +1333,7 @@ export async function renderToHTML( const documentResult = await getTracer().trace( RenderSpan.renderDocument, { - spanName: `render page ${renderOpts.pathname}`, + spanName: `render page (pages) ${renderOpts.pathname}`, }, async () => renderDocument() ) diff --git a/test/e2e/opentelemetry/app/app/rsc-fetch/page.tsx b/test/e2e/opentelemetry/app/app/rsc-fetch/page.tsx index 797bbd84c4a7e..da8cb48490ef9 100644 --- a/test/e2e/opentelemetry/app/app/rsc-fetch/page.tsx +++ b/test/e2e/opentelemetry/app/app/rsc-fetch/page.tsx @@ -1,4 +1,4 @@ -// We are fetching our own API +// We want to trace this fetch in runtime export const dynamic = 'force-dynamic' export default async function Page() { diff --git a/test/e2e/opentelemetry/opentelemetry.test.ts b/test/e2e/opentelemetry/opentelemetry.test.ts index 5ca9f44636ad7..8db0616248aa6 100644 --- a/test/e2e/opentelemetry/opentelemetry.test.ts +++ b/test/e2e/opentelemetry/opentelemetry.test.ts @@ -184,6 +184,18 @@ createNextDescribe( "code": 0, }, }, + Object { + "attributes": Object { + "next.span_name": "render page (app) /app/rsc-fetch", + "next.span_type": "AppRender.getBodyResult", + }, + "kind": 0, + "name": "render page (app) /app/rsc-fetch", + "parentId": "[parent-id]", + "status": Object { + "code": 0, + }, + }, Object { "attributes": Object { "http.method": "GET", @@ -263,11 +275,11 @@ createNextDescribe( }, Object { "attributes": Object { - "next.span_name": "render page /pages/getServerSideProps", + "next.span_name": "render page (pages) /pages/getServerSideProps", "next.span_type": "Render.renderDocument", }, "kind": 0, - "name": "render page /pages/getServerSideProps", + "name": "render page (pages) /pages/getServerSideProps", "parentId": "[parent-id]", "status": Object { "code": 0, @@ -312,11 +324,11 @@ createNextDescribe( }, Object { "attributes": Object { - "next.span_name": "render page /pages/static/[param]/getStaticProps", + "next.span_name": "render page (pages) /pages/static/[param]/getStaticProps", "next.span_type": "Render.renderDocument", }, "kind": 0, - "name": "render page /pages/static/[param]/getStaticProps", + "name": "render page (pages) /pages/static/[param]/getStaticProps", "parentId": "[parent-id]", "status": Object { "code": 0, From 316adfa9be92c7fce78ae2f983aae609020ed8dc Mon Sep 17 00:00:00 2001 From: Jan Kaifer Date: Tue, 21 Mar 2023 15:08:39 +0100 Subject: [PATCH 06/29] refactored tests and add test for app api handlers --- .../app/{app/api => api/app}/data/route.ts | 0 test/e2e/opentelemetry/app/app/layout.tsx | 7 +- .../opentelemetry/app/app/loading/loading.tsx | 0 .../opentelemetry/app/app/loading/page.tsx | 7 + test/e2e/opentelemetry/opentelemetry.test.ts | 569 +++++++++--------- test/e2e/opentelemetry/package.json | 13 + test/lib/create-next-install.js | 1 + 7 files changed, 327 insertions(+), 270 deletions(-) rename test/e2e/opentelemetry/app/{app/api => api/app}/data/route.ts (100%) create mode 100644 test/e2e/opentelemetry/app/app/loading/loading.tsx create mode 100644 test/e2e/opentelemetry/app/app/loading/page.tsx create mode 100644 test/e2e/opentelemetry/package.json diff --git a/test/e2e/opentelemetry/app/app/api/data/route.ts b/test/e2e/opentelemetry/app/api/app/data/route.ts similarity index 100% rename from test/e2e/opentelemetry/app/app/api/data/route.ts rename to test/e2e/opentelemetry/app/api/app/data/route.ts diff --git a/test/e2e/opentelemetry/app/app/layout.tsx b/test/e2e/opentelemetry/app/app/layout.tsx index c7295294439d6..6a52599f7ca7b 100644 --- a/test/e2e/opentelemetry/app/app/layout.tsx +++ b/test/e2e/opentelemetry/app/app/layout.tsx @@ -1,4 +1,9 @@ -export default function Layout({ children }: { children: React.ReactNode }) { +export default async function Layout({ + children, +}: { + children: React.ReactNode +}) { + await new Promise((resolve) => setTimeout(resolve, 5000)) return ( {children} diff --git a/test/e2e/opentelemetry/app/app/loading/loading.tsx b/test/e2e/opentelemetry/app/app/loading/loading.tsx new file mode 100644 index 0000000000000..e69de29bb2d1d diff --git a/test/e2e/opentelemetry/app/app/loading/page.tsx b/test/e2e/opentelemetry/app/app/loading/page.tsx new file mode 100644 index 0000000000000..f3fcb09ab6c8b --- /dev/null +++ b/test/e2e/opentelemetry/app/app/loading/page.tsx @@ -0,0 +1,7 @@ +// We want to trace this fetch in runtime +export const dynamic = 'force-dynamic' + +export default async function Page() { + await new Promise((resolve) => setTimeout(resolve, 1000)) + return

app/loading/page

+} diff --git a/test/e2e/opentelemetry/opentelemetry.test.ts b/test/e2e/opentelemetry/opentelemetry.test.ts index 8db0616248aa6..c566ca063ff89 100644 --- a/test/e2e/opentelemetry/opentelemetry.test.ts +++ b/test/e2e/opentelemetry/opentelemetry.test.ts @@ -8,17 +8,7 @@ createNextDescribe( { files: __dirname, skipDeployment: true, - dependencies: { - 'fs-extra': '^8.0.0', - '@types/fs-extra': '^8.0.0', - '@opentelemetry/api': '^1.0.0', - '@opentelemetry/core': '^1.0.0', - '@opentelemetry/resources': '^1.0.0', - '@opentelemetry/sdk-trace-base': '^1.0.0', - '@opentelemetry/sdk-trace-node': '^1.0.0', - '@opentelemetry/semantic-conventions': '^1.0.0', - '@opentelemetry/exporter-trace-otlp-http': '^0.34.0', - }, + dependencies: require('./package.json').dependencies, }, ({ next }) => { const getTraces = async (): Promise => { @@ -90,276 +80,317 @@ createNextDescribe( await cleanTraces() }) - it('should have spans when accessing page', async () => { - await next.fetch('/pages') + describe('app router', () => { + it('should handle RSC with fetch', async () => { + await next.fetch('/app/rsc-fetch') - expect(await getSanitizedTraces(1)).toMatchInlineSnapshot(` - Array [ - Object { - "attributes": Object { - "http.method": "GET", - "http.status_code": 200, - "http.target": "/pages", - "next.span_name": "GET /pages", - "next.span_type": "BaseServer.handleRequest", - }, - "kind": 1, - "name": "GET /pages", - "parentId": undefined, - "status": Object { - "code": 0, - }, - }, - Object { - "attributes": Object { - "next.route": "/pages", - "next.span_name": "resolving page into components", - "next.span_type": "NextNodeServer.findPageComponents", - }, - "kind": 0, - "name": "resolving page into components", - "parentId": "[parent-id]", - "status": Object { - "code": 0, - }, - }, - ] - `) - }) + expect(await getSanitizedTraces(1)).toMatchInlineSnapshot(` + Array [ + Object { + "attributes": Object { + "http.method": "GET", + "http.url": "https://vercel.com/", + "net.peer.name": "vercel.com", + "next.span_name": "fetch GET https://vercel.com/", + "next.span_type": "AppRender.fetch", + }, + "kind": 2, + "name": "fetch GET https://vercel.com/", + "parentId": "[parent-id]", + "status": Object { + "code": 0, + }, + }, + Object { + "attributes": Object { + "next.span_name": "render page (app) /app/rsc-fetch", + "next.span_type": "AppRender.getBodyResult", + }, + "kind": 0, + "name": "render page (app) /app/rsc-fetch", + "parentId": "[parent-id]", + "status": Object { + "code": 0, + }, + }, + Object { + "attributes": Object { + "http.method": "GET", + "http.status_code": 200, + "http.target": "/app/rsc-fetch", + "next.span_name": "GET /app/rsc-fetch", + "next.span_type": "BaseServer.handleRequest", + }, + "kind": 1, + "name": "GET /app/rsc-fetch", + "parentId": undefined, + "status": Object { + "code": 0, + }, + }, + Object { + "attributes": Object { + "next.route": "/app/rsc-fetch/page", + "next.span_name": "resolving page into components", + "next.span_type": "NextNodeServer.findPageComponents", + }, + "kind": 0, + "name": "resolving page into components", + "parentId": "[parent-id]", + "status": Object { + "code": 0, + }, + }, + ] + `) + }) - it('should handle route params', async () => { - await next.fetch('/pages/params/stuff') + it('should handle route handlers in app router', async () => { + await next.fetch('/api/app/data') - expect(await getSanitizedTraces(1)).toMatchInlineSnapshot(` - Array [ - Object { - "attributes": Object { - "http.method": "GET", - "http.status_code": 200, - "http.target": "/pages/params/stuff", - "next.span_name": "GET /pages/params/stuff", - "next.span_type": "BaseServer.handleRequest", - }, - "kind": 1, - "name": "GET /pages/params/stuff", - "parentId": undefined, - "status": Object { - "code": 0, - }, - }, - Object { - "attributes": Object { - "next.route": "/pages/params/[param]", - "next.span_name": "resolving page into components", - "next.span_type": "NextNodeServer.findPageComponents", - }, - "kind": 0, - "name": "resolving page into components", - "parentId": "[parent-id]", - "status": Object { - "code": 0, - }, - }, - ] - `) + expect(await getSanitizedTraces(1)).toMatchInlineSnapshot(` + Array [ + Object { + "attributes": Object { + "http.method": "GET", + "http.status_code": 200, + "http.target": "/api/app/data", + "next.span_name": "GET /api/app/data", + "next.span_type": "BaseServer.handleRequest", + }, + "kind": 1, + "name": "GET /api/app/data", + "parentId": undefined, + "status": Object { + "code": 0, + }, + }, + Object { + "attributes": Object { + "next.route": "/api/app/data/route", + "next.span_name": "resolving page into components", + "next.span_type": "NextNodeServer.findPageComponents", + }, + "kind": 0, + "name": "resolving page into components", + "parentId": "[parent-id]", + "status": Object { + "code": 0, + }, + }, + ] + `) + }) }) - it('should handle RSC with fetch', async () => { - await next.fetch('/app/rsc-fetch') + describe('pages', () => { + it('should have spans when accessing page', async () => { + await next.fetch('/pages') - expect(await getSanitizedTraces(1)).toMatchInlineSnapshot(` - Array [ - Object { - "attributes": Object { - "http.method": "GET", - "http.url": "https://vercel.com/", - "net.peer.name": "vercel.com", - "next.span_name": "fetch GET https://vercel.com/", - "next.span_type": "AppRender.fetch", - }, - "kind": 2, - "name": "fetch GET https://vercel.com/", - "parentId": "[parent-id]", - "status": Object { - "code": 0, - }, - }, - Object { - "attributes": Object { - "next.span_name": "render page (app) /app/rsc-fetch", - "next.span_type": "AppRender.getBodyResult", - }, - "kind": 0, - "name": "render page (app) /app/rsc-fetch", - "parentId": "[parent-id]", - "status": Object { - "code": 0, - }, - }, - Object { - "attributes": Object { - "http.method": "GET", - "http.status_code": 200, - "http.target": "/app/rsc-fetch", - "next.span_name": "GET /app/rsc-fetch", - "next.span_type": "BaseServer.handleRequest", - }, - "kind": 1, - "name": "GET /app/rsc-fetch", - "parentId": undefined, - "status": Object { - "code": 0, - }, - }, - Object { - "attributes": Object { - "next.route": "/app/rsc-fetch/page", - "next.span_name": "resolving page into components", - "next.span_type": "NextNodeServer.findPageComponents", - }, - "kind": 0, - "name": "resolving page into components", - "parentId": "[parent-id]", - "status": Object { - "code": 0, - }, - }, - ] - `) - }) + expect(await getSanitizedTraces(1)).toMatchInlineSnapshot(` + Array [ + Object { + "attributes": Object { + "http.method": "GET", + "http.status_code": 200, + "http.target": "/pages", + "next.span_name": "GET /pages", + "next.span_type": "BaseServer.handleRequest", + }, + "kind": 1, + "name": "GET /pages", + "parentId": undefined, + "status": Object { + "code": 0, + }, + }, + Object { + "attributes": Object { + "next.route": "/pages", + "next.span_name": "resolving page into components", + "next.span_type": "NextNodeServer.findPageComponents", + }, + "kind": 0, + "name": "resolving page into components", + "parentId": "[parent-id]", + "status": Object { + "code": 0, + }, + }, + ] + `) + }) - it('should handle getServerSideProps', async () => { - await next.fetch('/pages/getServerSideProps') + it('should handle route params', async () => { + await next.fetch('/pages/params/stuff') - expect(await getSanitizedTraces(1)).toMatchInlineSnapshot(` - Array [ - Object { - "attributes": Object { - "http.method": "GET", - "http.status_code": 200, - "http.target": "/pages/getServerSideProps", - "next.span_name": "GET /pages/getServerSideProps", - "next.span_type": "BaseServer.handleRequest", - }, - "kind": 1, - "name": "GET /pages/getServerSideProps", - "parentId": undefined, - "status": Object { - "code": 0, - }, - }, - Object { - "attributes": Object { - "next.route": "/pages/getServerSideProps", - "next.span_name": "resolving page into components", - "next.span_type": "NextNodeServer.findPageComponents", - }, - "kind": 0, - "name": "resolving page into components", - "parentId": "[parent-id]", - "status": Object { - "code": 0, - }, - }, - Object { - "attributes": Object { - "next.span_name": "getServerSideProps /pages/getServerSideProps", - "next.span_type": "Render.getServerSideProps", - }, - "kind": 0, - "name": "getServerSideProps /pages/getServerSideProps", - "parentId": "[parent-id]", - "status": Object { - "code": 0, - }, - }, - Object { - "attributes": Object { - "next.span_name": "render page (pages) /pages/getServerSideProps", - "next.span_type": "Render.renderDocument", - }, - "kind": 0, - "name": "render page (pages) /pages/getServerSideProps", - "parentId": "[parent-id]", - "status": Object { - "code": 0, - }, - }, - ] - `) - }) + expect(await getSanitizedTraces(1)).toMatchInlineSnapshot(` + Array [ + Object { + "attributes": Object { + "http.method": "GET", + "http.status_code": 200, + "http.target": "/pages/params/stuff", + "next.span_name": "GET /pages/params/stuff", + "next.span_type": "BaseServer.handleRequest", + }, + "kind": 1, + "name": "GET /pages/params/stuff", + "parentId": undefined, + "status": Object { + "code": 0, + }, + }, + Object { + "attributes": Object { + "next.route": "/pages/params/[param]", + "next.span_name": "resolving page into components", + "next.span_type": "NextNodeServer.findPageComponents", + }, + "kind": 0, + "name": "resolving page into components", + "parentId": "[parent-id]", + "status": Object { + "code": 0, + }, + }, + ] + `) + }) - it("should handle getStaticProps when fallback: 'blocking'", async () => { - await next.fetch('/pages/static/param/getStaticProps') + it('should handle getServerSideProps', async () => { + await next.fetch('/pages/getServerSideProps') - expect(await getSanitizedTraces(1)).toMatchInlineSnapshot(` - Array [ - Object { - "attributes": Object { - "http.method": "GET", - "http.status_code": 200, - "http.target": "/pages/static/param/getStaticProps", - "next.span_name": "GET /pages/static/param/getStaticProps", - "next.span_type": "BaseServer.handleRequest", - }, - "kind": 1, - "name": "GET /pages/static/param/getStaticProps", - "parentId": undefined, - "status": Object { - "code": 0, - }, - }, - Object { - "attributes": Object { - "next.route": "/pages/static/[param]/getStaticProps", - "next.span_name": "resolving page into components", - "next.span_type": "NextNodeServer.findPageComponents", - }, - "kind": 0, - "name": "resolving page into components", - "parentId": "[parent-id]", - "status": Object { - "code": 0, - }, - }, - Object { - "attributes": Object { - "next.span_name": "render page (pages) /pages/static/[param]/getStaticProps", - "next.span_type": "Render.renderDocument", - }, - "kind": 0, - "name": "render page (pages) /pages/static/[param]/getStaticProps", - "parentId": "[parent-id]", - "status": Object { - "code": 0, - }, - }, - ] - `) - }) + expect(await getSanitizedTraces(1)).toMatchInlineSnapshot(` + Array [ + Object { + "attributes": Object { + "http.method": "GET", + "http.status_code": 200, + "http.target": "/pages/getServerSideProps", + "next.span_name": "GET /pages/getServerSideProps", + "next.span_type": "BaseServer.handleRequest", + }, + "kind": 1, + "name": "GET /pages/getServerSideProps", + "parentId": undefined, + "status": Object { + "code": 0, + }, + }, + Object { + "attributes": Object { + "next.route": "/pages/getServerSideProps", + "next.span_name": "resolving page into components", + "next.span_type": "NextNodeServer.findPageComponents", + }, + "kind": 0, + "name": "resolving page into components", + "parentId": "[parent-id]", + "status": Object { + "code": 0, + }, + }, + Object { + "attributes": Object { + "next.span_name": "getServerSideProps /pages/getServerSideProps", + "next.span_type": "Render.getServerSideProps", + }, + "kind": 0, + "name": "getServerSideProps /pages/getServerSideProps", + "parentId": "[parent-id]", + "status": Object { + "code": 0, + }, + }, + Object { + "attributes": Object { + "next.span_name": "render page (pages) /pages/getServerSideProps", + "next.span_type": "Render.renderDocument", + }, + "kind": 0, + "name": "render page (pages) /pages/getServerSideProps", + "parentId": "[parent-id]", + "status": Object { + "code": 0, + }, + }, + ] + `) + }) - it('should handle route handlers in app router', async () => { - await next.fetch('/api/pages/basic') + it("should handle getStaticProps when fallback: 'blocking'", async () => { + await next.fetch('/pages/static/param/getStaticProps') - expect(await getSanitizedTraces(1)).toMatchInlineSnapshot(` - Array [ - Object { - "attributes": Object { - "http.method": "GET", - "http.status_code": 200, - "http.target": "/api/pages/basic", - "next.span_name": "GET /api/pages/basic", - "next.span_type": "BaseServer.handleRequest", - }, - "kind": 1, - "name": "GET /api/pages/basic", - "parentId": undefined, - "status": Object { - "code": 0, - }, - }, - ] - `) + expect(await getSanitizedTraces(1)).toMatchInlineSnapshot(` + Array [ + Object { + "attributes": Object { + "http.method": "GET", + "http.status_code": 200, + "http.target": "/pages/static/param/getStaticProps", + "next.span_name": "GET /pages/static/param/getStaticProps", + "next.span_type": "BaseServer.handleRequest", + }, + "kind": 1, + "name": "GET /pages/static/param/getStaticProps", + "parentId": undefined, + "status": Object { + "code": 0, + }, + }, + Object { + "attributes": Object { + "next.route": "/pages/static/[param]/getStaticProps", + "next.span_name": "resolving page into components", + "next.span_type": "NextNodeServer.findPageComponents", + }, + "kind": 0, + "name": "resolving page into components", + "parentId": "[parent-id]", + "status": Object { + "code": 0, + }, + }, + Object { + "attributes": Object { + "next.span_name": "render page (pages) /pages/static/[param]/getStaticProps", + "next.span_type": "Render.renderDocument", + }, + "kind": 0, + "name": "render page (pages) /pages/static/[param]/getStaticProps", + "parentId": "[parent-id]", + "status": Object { + "code": 0, + }, + }, + ] + `) + }) + + it('should handle api routes in pages', async () => { + await next.fetch('/api/pages/basic') + + expect(await getSanitizedTraces(1)).toMatchInlineSnapshot(` + Array [ + Object { + "attributes": Object { + "http.method": "GET", + "http.status_code": 200, + "http.target": "/api/pages/basic", + "next.span_name": "GET /api/pages/basic", + "next.span_type": "BaseServer.handleRequest", + }, + "kind": 1, + "name": "GET /api/pages/basic", + "parentId": undefined, + "status": Object { + "code": 0, + }, + }, + ] + `) + }) }) } ) diff --git a/test/e2e/opentelemetry/package.json b/test/e2e/opentelemetry/package.json new file mode 100644 index 0000000000000..a9c38e4ba3a41 --- /dev/null +++ b/test/e2e/opentelemetry/package.json @@ -0,0 +1,13 @@ +{ + "dependencies": { + "fs-extra": "^8.0.0", + "@types/fs-extra": "^8.0.0", + "@opentelemetry/api": "^1.0.0", + "@opentelemetry/core": "^1.0.0", + "@opentelemetry/resources": "^1.0.0", + "@opentelemetry/sdk-trace-base": "^1.0.0", + "@opentelemetry/sdk-trace-node": "^1.0.0", + "@opentelemetry/semantic-conventions": "^1.0.0", + "@opentelemetry/exporter-trace-otlp-http": "^0.34.0" + } +} diff --git a/test/lib/create-next-install.js b/test/lib/create-next-install.js index 0882b5cb2e32a..de10ba3e0f3eb 100644 --- a/test/lib/create-next-install.js +++ b/test/lib/create-next-install.js @@ -74,6 +74,7 @@ async function createNextInstall({ filter: (item) => { return ( !item.includes('node_modules') && + !item.includes('pnpm-lock.yaml') && !item.includes('.DS_Store') && // Exclude Rust compilation files !/next[\\/]build[\\/]swc[\\/]target/.test(item) && From 06e70647f4c350528df9917ed0f884c7bb4dfad0 Mon Sep 17 00:00:00 2001 From: Jan Kaifer Date: Tue, 21 Mar 2023 15:26:27 +0100 Subject: [PATCH 07/29] Wrap pages api routes with span --- packages/next/src/server/api-utils/node.ts | 10 +++- .../next/src/server/lib/trace/constants.ts | 7 +++ test/e2e/opentelemetry/opentelemetry.test.ts | 48 ++++++++++++------- 3 files changed, 46 insertions(+), 19 deletions(-) diff --git a/packages/next/src/server/api-utils/node.ts b/packages/next/src/server/api-utils/node.ts index 55dd33bc308e3..932ed5b1fa63b 100644 --- a/packages/next/src/server/api-utils/node.ts +++ b/packages/next/src/server/api-utils/node.ts @@ -34,6 +34,8 @@ import { RESPONSE_LIMIT_DEFAULT, } from './index' import { mockRequest } from '../lib/mock-request' +import { getTracer } from '../lib/trace/tracer' +import { NodeSpan } from '../lib/trace/constants' export function tryGetPreviewData( req: IncomingMessage | BaseNextRequest, @@ -528,7 +530,13 @@ export async function apiResolver( } // Call API route method - await resolver(req, res) + await getTracer().trace( + NodeSpan.runHandler, + { + spanName: `executing api route (pages) ${page}`, + }, + () => resolver(req, res) + ) if ( process.env.NODE_ENV !== 'production' && diff --git a/packages/next/src/server/lib/trace/constants.ts b/packages/next/src/server/lib/trace/constants.ts index f3d99f454bc86..32df98439ae4f 100644 --- a/packages/next/src/server/lib/trace/constants.ts +++ b/packages/next/src/server/lib/trace/constants.ts @@ -88,6 +88,10 @@ enum RouterSpan { executeRoute = 'Router.executeRoute', } +enum NodeSpan { + runHandler = 'Node.runHandler', +} + type SpanTypes = | `${BaseServerSpan}` | `${LoadComponentsSpan}` @@ -97,6 +101,7 @@ type SpanTypes = | `${RenderSpan}` | `${RouterSpan}` | `${AppRenderSpan}` + | `${NodeSpan}` // This list is used to filter out spans that are not relevant to the user export const NextVanillaSpanAllowlist = [ @@ -106,6 +111,7 @@ export const NextVanillaSpanAllowlist = [ AppRenderSpan.fetch, AppRenderSpan.getBodyResult, RenderSpan.renderDocument, + NodeSpan.runHandler, ] export { @@ -118,4 +124,5 @@ export { RenderSpan, RouterSpan, AppRenderSpan, + NodeSpan, } diff --git a/test/e2e/opentelemetry/opentelemetry.test.ts b/test/e2e/opentelemetry/opentelemetry.test.ts index c566ca063ff89..ec1ff804c69fc 100644 --- a/test/e2e/opentelemetry/opentelemetry.test.ts +++ b/test/e2e/opentelemetry/opentelemetry.test.ts @@ -372,24 +372,36 @@ createNextDescribe( await next.fetch('/api/pages/basic') expect(await getSanitizedTraces(1)).toMatchInlineSnapshot(` - Array [ - Object { - "attributes": Object { - "http.method": "GET", - "http.status_code": 200, - "http.target": "/api/pages/basic", - "next.span_name": "GET /api/pages/basic", - "next.span_type": "BaseServer.handleRequest", - }, - "kind": 1, - "name": "GET /api/pages/basic", - "parentId": undefined, - "status": Object { - "code": 0, - }, - }, - ] - `) + Array [ + Object { + "attributes": Object { + "http.method": "GET", + "http.status_code": 200, + "http.target": "/api/pages/basic", + "next.span_name": "GET /api/pages/basic", + "next.span_type": "BaseServer.handleRequest", + }, + "kind": 1, + "name": "GET /api/pages/basic", + "parentId": undefined, + "status": Object { + "code": 0, + }, + }, + Object { + "attributes": Object { + "next.span_name": "executing api route (pages) /api/pages/basic", + "next.span_type": "Node.runHandler", + }, + "kind": 0, + "name": "executing api route (pages) /api/pages/basic", + "parentId": "[parent-id]", + "status": Object { + "code": 0, + }, + }, + ] + `) }) }) } From 3a546e1a01c34e2d3fc36bcd0bba33a10448079a Mon Sep 17 00:00:00 2001 From: Jan Kaifer Date: Tue, 21 Mar 2023 16:19:38 +0100 Subject: [PATCH 08/29] add span around api handlers --- .../route-handlers/app-route-route-handler.ts | 24 ++++++++++++++++++- .../next/src/server/lib/trace/constants.ts | 7 ++++++ .../opentelemetry/app/api/app/data/route.ts | 2 ++ test/e2e/opentelemetry/opentelemetry.test.ts | 13 +++++++++- 4 files changed, 44 insertions(+), 2 deletions(-) diff --git a/packages/next/src/server/future/route-handlers/app-route-route-handler.ts b/packages/next/src/server/future/route-handlers/app-route-route-handler.ts index 94dab3bdb2b15..48eaf363da8ec 100644 --- a/packages/next/src/server/future/route-handlers/app-route-route-handler.ts +++ b/packages/next/src/server/future/route-handlers/app-route-route-handler.ts @@ -38,6 +38,9 @@ import { AppConfig } from '../../../build/utils' import { RequestCookies } from 'next/dist/compiled/@edge-runtime/cookies' import { NextURL } from '../../web/next-url' import { NextConfig } from '../../config-shared' +import { getTracer } from '../../lib/trace/tracer' +import { AppRouteRouteHandlersSpan } from '../../lib/trace/constants' +import path from 'path' // TODO-APP: This module has a dynamic require so when bundling for edge it causes issues. const NodeModuleLoader = @@ -340,6 +343,16 @@ function proxyRequest(req: NextRequest, module: AppRouteModule): NextRequest { }) } +function getPathnameFromAbsolutePath(absolutePath: string) { + // Remove prefix including app dir + const appDir = path.sep + 'app' + path.sep + const [, ...parts] = absolutePath.split(appDir) + const relativePath = path.sep + parts.join(appDir) + + // remove extension + const pathname = relativePath.split('.').slice(0, -1).join('.') + return pathname +} export class AppRouteRouteHandler implements RouteHandler { constructor( private readonly nextConfigOutput: NextConfig['output'] = undefined, @@ -527,7 +540,16 @@ export class AppRouteRouteHandler implements RouteHandler { module ) - return handle(wrappedRequest, { params }) + return getTracer().trace( + AppRouteRouteHandlersSpan.runHandler, + { + // TODO: propagate this pathname from route matcher + spanName: `executing api route (app) ${getPathnameFromAbsolutePath( + module.resolvedPagePath + )}`, + }, + () => handle(wrappedRequest, { params }) + ) } ) ) diff --git a/packages/next/src/server/lib/trace/constants.ts b/packages/next/src/server/lib/trace/constants.ts index 32df98439ae4f..fa54b636b8575 100644 --- a/packages/next/src/server/lib/trace/constants.ts +++ b/packages/next/src/server/lib/trace/constants.ts @@ -92,6 +92,10 @@ enum NodeSpan { runHandler = 'Node.runHandler', } +enum AppRouteRouteHandlersSpan { + runHandler = 'AppRouteRouteHandlers.runHandler', +} + type SpanTypes = | `${BaseServerSpan}` | `${LoadComponentsSpan}` @@ -102,6 +106,7 @@ type SpanTypes = | `${RouterSpan}` | `${AppRenderSpan}` | `${NodeSpan}` + | `${AppRouteRouteHandlersSpan}` // This list is used to filter out spans that are not relevant to the user export const NextVanillaSpanAllowlist = [ @@ -112,6 +117,7 @@ export const NextVanillaSpanAllowlist = [ AppRenderSpan.getBodyResult, RenderSpan.renderDocument, NodeSpan.runHandler, + AppRouteRouteHandlersSpan.runHandler, ] export { @@ -125,4 +131,5 @@ export { RouterSpan, AppRenderSpan, NodeSpan, + AppRouteRouteHandlersSpan, } diff --git a/test/e2e/opentelemetry/app/api/app/data/route.ts b/test/e2e/opentelemetry/app/api/app/data/route.ts index f3d6d572c5a42..f2b89ee775dee 100644 --- a/test/e2e/opentelemetry/app/api/app/data/route.ts +++ b/test/e2e/opentelemetry/app/api/app/data/route.ts @@ -1,3 +1,5 @@ export async function GET() { return new Response(JSON.stringify({ test: 'data' })) } + +export const dynamic = 'force-dynamic' diff --git a/test/e2e/opentelemetry/opentelemetry.test.ts b/test/e2e/opentelemetry/opentelemetry.test.ts index ec1ff804c69fc..31aed1ca0a3bc 100644 --- a/test/e2e/opentelemetry/opentelemetry.test.ts +++ b/test/e2e/opentelemetry/opentelemetry.test.ts @@ -150,10 +150,21 @@ createNextDescribe( expect(await getSanitizedTraces(1)).toMatchInlineSnapshot(` Array [ + Object { + "attributes": Object { + "next.span_name": "executing api route (app) /api/app/data/route", + "next.span_type": "AppRouteRouteHandlers.runHandler", + }, + "kind": 0, + "name": "executing api route (app) /api/app/data/route", + "parentId": "[parent-id]", + "status": Object { + "code": 0, + }, + }, Object { "attributes": Object { "http.method": "GET", - "http.status_code": 200, "http.target": "/api/app/data", "next.span_name": "GET /api/app/data", "next.span_type": "BaseServer.handleRequest", From d69189dae6ee24d0e424481a3452db7dd6966f06 Mon Sep 17 00:00:00 2001 From: Jan Kaifer Date: Tue, 21 Mar 2023 17:09:10 +0100 Subject: [PATCH 09/29] fix loading example --- test/e2e/opentelemetry/app/app/loading/loading.tsx | 4 ++++ test/e2e/opentelemetry/app/app/loading/page.tsx | 7 ------- .../opentelemetry/app/app/loading/page1/page.tsx | 14 ++++++++++++++ .../opentelemetry/app/app/loading/page2/page.tsx | 14 ++++++++++++++ 4 files changed, 32 insertions(+), 7 deletions(-) delete mode 100644 test/e2e/opentelemetry/app/app/loading/page.tsx create mode 100644 test/e2e/opentelemetry/app/app/loading/page1/page.tsx create mode 100644 test/e2e/opentelemetry/app/app/loading/page2/page.tsx diff --git a/test/e2e/opentelemetry/app/app/loading/loading.tsx b/test/e2e/opentelemetry/app/app/loading/loading.tsx index e69de29bb2d1d..e23bf121e5181 100644 --- a/test/e2e/opentelemetry/app/app/loading/loading.tsx +++ b/test/e2e/opentelemetry/app/app/loading/loading.tsx @@ -0,0 +1,4 @@ +export default function Loading() { + new Promise((resolve) => setTimeout(resolve, 3000)) + return
loading
+} diff --git a/test/e2e/opentelemetry/app/app/loading/page.tsx b/test/e2e/opentelemetry/app/app/loading/page.tsx deleted file mode 100644 index f3fcb09ab6c8b..0000000000000 --- a/test/e2e/opentelemetry/app/app/loading/page.tsx +++ /dev/null @@ -1,7 +0,0 @@ -// We want to trace this fetch in runtime -export const dynamic = 'force-dynamic' - -export default async function Page() { - await new Promise((resolve) => setTimeout(resolve, 1000)) - return

app/loading/page

-} diff --git a/test/e2e/opentelemetry/app/app/loading/page1/page.tsx b/test/e2e/opentelemetry/app/app/loading/page1/page.tsx new file mode 100644 index 0000000000000..2fbdff3f713ba --- /dev/null +++ b/test/e2e/opentelemetry/app/app/loading/page1/page.tsx @@ -0,0 +1,14 @@ +import Link from 'next/link' + +// We want to trace this fetch in runtime +export const dynamic = 'force-dynamic' + +export default async function Page() { + await new Promise((resolve) => setTimeout(resolve, 5000)) + return ( + <> +

app/loading/page1

+ Page2 + + ) +} diff --git a/test/e2e/opentelemetry/app/app/loading/page2/page.tsx b/test/e2e/opentelemetry/app/app/loading/page2/page.tsx new file mode 100644 index 0000000000000..ee2b37fe5a6ac --- /dev/null +++ b/test/e2e/opentelemetry/app/app/loading/page2/page.tsx @@ -0,0 +1,14 @@ +import Link from 'next/link' + +// We want to trace this fetch in runtime +export const dynamic = 'force-dynamic' + +export default async function Page() { + await new Promise((resolve) => setTimeout(resolve, 5000)) + return ( + <> +

app/loading/page2

+ Page1 + + ) +} From 00457b5a8b345310716d86c159b29c6f7827d4dd Mon Sep 17 00:00:00 2001 From: Jan Kaifer Date: Tue, 21 Mar 2023 17:30:16 +0100 Subject: [PATCH 10/29] Add generate metadata span --- .../next/src/lib/metadata/resolve-metadata.ts | 11 +- .../next/src/server/lib/trace/constants.ts | 7 + .../opentelemetry/app/app/rsc-fetch/page.tsx | 4 + test/e2e/opentelemetry/opentelemetry.test.ts | 128 ++++++++++-------- 4 files changed, 91 insertions(+), 59 deletions(-) diff --git a/packages/next/src/lib/metadata/resolve-metadata.ts b/packages/next/src/lib/metadata/resolve-metadata.ts index 7c8d5e97ad598..767f320a4520f 100644 --- a/packages/next/src/lib/metadata/resolve-metadata.ts +++ b/packages/next/src/lib/metadata/resolve-metadata.ts @@ -25,6 +25,8 @@ import { resolveViewport, } from './resolvers/resolve-basics' import { resolveIcons } from './resolvers/resolve-icons' +import { getTracer } from '../../server/lib/trace/tracer' +import { ResolveMetadataSpan } from '../../server/lib/trace/constants' type StaticMetadata = Awaited> @@ -190,7 +192,14 @@ async function getDefinedMetadata( } return ( (mod.generateMetadata - ? (parent: ResolvingMetadata) => mod.generateMetadata(props, parent) + ? (parent: ResolvingMetadata) => + getTracer().trace( + ResolveMetadataSpan.generateMetadata, + { + spanName: `generateMetadata`, + }, + () => mod.generateMetadata(props, parent) + ) : mod.metadata) || null ) } diff --git a/packages/next/src/server/lib/trace/constants.ts b/packages/next/src/server/lib/trace/constants.ts index fa54b636b8575..cc4fb49ee377c 100644 --- a/packages/next/src/server/lib/trace/constants.ts +++ b/packages/next/src/server/lib/trace/constants.ts @@ -96,6 +96,10 @@ enum AppRouteRouteHandlersSpan { runHandler = 'AppRouteRouteHandlers.runHandler', } +enum ResolveMetadataSpan { + generateMetadata = 'ResolveMetadata.generateMetadata', +} + type SpanTypes = | `${BaseServerSpan}` | `${LoadComponentsSpan}` @@ -107,6 +111,7 @@ type SpanTypes = | `${AppRenderSpan}` | `${NodeSpan}` | `${AppRouteRouteHandlersSpan}` + | `${ResolveMetadataSpan}` // This list is used to filter out spans that are not relevant to the user export const NextVanillaSpanAllowlist = [ @@ -118,6 +123,7 @@ export const NextVanillaSpanAllowlist = [ RenderSpan.renderDocument, NodeSpan.runHandler, AppRouteRouteHandlersSpan.runHandler, + ResolveMetadataSpan.generateMetadata, ] export { @@ -132,4 +138,5 @@ export { AppRenderSpan, NodeSpan, AppRouteRouteHandlersSpan, + ResolveMetadataSpan, } diff --git a/test/e2e/opentelemetry/app/app/rsc-fetch/page.tsx b/test/e2e/opentelemetry/app/app/rsc-fetch/page.tsx index da8cb48490ef9..fbc402b01d6ab 100644 --- a/test/e2e/opentelemetry/app/app/rsc-fetch/page.tsx +++ b/test/e2e/opentelemetry/app/app/rsc-fetch/page.tsx @@ -1,6 +1,10 @@ // We want to trace this fetch in runtime export const dynamic = 'force-dynamic' +export async function generateMetadata() { + return {} +} + export default async function Page() { const data = await fetch('https://vercel.com') return
{await data.text()}
diff --git a/test/e2e/opentelemetry/opentelemetry.test.ts b/test/e2e/opentelemetry/opentelemetry.test.ts index 31aed1ca0a3bc..d13a2673affcc 100644 --- a/test/e2e/opentelemetry/opentelemetry.test.ts +++ b/test/e2e/opentelemetry/opentelemetry.test.ts @@ -85,64 +85,76 @@ createNextDescribe( await next.fetch('/app/rsc-fetch') expect(await getSanitizedTraces(1)).toMatchInlineSnapshot(` - Array [ - Object { - "attributes": Object { - "http.method": "GET", - "http.url": "https://vercel.com/", - "net.peer.name": "vercel.com", - "next.span_name": "fetch GET https://vercel.com/", - "next.span_type": "AppRender.fetch", - }, - "kind": 2, - "name": "fetch GET https://vercel.com/", - "parentId": "[parent-id]", - "status": Object { - "code": 0, - }, - }, - Object { - "attributes": Object { - "next.span_name": "render page (app) /app/rsc-fetch", - "next.span_type": "AppRender.getBodyResult", - }, - "kind": 0, - "name": "render page (app) /app/rsc-fetch", - "parentId": "[parent-id]", - "status": Object { - "code": 0, - }, - }, - Object { - "attributes": Object { - "http.method": "GET", - "http.status_code": 200, - "http.target": "/app/rsc-fetch", - "next.span_name": "GET /app/rsc-fetch", - "next.span_type": "BaseServer.handleRequest", - }, - "kind": 1, - "name": "GET /app/rsc-fetch", - "parentId": undefined, - "status": Object { - "code": 0, - }, - }, - Object { - "attributes": Object { - "next.route": "/app/rsc-fetch/page", - "next.span_name": "resolving page into components", - "next.span_type": "NextNodeServer.findPageComponents", - }, - "kind": 0, - "name": "resolving page into components", - "parentId": "[parent-id]", - "status": Object { - "code": 0, - }, - }, - ] - `) + Array [ + Object { + "attributes": Object { + "http.method": "GET", + "http.url": "https://vercel.com/", + "net.peer.name": "vercel.com", + "next.span_name": "fetch GET https://vercel.com/", + "next.span_type": "AppRender.fetch", + }, + "kind": 2, + "name": "fetch GET https://vercel.com/", + "parentId": "[parent-id]", + "status": Object { + "code": 0, + }, + }, + Object { + "attributes": Object { + "next.span_name": "render page (app) /app/rsc-fetch", + "next.span_type": "AppRender.getBodyResult", + }, + "kind": 0, + "name": "render page (app) /app/rsc-fetch", + "parentId": "[parent-id]", + "status": Object { + "code": 0, + }, + }, + Object { + "attributes": Object { + "http.method": "GET", + "http.status_code": 200, + "http.target": "/app/rsc-fetch", + "next.span_name": "GET /app/rsc-fetch", + "next.span_type": "BaseServer.handleRequest", + }, + "kind": 1, + "name": "GET /app/rsc-fetch", + "parentId": undefined, + "status": Object { + "code": 0, + }, + }, + Object { + "attributes": Object { + "next.route": "/app/rsc-fetch/page", + "next.span_name": "resolving page into components", + "next.span_type": "NextNodeServer.findPageComponents", + }, + "kind": 0, + "name": "resolving page into components", + "parentId": "[parent-id]", + "status": Object { + "code": 0, + }, + }, + Object { + "attributes": Object { + "next.span_name": "generateMetadata", + "next.span_type": "ResolveMetadata.generateMetadata", + }, + "kind": 0, + "name": "generateMetadata", + "parentId": "[parent-id]", + "status": Object { + "code": 0, + }, + }, + ] + `) }) it('should handle route handlers in app router', async () => { From a06326d9f66698237bdd99a926ec119c000e7363 Mon Sep 17 00:00:00 2001 From: Jan Kaifer Date: Tue, 21 Mar 2023 17:34:35 +0100 Subject: [PATCH 11/29] Add generate metadata to layout and page --- test/e2e/opentelemetry/app/app/layout.tsx | 4 ++++ test/e2e/opentelemetry/opentelemetry.test.ts | 12 ++++++++++++ 2 files changed, 16 insertions(+) diff --git a/test/e2e/opentelemetry/app/app/layout.tsx b/test/e2e/opentelemetry/app/app/layout.tsx index 6a52599f7ca7b..09e4a22dda7a0 100644 --- a/test/e2e/opentelemetry/app/app/layout.tsx +++ b/test/e2e/opentelemetry/app/app/layout.tsx @@ -10,3 +10,7 @@ export default async function Layout({ ) } + +export async function generateMetadata() { + return {} +} diff --git a/test/e2e/opentelemetry/opentelemetry.test.ts b/test/e2e/opentelemetry/opentelemetry.test.ts index d13a2673affcc..705372cac9ce9 100644 --- a/test/e2e/opentelemetry/opentelemetry.test.ts +++ b/test/e2e/opentelemetry/opentelemetry.test.ts @@ -153,6 +153,18 @@ createNextDescribe( "code": 0, }, }, + Object { + "attributes": Object { + "next.span_name": "generateMetadata", + "next.span_type": "ResolveMetadata.generateMetadata", + }, + "kind": 0, + "name": "generateMetadata", + "parentId": "[parent-id]", + "status": Object { + "code": 0, + }, + }, ] `) }) From 41d60bb322c4a70e330325ac8979f9e24eeb5c2c Mon Sep 17 00:00:00 2001 From: Jan Kaifer Date: Tue, 21 Mar 2023 19:23:14 +0100 Subject: [PATCH 12/29] remove denpendency on `path` --- .../future/route-handlers/app-route-route-handler.ts | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/packages/next/src/server/future/route-handlers/app-route-route-handler.ts b/packages/next/src/server/future/route-handlers/app-route-route-handler.ts index c849ec94f05a0..710d82c679207 100644 --- a/packages/next/src/server/future/route-handlers/app-route-route-handler.ts +++ b/packages/next/src/server/future/route-handlers/app-route-route-handler.ts @@ -40,7 +40,6 @@ import { NextURL } from '../../web/next-url' import { NextConfig } from '../../config-shared' import { getTracer } from '../../lib/trace/tracer' import { AppRouteRouteHandlersSpan } from '../../lib/trace/constants' -import path from 'path' import { AppRouteRouteDefinition } from '../route-definitions/app-route-route-definition' import { WebNextRequest } from '../../base-http/web' @@ -301,9 +300,12 @@ function proxyRequest( function getPathnameFromAbsolutePath(absolutePath: string) { // Remove prefix including app dir - const appDir = path.sep + 'app' + path.sep + let appDir = '/app/' + if (!absolutePath.includes(appDir)) { + appDir = '\\app\\' + } const [, ...parts] = absolutePath.split(appDir) - const relativePath = path.sep + parts.join(appDir) + const relativePath = appDir[0] + parts.join(appDir) // remove extension const pathname = relativePath.split('.').slice(0, -1).join('.') From 467600c19e7ae087f4f46dbad7f68f19564dfba3 Mon Sep 17 00:00:00 2001 From: Jan Kaifer Date: Tue, 21 Mar 2023 19:36:18 +0100 Subject: [PATCH 13/29] remove findPageComponent span --- packages/next/src/server/app-render/index.tsx | 1 - .../next/src/server/lib/trace/constants.ts | 1 - test/e2e/opentelemetry/opentelemetry.test.ts | 295 +++++++----------- 3 files changed, 109 insertions(+), 188 deletions(-) diff --git a/packages/next/src/server/app-render/index.tsx b/packages/next/src/server/app-render/index.tsx index e9f4206a2ef4c..bb6b784253731 100644 --- a/packages/next/src/server/app-render/index.tsx +++ b/packages/next/src/server/app-render/index.tsx @@ -1159,7 +1159,6 @@ export async function renderToHTMLOrFlight( { spanName: `render page (app) ${pathname}`, }, - async ({ asNotFound, }: { diff --git a/packages/next/src/server/lib/trace/constants.ts b/packages/next/src/server/lib/trace/constants.ts index cc4fb49ee377c..255baecd035b1 100644 --- a/packages/next/src/server/lib/trace/constants.ts +++ b/packages/next/src/server/lib/trace/constants.ts @@ -116,7 +116,6 @@ type SpanTypes = // This list is used to filter out spans that are not relevant to the user export const NextVanillaSpanAllowlist = [ BaseServerSpan.handleRequest, - NextNodeServerSpan.findPageComponents, RenderSpan.getServerSideProps, AppRenderSpan.fetch, AppRenderSpan.getBodyResult, diff --git a/test/e2e/opentelemetry/opentelemetry.test.ts b/test/e2e/opentelemetry/opentelemetry.test.ts index 705372cac9ce9..2d3328f35c267 100644 --- a/test/e2e/opentelemetry/opentelemetry.test.ts +++ b/test/e2e/opentelemetry/opentelemetry.test.ts @@ -128,19 +128,6 @@ createNextDescribe( "code": 0, }, }, - Object { - "attributes": Object { - "next.route": "/app/rsc-fetch/page", - "next.span_name": "resolving page into components", - "next.span_type": "NextNodeServer.findPageComponents", - }, - "kind": 0, - "name": "resolving page into components", - "parentId": "[parent-id]", - "status": Object { - "code": 0, - }, - }, Object { "attributes": Object { "next.span_name": "generateMetadata", @@ -189,6 +176,7 @@ createNextDescribe( Object { "attributes": Object { "http.method": "GET", + "http.status_code": 200, "http.target": "/api/app/data", "next.span_name": "GET /api/app/data", "next.span_type": "BaseServer.handleRequest", @@ -200,19 +188,6 @@ createNextDescribe( "code": 0, }, }, - Object { - "attributes": Object { - "next.route": "/api/app/data/route", - "next.span_name": "resolving page into components", - "next.span_type": "NextNodeServer.findPageComponents", - }, - "kind": 0, - "name": "resolving page into components", - "parentId": "[parent-id]", - "status": Object { - "code": 0, - }, - }, ] `) }) @@ -223,184 +198,132 @@ createNextDescribe( await next.fetch('/pages') expect(await getSanitizedTraces(1)).toMatchInlineSnapshot(` - Array [ - Object { - "attributes": Object { - "http.method": "GET", - "http.status_code": 200, - "http.target": "/pages", - "next.span_name": "GET /pages", - "next.span_type": "BaseServer.handleRequest", - }, - "kind": 1, - "name": "GET /pages", - "parentId": undefined, - "status": Object { - "code": 0, - }, - }, - Object { - "attributes": Object { - "next.route": "/pages", - "next.span_name": "resolving page into components", - "next.span_type": "NextNodeServer.findPageComponents", - }, - "kind": 0, - "name": "resolving page into components", - "parentId": "[parent-id]", - "status": Object { - "code": 0, - }, - }, - ] - `) + Array [ + Object { + "attributes": Object { + "http.method": "GET", + "http.status_code": 200, + "http.target": "/pages", + "next.span_name": "GET /pages", + "next.span_type": "BaseServer.handleRequest", + }, + "kind": 1, + "name": "GET /pages", + "parentId": undefined, + "status": Object { + "code": 0, + }, + }, + ] + `) }) it('should handle route params', async () => { await next.fetch('/pages/params/stuff') expect(await getSanitizedTraces(1)).toMatchInlineSnapshot(` - Array [ - Object { - "attributes": Object { - "http.method": "GET", - "http.status_code": 200, - "http.target": "/pages/params/stuff", - "next.span_name": "GET /pages/params/stuff", - "next.span_type": "BaseServer.handleRequest", - }, - "kind": 1, - "name": "GET /pages/params/stuff", - "parentId": undefined, - "status": Object { - "code": 0, - }, - }, - Object { - "attributes": Object { - "next.route": "/pages/params/[param]", - "next.span_name": "resolving page into components", - "next.span_type": "NextNodeServer.findPageComponents", - }, - "kind": 0, - "name": "resolving page into components", - "parentId": "[parent-id]", - "status": Object { - "code": 0, - }, - }, - ] - `) + Array [ + Object { + "attributes": Object { + "http.method": "GET", + "http.status_code": 200, + "http.target": "/pages/params/stuff", + "next.span_name": "GET /pages/params/stuff", + "next.span_type": "BaseServer.handleRequest", + }, + "kind": 1, + "name": "GET /pages/params/stuff", + "parentId": undefined, + "status": Object { + "code": 0, + }, + }, + ] + `) }) it('should handle getServerSideProps', async () => { await next.fetch('/pages/getServerSideProps') expect(await getSanitizedTraces(1)).toMatchInlineSnapshot(` - Array [ - Object { - "attributes": Object { - "http.method": "GET", - "http.status_code": 200, - "http.target": "/pages/getServerSideProps", - "next.span_name": "GET /pages/getServerSideProps", - "next.span_type": "BaseServer.handleRequest", - }, - "kind": 1, - "name": "GET /pages/getServerSideProps", - "parentId": undefined, - "status": Object { - "code": 0, - }, - }, - Object { - "attributes": Object { - "next.route": "/pages/getServerSideProps", - "next.span_name": "resolving page into components", - "next.span_type": "NextNodeServer.findPageComponents", - }, - "kind": 0, - "name": "resolving page into components", - "parentId": "[parent-id]", - "status": Object { - "code": 0, - }, - }, - Object { - "attributes": Object { - "next.span_name": "getServerSideProps /pages/getServerSideProps", - "next.span_type": "Render.getServerSideProps", - }, - "kind": 0, - "name": "getServerSideProps /pages/getServerSideProps", - "parentId": "[parent-id]", - "status": Object { - "code": 0, - }, - }, - Object { - "attributes": Object { - "next.span_name": "render page (pages) /pages/getServerSideProps", - "next.span_type": "Render.renderDocument", - }, - "kind": 0, - "name": "render page (pages) /pages/getServerSideProps", - "parentId": "[parent-id]", - "status": Object { - "code": 0, - }, - }, - ] - `) + Array [ + Object { + "attributes": Object { + "http.method": "GET", + "http.status_code": 200, + "http.target": "/pages/getServerSideProps", + "next.span_name": "GET /pages/getServerSideProps", + "next.span_type": "BaseServer.handleRequest", + }, + "kind": 1, + "name": "GET /pages/getServerSideProps", + "parentId": undefined, + "status": Object { + "code": 0, + }, + }, + Object { + "attributes": Object { + "next.span_name": "getServerSideProps /pages/getServerSideProps", + "next.span_type": "Render.getServerSideProps", + }, + "kind": 0, + "name": "getServerSideProps /pages/getServerSideProps", + "parentId": "[parent-id]", + "status": Object { + "code": 0, + }, + }, + Object { + "attributes": Object { + "next.span_name": "render page (pages) /pages/getServerSideProps", + "next.span_type": "Render.renderDocument", + }, + "kind": 0, + "name": "render page (pages) /pages/getServerSideProps", + "parentId": "[parent-id]", + "status": Object { + "code": 0, + }, + }, + ] + `) }) it("should handle getStaticProps when fallback: 'blocking'", async () => { await next.fetch('/pages/static/param/getStaticProps') expect(await getSanitizedTraces(1)).toMatchInlineSnapshot(` - Array [ - Object { - "attributes": Object { - "http.method": "GET", - "http.status_code": 200, - "http.target": "/pages/static/param/getStaticProps", - "next.span_name": "GET /pages/static/param/getStaticProps", - "next.span_type": "BaseServer.handleRequest", - }, - "kind": 1, - "name": "GET /pages/static/param/getStaticProps", - "parentId": undefined, - "status": Object { - "code": 0, - }, - }, - Object { - "attributes": Object { - "next.route": "/pages/static/[param]/getStaticProps", - "next.span_name": "resolving page into components", - "next.span_type": "NextNodeServer.findPageComponents", - }, - "kind": 0, - "name": "resolving page into components", - "parentId": "[parent-id]", - "status": Object { - "code": 0, - }, - }, - Object { - "attributes": Object { - "next.span_name": "render page (pages) /pages/static/[param]/getStaticProps", - "next.span_type": "Render.renderDocument", - }, - "kind": 0, - "name": "render page (pages) /pages/static/[param]/getStaticProps", - "parentId": "[parent-id]", - "status": Object { - "code": 0, - }, - }, - ] - `) + Array [ + Object { + "attributes": Object { + "http.method": "GET", + "http.status_code": 200, + "http.target": "/pages/static/param/getStaticProps", + "next.span_name": "GET /pages/static/param/getStaticProps", + "next.span_type": "BaseServer.handleRequest", + }, + "kind": 1, + "name": "GET /pages/static/param/getStaticProps", + "parentId": undefined, + "status": Object { + "code": 0, + }, + }, + Object { + "attributes": Object { + "next.span_name": "render page (pages) /pages/static/[param]/getStaticProps", + "next.span_type": "Render.renderDocument", + }, + "kind": 0, + "name": "render page (pages) /pages/static/[param]/getStaticProps", + "parentId": "[parent-id]", + "status": Object { + "code": 0, + }, + }, + ] + `) }) it('should handle api routes in pages', async () => { From 10e35db5e84ce900c03363389419dc775ca6765c Mon Sep 17 00:00:00 2001 From: Jan Kaifer Date: Tue, 21 Mar 2023 20:31:24 +0100 Subject: [PATCH 14/29] Added a proper name for generateMetadata --- .../next/src/lib/metadata/resolve-metadata.ts | 30 +++++++--- ...te-flight-router-state-from-loader-tree.ts | 4 +- packages/next/src/server/app-render/index.tsx | 57 +++++++++++++------ .../next/src/server/lib/app-dir-module.ts | 14 ++++- packages/next/src/shared/lib/constants.ts | 1 + test/e2e/opentelemetry/opentelemetry.test.ts | 8 +-- 6 files changed, 81 insertions(+), 33 deletions(-) diff --git a/packages/next/src/lib/metadata/resolve-metadata.ts b/packages/next/src/lib/metadata/resolve-metadata.ts index 767f320a4520f..dee00574a0fa8 100644 --- a/packages/next/src/lib/metadata/resolve-metadata.ts +++ b/packages/next/src/lib/metadata/resolve-metadata.ts @@ -183,7 +183,8 @@ function merge( async function getDefinedMetadata( mod: any, - props: any + props: any, + pathname: string ): Promise { // Layer is a client component, we just skip it. It can't have metadata exported. // Return early to avoid accessing properties error for client references. @@ -196,7 +197,7 @@ async function getDefinedMetadata( getTracer().trace( ResolveMetadataSpan.generateMetadata, { - spanName: `generateMetadata`, + spanName: `generateMetadata ${pathname}`, }, () => mod.generateMetadata(props, parent) ) @@ -240,14 +241,27 @@ async function resolveStaticMetadata(components: ComponentsType) { } // [layout.metadata, static files metadata] -> ... -> [page.metadata, static files metadata] -export async function collectMetadata( - loaderTree: LoaderTree, - props: any, +export async function collectMetadata({ + loaderTree, + props, + array, + pathname, +}: { + loaderTree: LoaderTree + props: any array: MetadataItems -) { - const mod = await getLayoutOrPageModule(loaderTree) + pathname: string +}) { + const [mod, modType] = await getLayoutOrPageModule(loaderTree) + + if (modType) { + pathname += `/${modType}` + } + const staticFilesMetadata = await resolveStaticMetadata(loaderTree[2]) - const metadataExport = mod ? await getDefinedMetadata(mod, props) : null + const metadataExport = mod + ? await getDefinedMetadata(mod, props, pathname) + : null array.push([metadataExport, staticFilesMetadata]) } diff --git a/packages/next/src/server/app-render/create-flight-router-state-from-loader-tree.ts b/packages/next/src/server/app-render/create-flight-router-state-from-loader-tree.ts index 46afe6473fe85..959916ac488d1 100644 --- a/packages/next/src/server/app-render/create-flight-router-state-from-loader-tree.ts +++ b/packages/next/src/server/app-render/create-flight-router-state-from-loader-tree.ts @@ -1,9 +1,7 @@ import { LoaderTree } from '../lib/app-dir-module' import { FlightRouterState, Segment } from './types' import { GetDynamicParamFromSegment } from './index' - -// TODO-APP: Move __PAGE__ to a shared constant -const PAGE_SEGMENT_KEY = '__PAGE__' +import { PAGE_SEGMENT_KEY } from '../../shared/lib/constants' export function addSearchParamsIfPageSegment( segment: Segment, diff --git a/packages/next/src/server/app-render/index.tsx b/packages/next/src/server/app-render/index.tsx index bb6b784253731..70cc3dc8f36fe 100644 --- a/packages/next/src/server/app-render/index.tsx +++ b/packages/next/src/server/app-render/index.tsx @@ -72,6 +72,7 @@ import { addSearchParamsIfPageSegment, createFlightRouterStateFromLoaderTree, } from './create-flight-router-state-from-loader-tree' +import { PAGE_SEGMENT_KEY } from '../../shared/lib/constants' export const isEdgeRuntime = process.env.NEXT_RUNTIME === 'edge' @@ -279,12 +280,21 @@ export async function renderToHTMLOrFlight( } } - async function resolveHead( - tree: LoaderTree, - parentParams: { [key: string]: any }, + async function resolveHead({ + tree, + parentParams, + metadataItems, + treePrefix = [], + }: { + tree: LoaderTree + parentParams: { [key: string]: any } metadataItems: MetadataItems - ): Promise<[React.ReactNode, MetadataItems]> { + /** Provided tree can be nested subtree, this argument says what is the path of such subtree */ + treePrefix?: string[] + }): Promise<[React.ReactNode, MetadataItems]> { const [segment, parallelRoutes, { head, page }] = tree + const currentTreePrefix = [...treePrefix, segment] + console.log(currentTreePrefix, tree) const isPage = typeof page !== 'undefined' // Handle dynamic segment params. const segmentParam = getDynamicParamFromSegment(segment) @@ -306,15 +316,24 @@ export async function renderToHTMLOrFlight( ...(isPage && searchParamsProps), } - await collectMetadata(tree, layerProps, metadataItems) + await collectMetadata({ + loaderTree: tree, + props: layerProps, + array: metadataItems, + pathname: currentTreePrefix + // __PAGE__ shouldn't be shown in a pathname + .filter((s) => s !== PAGE_SEGMENT_KEY) + .join('/'), + }) for (const key in parallelRoutes) { const childTree = parallelRoutes[key] - const [returnedHead] = await resolveHead( - childTree, - currentParams, - metadataItems - ) + const [returnedHead] = await resolveHead({ + tree: childTree, + parentParams: currentParams, + metadataItems, + treePrefix: currentTreePrefix, + }) if (returnedHead) { return [returnedHead, metadataItems] } @@ -473,7 +492,7 @@ export async function renderToHTMLOrFlight( const isLayout = typeof layout !== 'undefined' const isPage = typeof page !== 'undefined' - const layoutOrPageMod = await getLayoutOrPageModule(tree) + const [layoutOrPageMod] = await getLayoutOrPageModule(tree) /** * Checks if the current segment is a root layout. @@ -979,11 +998,11 @@ export async function renderToHTMLOrFlight( return [actualSegment] } - const [resolvedHead, metadataItems] = await resolveHead( - loaderTree, - {}, - [] - ) + const [resolvedHead, metadataItems] = await resolveHead({ + tree: loaderTree, + parentParams: {}, + metadataItems: [], + }) // Flight data that is going to be passed to the browser. // Currently a single item array but in the future multiple patches might be combined in a single request. const flightData: FlightData = [ @@ -1074,7 +1093,11 @@ export async function renderToHTMLOrFlight( } : {} - const [initialHead, metadataItems] = await resolveHead(loaderTree, {}, []) + const [initialHead, metadataItems] = await resolveHead({ + tree: loaderTree, + parentParams: {}, + metadataItems: [], + }) /** * A new React Component that renders the provided React Component diff --git a/packages/next/src/server/lib/app-dir-module.ts b/packages/next/src/server/lib/app-dir-module.ts index 9694fb7d4fd40..77a35c215f25d 100644 --- a/packages/next/src/server/lib/app-dir-module.ts +++ b/packages/next/src/server/lib/app-dir-module.ts @@ -13,5 +13,17 @@ export async function getLayoutOrPageModule(loaderTree: LoaderTree) { const { layout, page } = loaderTree[2] const isLayout = typeof layout !== 'undefined' const isPage = typeof page !== 'undefined' - return isLayout ? await layout[0]() : isPage ? await page[0]() : undefined + + let value = undefined + let modType: 'layout' | 'page' | undefined = undefined + if (isLayout) { + value = await layout[0]() + modType = 'layout' + } + if (isPage) { + value = await page[0]() + modType = 'page' + } + + return [value, modType] as const } diff --git a/packages/next/src/shared/lib/constants.ts b/packages/next/src/shared/lib/constants.ts index 8dd9117317516..ec306eb4ebafe 100644 --- a/packages/next/src/shared/lib/constants.ts +++ b/packages/next/src/shared/lib/constants.ts @@ -96,6 +96,7 @@ export const TEMPORARY_REDIRECT_STATUS = 307 export const PERMANENT_REDIRECT_STATUS = 308 export const STATIC_PROPS_ID = '__N_SSG' export const SERVER_PROPS_ID = '__N_SSP' +export const PAGE_SEGMENT_KEY = '__PAGE__' export const GOOGLE_FONT_PROVIDER = 'https://fonts.googleapis.com/' export const OPTIMIZED_FONT_PROVIDERS = [ { url: GOOGLE_FONT_PROVIDER, preconnect: 'https://fonts.gstatic.com' }, diff --git a/test/e2e/opentelemetry/opentelemetry.test.ts b/test/e2e/opentelemetry/opentelemetry.test.ts index 2d3328f35c267..306d4bd1fabdc 100644 --- a/test/e2e/opentelemetry/opentelemetry.test.ts +++ b/test/e2e/opentelemetry/opentelemetry.test.ts @@ -130,11 +130,11 @@ createNextDescribe( }, Object { "attributes": Object { - "next.span_name": "generateMetadata", + "next.span_name": "generateMetadata /app/layout", "next.span_type": "ResolveMetadata.generateMetadata", }, "kind": 0, - "name": "generateMetadata", + "name": "generateMetadata /app/layout", "parentId": "[parent-id]", "status": Object { "code": 0, @@ -142,11 +142,11 @@ createNextDescribe( }, Object { "attributes": Object { - "next.span_name": "generateMetadata", + "next.span_name": "generateMetadata /app/rsc-fetch/page", "next.span_type": "ResolveMetadata.generateMetadata", }, "kind": 0, - "name": "generateMetadata", + "name": "generateMetadata /app/rsc-fetch/page", "parentId": "[parent-id]", "status": Object { "code": 0, From 6d9bb29d3260fa922b7789f828335c9cd7f43d3c Mon Sep 17 00:00:00 2001 From: Jan Kaifer Date: Tue, 21 Mar 2023 21:16:54 +0100 Subject: [PATCH 15/29] rename pathname to route --- .../next/src/lib/metadata/resolve-metadata.ts | 15 +++++++++------ packages/next/src/server/app-render/index.tsx | 4 ++-- 2 files changed, 11 insertions(+), 8 deletions(-) diff --git a/packages/next/src/lib/metadata/resolve-metadata.ts b/packages/next/src/lib/metadata/resolve-metadata.ts index dee00574a0fa8..d369bf6fc82f1 100644 --- a/packages/next/src/lib/metadata/resolve-metadata.ts +++ b/packages/next/src/lib/metadata/resolve-metadata.ts @@ -184,7 +184,7 @@ function merge( async function getDefinedMetadata( mod: any, props: any, - pathname: string + route: string ): Promise { // Layer is a client component, we just skip it. It can't have metadata exported. // Return early to avoid accessing properties error for client references. @@ -197,7 +197,10 @@ async function getDefinedMetadata( getTracer().trace( ResolveMetadataSpan.generateMetadata, { - spanName: `generateMetadata ${pathname}`, + spanName: `generateMetadata ${route}`, + attributes: { + 'next.route': route, + }, }, () => mod.generateMetadata(props, parent) ) @@ -245,22 +248,22 @@ export async function collectMetadata({ loaderTree, props, array, - pathname, + route, }: { loaderTree: LoaderTree props: any array: MetadataItems - pathname: string + route: string }) { const [mod, modType] = await getLayoutOrPageModule(loaderTree) if (modType) { - pathname += `/${modType}` + route += `/${modType}` } const staticFilesMetadata = await resolveStaticMetadata(loaderTree[2]) const metadataExport = mod - ? await getDefinedMetadata(mod, props, pathname) + ? await getDefinedMetadata(mod, props, route) : null array.push([metadataExport, staticFilesMetadata]) diff --git a/packages/next/src/server/app-render/index.tsx b/packages/next/src/server/app-render/index.tsx index 70cc3dc8f36fe..18d7ab880de08 100644 --- a/packages/next/src/server/app-render/index.tsx +++ b/packages/next/src/server/app-render/index.tsx @@ -320,8 +320,8 @@ export async function renderToHTMLOrFlight( loaderTree: tree, props: layerProps, array: metadataItems, - pathname: currentTreePrefix - // __PAGE__ shouldn't be shown in a pathname + route: currentTreePrefix + // __PAGE__ shouldn't be shown in a route .filter((s) => s !== PAGE_SEGMENT_KEY) .join('/'), }) From 4004295e0c1e84eceb4ad8c51f1dad6894f5cb63 Mon Sep 17 00:00:00 2001 From: Jan Kaifer Date: Tue, 21 Mar 2023 21:22:04 +0100 Subject: [PATCH 16/29] regenerate snapshots --- test/e2e/opentelemetry/opentelemetry.test.ts | 2 ++ 1 file changed, 2 insertions(+) diff --git a/test/e2e/opentelemetry/opentelemetry.test.ts b/test/e2e/opentelemetry/opentelemetry.test.ts index 306d4bd1fabdc..011d9642c9852 100644 --- a/test/e2e/opentelemetry/opentelemetry.test.ts +++ b/test/e2e/opentelemetry/opentelemetry.test.ts @@ -130,6 +130,7 @@ createNextDescribe( }, Object { "attributes": Object { + "next.route": "/app/layout", "next.span_name": "generateMetadata /app/layout", "next.span_type": "ResolveMetadata.generateMetadata", }, @@ -142,6 +143,7 @@ createNextDescribe( }, Object { "attributes": Object { + "next.route": "/app/rsc-fetch/page", "next.span_name": "generateMetadata /app/rsc-fetch/page", "next.span_type": "ResolveMetadata.generateMetadata", }, From 377f7e4e750e135a2017b35c43554720bb4d26c0 Mon Sep 17 00:00:00 2001 From: Jan Kaifer Date: Tue, 21 Mar 2023 21:29:40 +0100 Subject: [PATCH 17/29] remove debug log --- packages/next/src/server/app-render/index.tsx | 1 - 1 file changed, 1 deletion(-) diff --git a/packages/next/src/server/app-render/index.tsx b/packages/next/src/server/app-render/index.tsx index 18d7ab880de08..fb0f6fe4529f5 100644 --- a/packages/next/src/server/app-render/index.tsx +++ b/packages/next/src/server/app-render/index.tsx @@ -294,7 +294,6 @@ export async function renderToHTMLOrFlight( }): Promise<[React.ReactNode, MetadataItems]> { const [segment, parallelRoutes, { head, page }] = tree const currentTreePrefix = [...treePrefix, segment] - console.log(currentTreePrefix, tree) const isPage = typeof page !== 'undefined' // Handle dynamic segment params. const segmentParam = getDynamicParamFromSegment(segment) From e38fa6947b02412919d9142c17784a8cc6eaaad7 Mon Sep 17 00:00:00 2001 From: Jan Kaifer Date: Tue, 21 Mar 2023 22:06:03 +0100 Subject: [PATCH 18/29] move test paths behind param to distinguish pathname from routes --- .../app/api/app/{ => [param]}/data/route.ts | 0 .../app/app/{ => [param]}/layout.tsx | 0 .../app/app/{ => [param]}/loading/loading.tsx | 0 .../app/{ => [param]}/loading/page1/page.tsx | 0 .../app/{ => [param]}/loading/page2/page.tsx | 0 .../app/app/{ => [param]}/rsc-fetch/page.tsx | 0 test/e2e/opentelemetry/opentelemetry.test.ts | 124 ++++++------------ .../pages/api/pages/{ => [param]}/basic.ts | 0 .../{ => [param]}/getServerSideProps.tsx | 0 .../{static => }/[param]/getStaticProps.tsx | 2 +- test/e2e/opentelemetry/pages/pages/index.tsx | 3 - .../pages/pages/params/[param].tsx | 3 - 12 files changed, 39 insertions(+), 93 deletions(-) rename test/e2e/opentelemetry/app/api/app/{ => [param]}/data/route.ts (100%) rename test/e2e/opentelemetry/app/app/{ => [param]}/layout.tsx (100%) rename test/e2e/opentelemetry/app/app/{ => [param]}/loading/loading.tsx (100%) rename test/e2e/opentelemetry/app/app/{ => [param]}/loading/page1/page.tsx (100%) rename test/e2e/opentelemetry/app/app/{ => [param]}/loading/page2/page.tsx (100%) rename test/e2e/opentelemetry/app/app/{ => [param]}/rsc-fetch/page.tsx (100%) rename test/e2e/opentelemetry/pages/api/pages/{ => [param]}/basic.ts (100%) rename test/e2e/opentelemetry/pages/pages/{ => [param]}/getServerSideProps.tsx (100%) rename test/e2e/opentelemetry/pages/pages/{static => }/[param]/getStaticProps.tsx (67%) delete mode 100644 test/e2e/opentelemetry/pages/pages/index.tsx delete mode 100644 test/e2e/opentelemetry/pages/pages/params/[param].tsx diff --git a/test/e2e/opentelemetry/app/api/app/data/route.ts b/test/e2e/opentelemetry/app/api/app/[param]/data/route.ts similarity index 100% rename from test/e2e/opentelemetry/app/api/app/data/route.ts rename to test/e2e/opentelemetry/app/api/app/[param]/data/route.ts diff --git a/test/e2e/opentelemetry/app/app/layout.tsx b/test/e2e/opentelemetry/app/app/[param]/layout.tsx similarity index 100% rename from test/e2e/opentelemetry/app/app/layout.tsx rename to test/e2e/opentelemetry/app/app/[param]/layout.tsx diff --git a/test/e2e/opentelemetry/app/app/loading/loading.tsx b/test/e2e/opentelemetry/app/app/[param]/loading/loading.tsx similarity index 100% rename from test/e2e/opentelemetry/app/app/loading/loading.tsx rename to test/e2e/opentelemetry/app/app/[param]/loading/loading.tsx diff --git a/test/e2e/opentelemetry/app/app/loading/page1/page.tsx b/test/e2e/opentelemetry/app/app/[param]/loading/page1/page.tsx similarity index 100% rename from test/e2e/opentelemetry/app/app/loading/page1/page.tsx rename to test/e2e/opentelemetry/app/app/[param]/loading/page1/page.tsx diff --git a/test/e2e/opentelemetry/app/app/loading/page2/page.tsx b/test/e2e/opentelemetry/app/app/[param]/loading/page2/page.tsx similarity index 100% rename from test/e2e/opentelemetry/app/app/loading/page2/page.tsx rename to test/e2e/opentelemetry/app/app/[param]/loading/page2/page.tsx diff --git a/test/e2e/opentelemetry/app/app/rsc-fetch/page.tsx b/test/e2e/opentelemetry/app/app/[param]/rsc-fetch/page.tsx similarity index 100% rename from test/e2e/opentelemetry/app/app/rsc-fetch/page.tsx rename to test/e2e/opentelemetry/app/app/[param]/rsc-fetch/page.tsx diff --git a/test/e2e/opentelemetry/opentelemetry.test.ts b/test/e2e/opentelemetry/opentelemetry.test.ts index 011d9642c9852..ea03e68bec762 100644 --- a/test/e2e/opentelemetry/opentelemetry.test.ts +++ b/test/e2e/opentelemetry/opentelemetry.test.ts @@ -82,7 +82,7 @@ createNextDescribe( describe('app router', () => { it('should handle RSC with fetch', async () => { - await next.fetch('/app/rsc-fetch') + await next.fetch('/app/param/rsc-fetch') expect(await getSanitizedTraces(1)).toMatchInlineSnapshot(` Array [ @@ -103,11 +103,11 @@ createNextDescribe( }, Object { "attributes": Object { - "next.span_name": "render page (app) /app/rsc-fetch", + "next.span_name": "render page (app) /app/[param]/rsc-fetch", "next.span_type": "AppRender.getBodyResult", }, "kind": 0, - "name": "render page (app) /app/rsc-fetch", + "name": "render page (app) /app/[param]/rsc-fetch", "parentId": "[parent-id]", "status": Object { "code": 0, @@ -117,12 +117,12 @@ createNextDescribe( "attributes": Object { "http.method": "GET", "http.status_code": 200, - "http.target": "/app/rsc-fetch", - "next.span_name": "GET /app/rsc-fetch", + "http.target": "/app/param/rsc-fetch", + "next.span_name": "GET /app/param/rsc-fetch", "next.span_type": "BaseServer.handleRequest", }, "kind": 1, - "name": "GET /app/rsc-fetch", + "name": "GET /app/param/rsc-fetch", "parentId": undefined, "status": Object { "code": 0, @@ -130,12 +130,12 @@ createNextDescribe( }, Object { "attributes": Object { - "next.route": "/app/layout", - "next.span_name": "generateMetadata /app/layout", + "next.route": "/app/[param]/layout", + "next.span_name": "generateMetadata /app/[param]/layout", "next.span_type": "ResolveMetadata.generateMetadata", }, "kind": 0, - "name": "generateMetadata /app/layout", + "name": "generateMetadata /app/[param]/layout", "parentId": "[parent-id]", "status": Object { "code": 0, @@ -143,12 +143,12 @@ createNextDescribe( }, Object { "attributes": Object { - "next.route": "/app/rsc-fetch/page", - "next.span_name": "generateMetadata /app/rsc-fetch/page", + "next.route": "/app/[param]/rsc-fetch/page", + "next.span_name": "generateMetadata /app/[param]/rsc-fetch/page", "next.span_type": "ResolveMetadata.generateMetadata", }, "kind": 0, - "name": "generateMetadata /app/rsc-fetch/page", + "name": "generateMetadata /app/[param]/rsc-fetch/page", "parentId": "[parent-id]", "status": Object { "code": 0, @@ -159,17 +159,17 @@ createNextDescribe( }) it('should handle route handlers in app router', async () => { - await next.fetch('/api/app/data') + await next.fetch('/api/app/param/data') expect(await getSanitizedTraces(1)).toMatchInlineSnapshot(` Array [ Object { "attributes": Object { - "next.span_name": "executing api route (app) /api/app/data/route", + "next.span_name": "executing api route (app) /api/app/[param]/data/route", "next.span_type": "AppRouteRouteHandlers.runHandler", }, "kind": 0, - "name": "executing api route (app) /api/app/data/route", + "name": "executing api route (app) /api/app/[param]/data/route", "parentId": "[parent-id]", "status": Object { "code": 0, @@ -179,12 +179,12 @@ createNextDescribe( "attributes": Object { "http.method": "GET", "http.status_code": 200, - "http.target": "/api/app/data", - "next.span_name": "GET /api/app/data", + "http.target": "/api/app/param/data", + "next.span_name": "GET /api/app/param/data", "next.span_type": "BaseServer.handleRequest", }, "kind": 1, - "name": "GET /api/app/data", + "name": "GET /api/app/param/data", "parentId": undefined, "status": Object { "code": 0, @@ -196,56 +196,8 @@ createNextDescribe( }) describe('pages', () => { - it('should have spans when accessing page', async () => { - await next.fetch('/pages') - - expect(await getSanitizedTraces(1)).toMatchInlineSnapshot(` - Array [ - Object { - "attributes": Object { - "http.method": "GET", - "http.status_code": 200, - "http.target": "/pages", - "next.span_name": "GET /pages", - "next.span_type": "BaseServer.handleRequest", - }, - "kind": 1, - "name": "GET /pages", - "parentId": undefined, - "status": Object { - "code": 0, - }, - }, - ] - `) - }) - - it('should handle route params', async () => { - await next.fetch('/pages/params/stuff') - - expect(await getSanitizedTraces(1)).toMatchInlineSnapshot(` - Array [ - Object { - "attributes": Object { - "http.method": "GET", - "http.status_code": 200, - "http.target": "/pages/params/stuff", - "next.span_name": "GET /pages/params/stuff", - "next.span_type": "BaseServer.handleRequest", - }, - "kind": 1, - "name": "GET /pages/params/stuff", - "parentId": undefined, - "status": Object { - "code": 0, - }, - }, - ] - `) - }) - it('should handle getServerSideProps', async () => { - await next.fetch('/pages/getServerSideProps') + await next.fetch('/pages/param/getServerSideProps') expect(await getSanitizedTraces(1)).toMatchInlineSnapshot(` Array [ @@ -253,12 +205,12 @@ createNextDescribe( "attributes": Object { "http.method": "GET", "http.status_code": 200, - "http.target": "/pages/getServerSideProps", - "next.span_name": "GET /pages/getServerSideProps", + "http.target": "/pages/param/getServerSideProps", + "next.span_name": "GET /pages/param/getServerSideProps", "next.span_type": "BaseServer.handleRequest", }, "kind": 1, - "name": "GET /pages/getServerSideProps", + "name": "GET /pages/param/getServerSideProps", "parentId": undefined, "status": Object { "code": 0, @@ -266,11 +218,11 @@ createNextDescribe( }, Object { "attributes": Object { - "next.span_name": "getServerSideProps /pages/getServerSideProps", + "next.span_name": "getServerSideProps /pages/[param]/getServerSideProps", "next.span_type": "Render.getServerSideProps", }, "kind": 0, - "name": "getServerSideProps /pages/getServerSideProps", + "name": "getServerSideProps /pages/[param]/getServerSideProps", "parentId": "[parent-id]", "status": Object { "code": 0, @@ -278,11 +230,11 @@ createNextDescribe( }, Object { "attributes": Object { - "next.span_name": "render page (pages) /pages/getServerSideProps", + "next.span_name": "render page (pages) /pages/[param]/getServerSideProps", "next.span_type": "Render.renderDocument", }, "kind": 0, - "name": "render page (pages) /pages/getServerSideProps", + "name": "render page (pages) /pages/[param]/getServerSideProps", "parentId": "[parent-id]", "status": Object { "code": 0, @@ -293,7 +245,7 @@ createNextDescribe( }) it("should handle getStaticProps when fallback: 'blocking'", async () => { - await next.fetch('/pages/static/param/getStaticProps') + await next.fetch('/pages/param/getStaticProps') expect(await getSanitizedTraces(1)).toMatchInlineSnapshot(` Array [ @@ -301,12 +253,12 @@ createNextDescribe( "attributes": Object { "http.method": "GET", "http.status_code": 200, - "http.target": "/pages/static/param/getStaticProps", - "next.span_name": "GET /pages/static/param/getStaticProps", + "http.target": "/pages/param/getStaticProps", + "next.span_name": "GET /pages/param/getStaticProps", "next.span_type": "BaseServer.handleRequest", }, "kind": 1, - "name": "GET /pages/static/param/getStaticProps", + "name": "GET /pages/param/getStaticProps", "parentId": undefined, "status": Object { "code": 0, @@ -314,11 +266,11 @@ createNextDescribe( }, Object { "attributes": Object { - "next.span_name": "render page (pages) /pages/static/[param]/getStaticProps", + "next.span_name": "render page (pages) /pages/[param]/getStaticProps", "next.span_type": "Render.renderDocument", }, "kind": 0, - "name": "render page (pages) /pages/static/[param]/getStaticProps", + "name": "render page (pages) /pages/[param]/getStaticProps", "parentId": "[parent-id]", "status": Object { "code": 0, @@ -329,7 +281,7 @@ createNextDescribe( }) it('should handle api routes in pages', async () => { - await next.fetch('/api/pages/basic') + await next.fetch('/api/pages/param/basic') expect(await getSanitizedTraces(1)).toMatchInlineSnapshot(` Array [ @@ -337,12 +289,12 @@ createNextDescribe( "attributes": Object { "http.method": "GET", "http.status_code": 200, - "http.target": "/api/pages/basic", - "next.span_name": "GET /api/pages/basic", + "http.target": "/api/pages/param/basic", + "next.span_name": "GET /api/pages/param/basic", "next.span_type": "BaseServer.handleRequest", }, "kind": 1, - "name": "GET /api/pages/basic", + "name": "GET /api/pages/param/basic", "parentId": undefined, "status": Object { "code": 0, @@ -350,11 +302,11 @@ createNextDescribe( }, Object { "attributes": Object { - "next.span_name": "executing api route (pages) /api/pages/basic", + "next.span_name": "executing api route (pages) /api/pages/[param]/basic", "next.span_type": "Node.runHandler", }, "kind": 0, - "name": "executing api route (pages) /api/pages/basic", + "name": "executing api route (pages) /api/pages/[param]/basic", "parentId": "[parent-id]", "status": Object { "code": 0, diff --git a/test/e2e/opentelemetry/pages/api/pages/basic.ts b/test/e2e/opentelemetry/pages/api/pages/[param]/basic.ts similarity index 100% rename from test/e2e/opentelemetry/pages/api/pages/basic.ts rename to test/e2e/opentelemetry/pages/api/pages/[param]/basic.ts diff --git a/test/e2e/opentelemetry/pages/pages/getServerSideProps.tsx b/test/e2e/opentelemetry/pages/pages/[param]/getServerSideProps.tsx similarity index 100% rename from test/e2e/opentelemetry/pages/pages/getServerSideProps.tsx rename to test/e2e/opentelemetry/pages/pages/[param]/getServerSideProps.tsx diff --git a/test/e2e/opentelemetry/pages/pages/static/[param]/getStaticProps.tsx b/test/e2e/opentelemetry/pages/pages/[param]/getStaticProps.tsx similarity index 67% rename from test/e2e/opentelemetry/pages/pages/static/[param]/getStaticProps.tsx rename to test/e2e/opentelemetry/pages/pages/[param]/getStaticProps.tsx index 0429df7d29a91..3ba125ed2fe9c 100644 --- a/test/e2e/opentelemetry/pages/pages/static/[param]/getStaticProps.tsx +++ b/test/e2e/opentelemetry/pages/pages/[param]/getStaticProps.tsx @@ -8,7 +8,7 @@ export function getStaticProps() { } } -// We want to make sure that we are running getStaticProps on server and thus we are creating a traces for it. +// We don't want to render in build time export async function getStaticPaths() { return { paths: [], diff --git a/test/e2e/opentelemetry/pages/pages/index.tsx b/test/e2e/opentelemetry/pages/pages/index.tsx deleted file mode 100644 index ff7159d9149fe..0000000000000 --- a/test/e2e/opentelemetry/pages/pages/index.tsx +++ /dev/null @@ -1,3 +0,0 @@ -export default function Page() { - return

hello world

-} diff --git a/test/e2e/opentelemetry/pages/pages/params/[param].tsx b/test/e2e/opentelemetry/pages/pages/params/[param].tsx deleted file mode 100644 index ff7159d9149fe..0000000000000 --- a/test/e2e/opentelemetry/pages/pages/params/[param].tsx +++ /dev/null @@ -1,3 +0,0 @@ -export default function Page() { - return

hello world

-} From c34d5a1efdf7606f23300d7c2441eef07abc5498 Mon Sep 17 00:00:00 2001 From: Jan Kaifer Date: Tue, 21 Mar 2023 22:17:47 +0100 Subject: [PATCH 19/29] Added span for getStaticProps --- .../next/src/server/lib/trace/constants.ts | 2 ++ packages/next/src/server/render.tsx | 29 ++++++++++++------- test/e2e/opentelemetry/opentelemetry.test.ts | 12 ++++++++ 3 files changed, 33 insertions(+), 10 deletions(-) diff --git a/packages/next/src/server/lib/trace/constants.ts b/packages/next/src/server/lib/trace/constants.ts index 255baecd035b1..a9f2681abae26 100644 --- a/packages/next/src/server/lib/trace/constants.ts +++ b/packages/next/src/server/lib/trace/constants.ts @@ -72,6 +72,7 @@ enum StartServerSpan { enum RenderSpan { getServerSideProps = 'Render.getServerSideProps', + getStaticProps = 'Render.getStaticProps', renderToString = 'Render.renderToString', renderDocument = 'Render.renderDocument', createBodyResult = 'Render.createBodyResult', @@ -117,6 +118,7 @@ type SpanTypes = export const NextVanillaSpanAllowlist = [ BaseServerSpan.handleRequest, RenderSpan.getServerSideProps, + RenderSpan.getStaticProps, AppRenderSpan.fetch, AppRenderSpan.getBodyResult, RenderSpan.renderDocument, diff --git a/packages/next/src/server/render.tsx b/packages/next/src/server/render.tsx index 219ee0730cd98..0a18c1458799d 100644 --- a/packages/next/src/server/render.tsx +++ b/packages/next/src/server/render.tsx @@ -566,7 +566,7 @@ export async function renderToHTML( await Loadable.preloadAll() // Make sure all dynamic imports are loaded - let isPreview + let isPreview: boolean | undefined = undefined let previewData: PreviewData if ( @@ -761,15 +761,24 @@ export async function renderToHTML( let data: UnwrapPromise> try { - data = await getStaticProps!({ - ...(pageIsDynamic ? { params: query as ParsedUrlQuery } : undefined), - ...(isPreview - ? { preview: true, previewData: previewData } - : undefined), - locales: renderOpts.locales, - locale: renderOpts.locale, - defaultLocale: renderOpts.defaultLocale, - }) + data = await getTracer().trace( + RenderSpan.getStaticProps, + { + spanName: `getStaticProps ${pathname}`, + }, + () => + getStaticProps!({ + ...(pageIsDynamic + ? { params: query as ParsedUrlQuery } + : undefined), + ...(isPreview + ? { preview: true, previewData: previewData } + : undefined), + locales: renderOpts.locales, + locale: renderOpts.locale, + defaultLocale: renderOpts.defaultLocale, + }) + ) } catch (staticPropsError: any) { // remove not found error code to prevent triggering legacy // 404 rendering diff --git a/test/e2e/opentelemetry/opentelemetry.test.ts b/test/e2e/opentelemetry/opentelemetry.test.ts index ea03e68bec762..24e11ad2089fc 100644 --- a/test/e2e/opentelemetry/opentelemetry.test.ts +++ b/test/e2e/opentelemetry/opentelemetry.test.ts @@ -264,6 +264,18 @@ createNextDescribe( "code": 0, }, }, + Object { + "attributes": Object { + "next.span_name": "getStaticProps /pages/[param]/getStaticProps", + "next.span_type": "Render.getStaticProps", + }, + "kind": 0, + "name": "getStaticProps /pages/[param]/getStaticProps", + "parentId": "[parent-id]", + "status": Object { + "code": 0, + }, + }, Object { "attributes": Object { "next.span_name": "render page (pages) /pages/[param]/getStaticProps", From 55f50bcf552509eb03553049d7790f5d65275b5d Mon Sep 17 00:00:00 2001 From: Jan Kaifer Date: Tue, 21 Mar 2023 22:21:14 +0100 Subject: [PATCH 20/29] fix naming to adhere to page vs route distinction --- packages/next/src/server/app-render/index.tsx | 2 +- packages/next/src/server/render.tsx | 2 +- test/e2e/opentelemetry/opentelemetry.test.ts | 12 ++++++------ 3 files changed, 8 insertions(+), 8 deletions(-) diff --git a/packages/next/src/server/app-render/index.tsx b/packages/next/src/server/app-render/index.tsx index fb0f6fe4529f5..c091533403406 100644 --- a/packages/next/src/server/app-render/index.tsx +++ b/packages/next/src/server/app-render/index.tsx @@ -1179,7 +1179,7 @@ export async function renderToHTMLOrFlight( const bodyResult = getTracer().wrap( AppRenderSpan.getBodyResult, { - spanName: `render page (app) ${pathname}`, + spanName: `render route (app) ${pathname}`, }, async ({ asNotFound, diff --git a/packages/next/src/server/render.tsx b/packages/next/src/server/render.tsx index 0a18c1458799d..26495dc71bf6e 100644 --- a/packages/next/src/server/render.tsx +++ b/packages/next/src/server/render.tsx @@ -1334,7 +1334,7 @@ export async function renderToHTML( const documentResult = await getTracer().trace( RenderSpan.renderDocument, { - spanName: `render page (pages) ${renderOpts.pathname}`, + spanName: `render route (pages) ${renderOpts.pathname}`, }, async () => renderDocument() ) diff --git a/test/e2e/opentelemetry/opentelemetry.test.ts b/test/e2e/opentelemetry/opentelemetry.test.ts index 24e11ad2089fc..a323ddc39db87 100644 --- a/test/e2e/opentelemetry/opentelemetry.test.ts +++ b/test/e2e/opentelemetry/opentelemetry.test.ts @@ -103,11 +103,11 @@ createNextDescribe( }, Object { "attributes": Object { - "next.span_name": "render page (app) /app/[param]/rsc-fetch", + "next.span_name": "render route (app) /app/[param]/rsc-fetch", "next.span_type": "AppRender.getBodyResult", }, "kind": 0, - "name": "render page (app) /app/[param]/rsc-fetch", + "name": "render route (app) /app/[param]/rsc-fetch", "parentId": "[parent-id]", "status": Object { "code": 0, @@ -230,11 +230,11 @@ createNextDescribe( }, Object { "attributes": Object { - "next.span_name": "render page (pages) /pages/[param]/getServerSideProps", + "next.span_name": "render route (pages) /pages/[param]/getServerSideProps", "next.span_type": "Render.renderDocument", }, "kind": 0, - "name": "render page (pages) /pages/[param]/getServerSideProps", + "name": "render route (pages) /pages/[param]/getServerSideProps", "parentId": "[parent-id]", "status": Object { "code": 0, @@ -278,11 +278,11 @@ createNextDescribe( }, Object { "attributes": Object { - "next.span_name": "render page (pages) /pages/[param]/getStaticProps", + "next.span_name": "render route (pages) /pages/[param]/getStaticProps", "next.span_type": "Render.renderDocument", }, "kind": 0, - "name": "render page (pages) /pages/[param]/getStaticProps", + "name": "render route (pages) /pages/[param]/getStaticProps", "parentId": "[parent-id]", "status": Object { "code": 0, From 6c6cc4e7c7881bd42cf51d39705b8fc089d63758 Mon Sep 17 00:00:00 2001 From: Jan Kaifer Date: Wed, 22 Mar 2023 11:52:23 +0100 Subject: [PATCH 21/29] Add route into root component --- packages/next/src/server/base-server.ts | 30 ++++- packages/next/src/server/lib/trace/tracer.ts | 114 +++++++++++++------ packages/next/src/server/next-server.ts | 1 + test/e2e/opentelemetry/opentelemetry.test.ts | 16 ++- 4 files changed, 118 insertions(+), 43 deletions(-) diff --git a/packages/next/src/server/base-server.ts b/packages/next/src/server/base-server.ts index 7b8c1843b6a4c..a5d8e5916f643 100644 --- a/packages/next/src/server/base-server.ts +++ b/packages/next/src/server/base-server.ts @@ -520,22 +520,44 @@ export default abstract class Server { res: BaseNextResponse, parsedUrl?: NextUrlWithParsedQuery ): Promise { + const method = req.method.toUpperCase() return getTracer().trace( BaseServerSpan.handleRequest, { - spanName: [req.method, req.url].join(' '), + spanName: `${method} ${req.url}`, kind: SpanKind.SERVER, attributes: { - 'http.method': req.method, + 'http.method': method, 'http.target': req.url, }, }, async (span) => - this.handleRequestImpl(req, res, parsedUrl).finally(() => + this.handleRequestImpl(req, res, parsedUrl).finally(() => { span?.setAttributes({ 'http.status_code': res.statusCode, }) - ) + const rootSpanAttributes = getTracer().getRootSpanAttributes() + if ( + rootSpanAttributes?.get('next.span_type') !== + BaseServerSpan.handleRequest + ) { + console.warn( + `Unexpected root span type '${rootSpanAttributes?.get( + 'next.span_type' + )}'. This is a Next.js bug. Please report this. ` + ) + return + } + + const route = rootSpanAttributes.get('next.route') + if (route) { + span?.setAttributes({ + 'next.route': route, + 'http.route': route, + }) + span?.updateName(`${method} ${route}`) + } + }) ) } diff --git a/packages/next/src/server/lib/trace/tracer.ts b/packages/next/src/server/lib/trace/tracer.ts index 45e8a229b7049..fa844f859eab3 100644 --- a/packages/next/src/server/lib/trace/tracer.ts +++ b/packages/next/src/server/lib/trace/tracer.ts @@ -1,6 +1,12 @@ import { NextVanillaSpanAllowlist, SpanTypes } from './constants' -import type { ContextAPI, Span, SpanOptions, Tracer } from '@opentelemetry/api' +import type { + ContextAPI, + Span, + SpanOptions, + Tracer, + AttributeValue, +} from '@opentelemetry/api' let api: typeof import('@opentelemetry/api') @@ -31,9 +37,10 @@ const closeSpanWithError = (span: Span, error?: Error) => { span.end() } -type TracerSpanOptions = SpanOptions & { +type TracerSpanOptions = Omit & { parentSpan?: Span spanName?: string + attributes?: Partial> } interface NextTracer { @@ -117,6 +124,23 @@ interface NextTracer { getActiveScopeSpan(): Span | undefined } +type NextAttributeNames = + | 'next.route' + | 'next.page' + | 'next.span_name' + | 'next.span_type' +type OTELAttributeNames = `http.${string}` +type AttributeNames = NextAttributeNames | OTELAttributeNames + +/** we use this map to propagate attributes from nested spans to the top span */ +const rootSpanAttributesStore = new Map< + number, + Map +>() +const rootSpanIdKey = api.createContextKey('next.rootSpanId') +let lastSpanId = 0 +const getSpanId = () => lastSpanId++ + class NextTracerImpl implements NextTracer { /** * Returns an instance to the trace with configured name. @@ -186,9 +210,17 @@ class NextTracerImpl implements NextTracer { const spanName = options.spanName ?? type // Trying to get active scoped span to assign parent. If option specifies parent span manually, will try to use it. - const spanContext = this.getSpanContext( + let spanContext = this.getSpanContext( options?.parentSpan ?? this.getActiveScopeSpan() ) + let isRootSpan = false + + if (!spanContext) { + spanContext = api.ROOT_CONTEXT + isRootSpan = true + } + + const spanId = getSpanId() options.attributes = { 'next.span_name': spanName, @@ -196,39 +228,46 @@ class NextTracerImpl implements NextTracer { ...options.attributes, } - const runWithContext = (actualFn: (span: Span) => T | Promise) => - spanContext - ? this.getTracerInstance().startActiveSpan( - spanName, - options, - spanContext, - actualFn - ) - : this.getTracerInstance().startActiveSpan(spanName, options, actualFn) - - return runWithContext((span: Span) => { - try { - if (fn.length > 1) { - return fn(span, (err?: Error) => closeSpanWithError(span, err)) - } - - const result = fn(span) - - if (isPromise(result)) { - result.then( - () => span.end(), - (err) => closeSpanWithError(span, err) - ) - } else { - span.end() + return api.context.with(spanContext.setValue(rootSpanIdKey, spanId), () => + this.getTracerInstance().startActiveSpan( + spanName, + options, + (span: Span) => { + if (isRootSpan) { + rootSpanAttributesStore.set( + spanId, + new Map( + Object.entries(options.attributes ?? {}) as [ + AttributeNames, + AttributeValue | undefined + ][] + ) + ) + } + try { + if (fn.length > 1) { + return fn(span, (err?: Error) => closeSpanWithError(span, err)) + } + + const result = fn(span) + + if (isPromise(result)) { + result.then( + () => span.end(), + (err) => closeSpanWithError(span, err) + ) + } else { + span.end() + } + + return result + } catch (err: any) { + closeSpanWithError(span, err) + throw err + } } - - return result - } catch (err: any) { - closeSpanWithError(span, err) - throw err - } - }) + ) + ) } public wrap) => any>(type: SpanTypes, fn: T): T @@ -297,6 +336,11 @@ class NextTracerImpl implements NextTracer { return spanContext } + + public getRootSpanAttributes() { + const spanId = context.active().getValue(rootSpanIdKey) as number + return rootSpanAttributesStore.get(spanId) + } } const getTracer = (() => { diff --git a/packages/next/src/server/next-server.ts b/packages/next/src/server/next-server.ts index 9303499e32354..4df2c7cf2fabf 100644 --- a/packages/next/src/server/next-server.ts +++ b/packages/next/src/server/next-server.ts @@ -958,6 +958,7 @@ export default class NextNodeServer extends BaseServer { params: Params | null isAppPath: boolean }): Promise { + getTracer().getRootSpanAttributes()?.set('next.route', pathname) return getTracer().trace( NextNodeServerSpan.findPageComponents, { diff --git a/test/e2e/opentelemetry/opentelemetry.test.ts b/test/e2e/opentelemetry/opentelemetry.test.ts index a323ddc39db87..a785322bc6e55 100644 --- a/test/e2e/opentelemetry/opentelemetry.test.ts +++ b/test/e2e/opentelemetry/opentelemetry.test.ts @@ -116,13 +116,15 @@ createNextDescribe( Object { "attributes": Object { "http.method": "GET", + "http.route": "/app/[param]/rsc-fetch/page", "http.status_code": 200, "http.target": "/app/param/rsc-fetch", + "next.route": "/app/[param]/rsc-fetch/page", "next.span_name": "GET /app/param/rsc-fetch", "next.span_type": "BaseServer.handleRequest", }, "kind": 1, - "name": "GET /app/param/rsc-fetch", + "name": "GET /app/[param]/rsc-fetch/page", "parentId": undefined, "status": Object { "code": 0, @@ -178,13 +180,15 @@ createNextDescribe( Object { "attributes": Object { "http.method": "GET", + "http.route": "/api/app/[param]/data/route", "http.status_code": 200, "http.target": "/api/app/param/data", + "next.route": "/api/app/[param]/data/route", "next.span_name": "GET /api/app/param/data", "next.span_type": "BaseServer.handleRequest", }, "kind": 1, - "name": "GET /api/app/param/data", + "name": "GET /api/app/[param]/data/route", "parentId": undefined, "status": Object { "code": 0, @@ -204,13 +208,15 @@ createNextDescribe( Object { "attributes": Object { "http.method": "GET", + "http.route": "/pages/[param]/getServerSideProps", "http.status_code": 200, "http.target": "/pages/param/getServerSideProps", + "next.route": "/pages/[param]/getServerSideProps", "next.span_name": "GET /pages/param/getServerSideProps", "next.span_type": "BaseServer.handleRequest", }, "kind": 1, - "name": "GET /pages/param/getServerSideProps", + "name": "GET /pages/[param]/getServerSideProps", "parentId": undefined, "status": Object { "code": 0, @@ -252,13 +258,15 @@ createNextDescribe( Object { "attributes": Object { "http.method": "GET", + "http.route": "/pages/[param]/getStaticProps", "http.status_code": 200, "http.target": "/pages/param/getStaticProps", + "next.route": "/pages/[param]/getStaticProps", "next.span_name": "GET /pages/param/getStaticProps", "next.span_type": "BaseServer.handleRequest", }, "kind": 1, - "name": "GET /pages/param/getStaticProps", + "name": "GET /pages/[param]/getStaticProps", "parentId": undefined, "status": Object { "code": 0, From 0200ac2f0bbe64c87b7e22215e37bfb7a04e8471 Mon Sep 17 00:00:00 2001 From: Jan Kaifer Date: Wed, 22 Mar 2023 13:51:56 +0100 Subject: [PATCH 22/29] fix wrong span attribute in renderToResponse --- packages/next/src/server/base-server.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/next/src/server/base-server.ts b/packages/next/src/server/base-server.ts index a5d8e5916f643..b3203368050d8 100644 --- a/packages/next/src/server/base-server.ts +++ b/packages/next/src/server/base-server.ts @@ -1982,7 +1982,7 @@ export default abstract class Server { { spanName: `rendering page`, attributes: { - 'next.pathname': ctx.pathname, + 'next.route': ctx.pathname, }, }, async () => { From 7b40636d4cf8abd6371fd2aea033e5eff38ca88a Mon Sep 17 00:00:00 2001 From: Jan Kaifer Date: Wed, 22 Mar 2023 14:33:29 +0100 Subject: [PATCH 23/29] Update packages/next/src/server/base-server.ts Co-authored-by: Steven --- packages/next/src/server/base-server.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/next/src/server/base-server.ts b/packages/next/src/server/base-server.ts index b3203368050d8..965eb63c16124 100644 --- a/packages/next/src/server/base-server.ts +++ b/packages/next/src/server/base-server.ts @@ -544,7 +544,7 @@ export default abstract class Server { console.warn( `Unexpected root span type '${rootSpanAttributes?.get( 'next.span_type' - )}'. This is a Next.js bug. Please report this. ` + )}'. Please report this Next.js issue https://github.com/vercel/next.js` ) return } From ab6b7cb275aadcf315a28867b88031f0ee4fc1f6 Mon Sep 17 00:00:00 2001 From: Jan Kaifer Date: Wed, 22 Mar 2023 14:35:34 +0100 Subject: [PATCH 24/29] add missing otel attribute names net. --- packages/next/src/server/lib/trace/tracer.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/next/src/server/lib/trace/tracer.ts b/packages/next/src/server/lib/trace/tracer.ts index fa844f859eab3..3896256017c52 100644 --- a/packages/next/src/server/lib/trace/tracer.ts +++ b/packages/next/src/server/lib/trace/tracer.ts @@ -129,7 +129,7 @@ type NextAttributeNames = | 'next.page' | 'next.span_name' | 'next.span_type' -type OTELAttributeNames = `http.${string}` +type OTELAttributeNames = `http.${string}` | `net.${string}` type AttributeNames = NextAttributeNames | OTELAttributeNames /** we use this map to propagate attributes from nested spans to the top span */ From dea9a06fda56a003d8ae54103ab2f3a3ea983869 Mon Sep 17 00:00:00 2001 From: Jan Kaifer Date: Wed, 22 Mar 2023 14:42:05 +0100 Subject: [PATCH 25/29] fix next attribute name --- packages/next/src/server/router.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/next/src/server/router.ts b/packages/next/src/server/router.ts index 0d500e7334b7d..432293f425c61 100644 --- a/packages/next/src/server/router.ts +++ b/packages/next/src/server/router.ts @@ -246,7 +246,7 @@ export default class Router { RouterSpan.executeRoute, { attributes: { - route: route.name, + 'next.route': route.name, }, }, route.fn From 36fbc1900d1f69fa112c7f8e9a012e5e803975af Mon Sep 17 00:00:00 2001 From: Jan Kaifer Date: Wed, 22 Mar 2023 14:55:03 +0100 Subject: [PATCH 26/29] add check when tracing is not enabled --- packages/next/src/server/base-server.ts | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/packages/next/src/server/base-server.ts b/packages/next/src/server/base-server.ts index 965eb63c16124..7ae0f3f2e9ac0 100644 --- a/packages/next/src/server/base-server.ts +++ b/packages/next/src/server/base-server.ts @@ -533,7 +533,8 @@ export default abstract class Server { }, async (span) => this.handleRequestImpl(req, res, parsedUrl).finally(() => { - span?.setAttributes({ + if (!span) return + span.setAttributes({ 'http.status_code': res.statusCode, }) const rootSpanAttributes = getTracer().getRootSpanAttributes() @@ -551,11 +552,11 @@ export default abstract class Server { const route = rootSpanAttributes.get('next.route') if (route) { - span?.setAttributes({ + span.setAttributes({ 'next.route': route, 'http.route': route, }) - span?.updateName(`${method} ${route}`) + span.updateName(`${method} ${route}`) } }) ) From 3198279da96ffbee8921d8e9c8490bf32ef1d6e8 Mon Sep 17 00:00:00 2001 From: Jan Kaifer Date: Wed, 22 Mar 2023 15:05:24 +0100 Subject: [PATCH 27/29] don't throw when we haven't found root span attributes --- packages/next/src/server/base-server.ts | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/packages/next/src/server/base-server.ts b/packages/next/src/server/base-server.ts index 7ae0f3f2e9ac0..44560dc0a5d55 100644 --- a/packages/next/src/server/base-server.ts +++ b/packages/next/src/server/base-server.ts @@ -538,12 +538,15 @@ export default abstract class Server { 'http.status_code': res.statusCode, }) const rootSpanAttributes = getTracer().getRootSpanAttributes() + // We were unable to get attributes, probably OTEL is not enabled + if (!rootSpanAttributes) return + if ( - rootSpanAttributes?.get('next.span_type') !== + rootSpanAttributes.get('next.span_type') !== BaseServerSpan.handleRequest ) { console.warn( - `Unexpected root span type '${rootSpanAttributes?.get( + `Unexpected root span type '${rootSpanAttributes.get( 'next.span_type' )}'. Please report this Next.js issue https://github.com/vercel/next.js` ) From 4d0c596caedcbd9c4ace0ce91ec5ad3992252138 Mon Sep 17 00:00:00 2001 From: Jan Kaifer Date: Thu, 23 Mar 2023 09:40:59 +0100 Subject: [PATCH 28/29] Cleanup rootSpanAttributesStore entries when span finishes --- packages/next/src/server/lib/trace/tracer.ts | 2 ++ 1 file changed, 2 insertions(+) diff --git a/packages/next/src/server/lib/trace/tracer.ts b/packages/next/src/server/lib/trace/tracer.ts index 3896256017c52..e89bdb1d77b6a 100644 --- a/packages/next/src/server/lib/trace/tracer.ts +++ b/packages/next/src/server/lib/trace/tracer.ts @@ -264,6 +264,8 @@ class NextTracerImpl implements NextTracer { } catch (err: any) { closeSpanWithError(span, err) throw err + } finally { + rootSpanAttributesStore.delete(spanId) } } ) From 8ca6b908b4c0632547c1ede509b059e7997ea30a Mon Sep 17 00:00:00 2001 From: Jan Kaifer Date: Thu, 23 Mar 2023 11:13:09 +0100 Subject: [PATCH 29/29] fix cleanup to handle async and sync separately --- packages/next/src/server/lib/trace/tracer.ts | 17 +++++++++++------ 1 file changed, 11 insertions(+), 6 deletions(-) diff --git a/packages/next/src/server/lib/trace/tracer.ts b/packages/next/src/server/lib/trace/tracer.ts index e89bdb1d77b6a..88a8b0badb101 100644 --- a/packages/next/src/server/lib/trace/tracer.ts +++ b/packages/next/src/server/lib/trace/tracer.ts @@ -233,6 +233,9 @@ class NextTracerImpl implements NextTracer { spanName, options, (span: Span) => { + const onCleanup = () => { + rootSpanAttributesStore.delete(spanId) + } if (isRootSpan) { rootSpanAttributesStore.set( spanId, @@ -252,20 +255,22 @@ class NextTracerImpl implements NextTracer { const result = fn(span) if (isPromise(result)) { - result.then( - () => span.end(), - (err) => closeSpanWithError(span, err) - ) + result + .then( + () => span.end(), + (err) => closeSpanWithError(span, err) + ) + .finally(onCleanup) } else { span.end() + onCleanup() } return result } catch (err: any) { closeSpanWithError(span, err) + onCleanup() throw err - } finally { - rootSpanAttributesStore.delete(spanId) } } )