Skip to content

Commit fe639f4

Browse files
lforstchargome
andauthored
feat(nextjs/vercel-edge/cloudflare): Switch to OTEL for performance monitoring (#13889)
Co-authored-by: Charly Gomez <[email protected]>
1 parent c56d84d commit fe639f4

File tree

71 files changed

+1565
-1094
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

71 files changed

+1565
-1094
lines changed

.github/workflows/build.yml

+2
Original file line numberDiff line numberDiff line change
@@ -1037,6 +1037,7 @@ jobs:
10371037
retention-days: 7
10381038

10391039
- name: Pre-process E2E Test Dumps
1040+
if: always()
10401041
run: |
10411042
node ./scripts/normalize-e2e-test-dump-transaction-events.js
10421043
@@ -1193,6 +1194,7 @@ jobs:
11931194
run: pnpm ${{ matrix.assert-command || 'test:assert' }}
11941195

11951196
- name: Pre-process E2E Test Dumps
1197+
if: always()
11961198
run: |
11971199
node ./scripts/normalize-e2e-test-dump-transaction-events.js
11981200

.size-limit.js

+1-1
Original file line numberDiff line numberDiff line change
@@ -224,7 +224,7 @@ module.exports = [
224224
import: createImport('init'),
225225
ignore: ['next/router', 'next/constants'],
226226
gzip: true,
227-
limit: '39 KB',
227+
limit: '39.1 KB',
228228
},
229229
// SvelteKit SDK (ESM)
230230
{

dev-packages/e2e-tests/publish-packages.ts

+4-6
Original file line numberDiff line numberDiff line change
@@ -12,21 +12,19 @@ const packageTarballPaths = glob.sync('packages/*/sentry-*.tgz', {
1212

1313
// Publish built packages to the fake registry
1414
packageTarballPaths.forEach(tarballPath => {
15+
// eslint-disable-next-line no-console
16+
console.log(`Publishing tarball ${tarballPath} ...`);
1517
// `--userconfig` flag needs to be before `publish`
1618
childProcess.exec(
1719
`npm --userconfig ${__dirname}/test-registry.npmrc publish ${tarballPath}`,
1820
{
1921
cwd: repositoryRoot, // Can't use __dirname here because npm would try to publish `@sentry-internal/e2e-tests`
2022
encoding: 'utf8',
2123
},
22-
(err, stdout, stderr) => {
23-
// eslint-disable-next-line no-console
24-
console.log(stdout);
25-
// eslint-disable-next-line no-console
26-
console.log(stderr);
24+
err => {
2725
if (err) {
2826
// eslint-disable-next-line no-console
29-
console.error(err);
27+
console.error(`Error publishing tarball ${tarballPath}`, err);
3028
process.exit(1);
3129
}
3230
},

dev-packages/e2e-tests/test-applications/nextjs-13/package.json

+1-1
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@
1717
"@types/node": "18.11.17",
1818
"@types/react": "18.0.26",
1919
"@types/react-dom": "18.0.9",
20-
"next": "13.2.0",
20+
"next": "13.5.7",
2121
"react": "18.2.0",
2222
"react-dom": "18.2.0",
2323
"typescript": "4.9.5"

dev-packages/e2e-tests/test-applications/nextjs-13/tests/client/pages-dir-pageload.test.ts

+35
Original file line numberDiff line numberDiff line change
@@ -46,3 +46,38 @@ test('should create a pageload transaction when the `pages` directory is used',
4646
type: 'transaction',
4747
});
4848
});
49+
50+
test('should create a pageload transaction with correct name when an error occurs in getServerSideProps', async ({
51+
page,
52+
}) => {
53+
const transactionPromise = waitForTransaction('nextjs-13', async transactionEvent => {
54+
return (
55+
transactionEvent.transaction === '/[param]/error-getServerSideProps' &&
56+
transactionEvent.contexts?.trace?.op === 'pageload'
57+
);
58+
});
59+
60+
await page.goto(`/something/error-getServerSideProps`, { waitUntil: 'networkidle' });
61+
62+
const transaction = await transactionPromise;
63+
64+
expect(transaction).toMatchObject({
65+
contexts: {
66+
trace: {
67+
data: {
68+
'sentry.op': 'pageload',
69+
'sentry.origin': 'auto.pageload.nextjs.pages_router_instrumentation',
70+
'sentry.source': 'route',
71+
},
72+
op: 'pageload',
73+
origin: 'auto.pageload.nextjs.pages_router_instrumentation',
74+
},
75+
},
76+
transaction: '/[param]/error-getServerSideProps',
77+
transaction_info: { source: 'route' },
78+
type: 'transaction',
79+
});
80+
81+
// Ensure the transaction name is not '/_error'
82+
expect(transaction.transaction).not.toBe('/_error');
83+
});

dev-packages/e2e-tests/test-applications/nextjs-13/tests/isomorphic/getInitialProps.test.ts

+2-2
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ test('should propagate serverside `getInitialProps` trace to client', async ({ p
1111

1212
const serverTransactionPromise = waitForTransaction('nextjs-13', async transactionEvent => {
1313
return (
14-
transactionEvent.transaction === '/[param]/withInitialProps' &&
14+
transactionEvent.transaction === 'GET /[param]/withInitialProps' &&
1515
transactionEvent.contexts?.trace?.op === 'http.server'
1616
);
1717
});
@@ -47,7 +47,7 @@ test('should propagate serverside `getInitialProps` trace to client', async ({ p
4747
status: 'ok',
4848
},
4949
},
50-
transaction: '/[param]/withInitialProps',
50+
transaction: 'GET /[param]/withInitialProps',
5151
transaction_info: {
5252
source: 'route',
5353
},

dev-packages/e2e-tests/test-applications/nextjs-13/tests/isomorphic/getServerSideProps.test.ts

+2-2
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ test('Should record performance for getServerSideProps', async ({ page }) => {
1111

1212
const serverTransactionPromise = waitForTransaction('nextjs-13', async transactionEvent => {
1313
return (
14-
transactionEvent.transaction === '/[param]/withServerSideProps' &&
14+
transactionEvent.transaction === 'GET /[param]/withServerSideProps' &&
1515
transactionEvent.contexts?.trace?.op === 'http.server'
1616
);
1717
});
@@ -47,7 +47,7 @@ test('Should record performance for getServerSideProps', async ({ page }) => {
4747
status: 'ok',
4848
},
4949
},
50-
transaction: '/[param]/withServerSideProps',
50+
transaction: 'GET /[param]/withServerSideProps',
5151
transaction_info: {
5252
source: 'route',
5353
},
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import { expect, test } from '@playwright/test';
22
import { waitForTransaction } from '@sentry-internal/test-utils';
33

4-
test('should not automatically create transactions for routes that were excluded from auto wrapping (string)', async ({
4+
test('should not apply build-time instrumentation for routes that were excluded from auto wrapping (string)', async ({
55
request,
66
}) => {
77
const transactionPromise = waitForTransaction('nextjs-13', async transactionEvent => {
@@ -13,17 +13,13 @@ test('should not automatically create transactions for routes that were excluded
1313

1414
expect(await (await request.get(`/api/endpoint-excluded-with-string`)).text()).toBe('{"success":true}');
1515

16-
let transactionPromiseReceived = false;
17-
transactionPromise.then(() => {
18-
transactionPromiseReceived = true;
19-
});
20-
21-
await new Promise(resolve => setTimeout(resolve, 5_000));
16+
const transaction = await transactionPromise;
2217

23-
expect(transactionPromiseReceived).toBe(false);
18+
expect(transaction.contexts?.trace?.data?.['sentry.origin']).toBeDefined();
19+
expect(transaction.contexts?.trace?.data?.['sentry.origin']).not.toBe('auto.http.nextjs'); // This is the origin set by the build time instrumentation
2420
});
2521

26-
test('should not automatically create transactions for routes that were excluded from auto wrapping (regex)', async ({
22+
test('should not apply build-time instrumentation for routes that were excluded from auto wrapping (regex)', async ({
2723
request,
2824
}) => {
2925
const transactionPromise = waitForTransaction('nextjs-13', async transactionEvent => {
@@ -35,12 +31,8 @@ test('should not automatically create transactions for routes that were excluded
3531

3632
expect(await (await request.get(`/api/endpoint-excluded-with-regex`)).text()).toBe('{"success":true}');
3733

38-
let transactionPromiseReceived = false;
39-
transactionPromise.then(() => {
40-
transactionPromiseReceived = true;
41-
});
42-
43-
await new Promise(resolve => setTimeout(resolve, 5_000));
34+
const transaction = await transactionPromise;
4435

45-
expect(transactionPromiseReceived).toBe(false);
36+
expect(transaction.contexts?.trace?.data?.['sentry.origin']).toBeDefined();
37+
expect(transaction.contexts?.trace?.data?.['sentry.origin']).not.toBe('auto.http.nextjs'); // This is the origin set by the build time instrumentation
4638
});

dev-packages/e2e-tests/test-applications/nextjs-13/tests/server/getServerSideProps.test.ts

+13-12
Original file line numberDiff line numberDiff line change
@@ -8,12 +8,12 @@ test('Should report an error event for errors thrown in getServerSideProps', asy
88

99
const transactionEventPromise = waitForTransaction('nextjs-13', transactionEvent => {
1010
return (
11-
transactionEvent.transaction === '/error-getServerSideProps' &&
11+
transactionEvent.transaction === 'GET /[param]/error-getServerSideProps' &&
1212
transactionEvent.contexts?.trace?.op === 'http.server'
1313
);
1414
});
1515

16-
await page.goto('/error-getServerSideProps');
16+
await page.goto('/dogsaregreat/error-getServerSideProps');
1717

1818
expect(await errorEventPromise).toMatchObject({
1919
contexts: {
@@ -40,7 +40,7 @@ test('Should report an error event for errors thrown in getServerSideProps', asy
4040
url: expect.stringMatching(/^http.*\/error-getServerSideProps/),
4141
},
4242
timestamp: expect.any(Number),
43-
transaction: 'getServerSideProps (/error-getServerSideProps)',
43+
transaction: 'getServerSideProps (/[param]/error-getServerSideProps)',
4444
});
4545

4646
expect(await transactionEventPromise).toMatchObject({
@@ -60,11 +60,11 @@ test('Should report an error event for errors thrown in getServerSideProps', asy
6060
data: {
6161
'http.response.status_code': 500,
6262
'sentry.op': 'http.server',
63-
'sentry.origin': 'auto.function.nextjs',
63+
'sentry.origin': 'auto',
6464
'sentry.source': 'route',
6565
},
6666
op: 'http.server',
67-
origin: 'auto.function.nextjs',
67+
origin: 'auto',
6868
span_id: expect.any(String),
6969
status: 'internal_error',
7070
trace_id: expect.any(String),
@@ -80,7 +80,7 @@ test('Should report an error event for errors thrown in getServerSideProps', asy
8080
},
8181
start_timestamp: expect.any(Number),
8282
timestamp: expect.any(Number),
83-
transaction: '/error-getServerSideProps',
83+
transaction: 'GET /[param]/error-getServerSideProps',
8484
transaction_info: { source: 'route' },
8585
type: 'transaction',
8686
});
@@ -95,11 +95,12 @@ test('Should report an error event for errors thrown in getServerSideProps in pa
9595

9696
const transactionEventPromise = waitForTransaction('nextjs-13', transactionEvent => {
9797
return (
98-
transactionEvent.transaction === '/customPageExtension' && transactionEvent.contexts?.trace?.op === 'http.server'
98+
transactionEvent.transaction === 'GET /[param]/customPageExtension' &&
99+
transactionEvent.contexts?.trace?.op === 'http.server'
99100
);
100101
});
101102

102-
await page.goto('/customPageExtension');
103+
await page.goto('/123/customPageExtension');
103104

104105
expect(await errorEventPromise).toMatchObject({
105106
contexts: {
@@ -126,7 +127,7 @@ test('Should report an error event for errors thrown in getServerSideProps in pa
126127
url: expect.stringMatching(/^http.*\/customPageExtension/),
127128
},
128129
timestamp: expect.any(Number),
129-
transaction: 'getServerSideProps (/customPageExtension)',
130+
transaction: 'getServerSideProps (/[param]/customPageExtension)',
130131
});
131132

132133
expect(await transactionEventPromise).toMatchObject({
@@ -146,11 +147,11 @@ test('Should report an error event for errors thrown in getServerSideProps in pa
146147
data: {
147148
'http.response.status_code': 500,
148149
'sentry.op': 'http.server',
149-
'sentry.origin': 'auto.function.nextjs',
150+
'sentry.origin': 'auto',
150151
'sentry.source': 'route',
151152
},
152153
op: 'http.server',
153-
origin: 'auto.function.nextjs',
154+
origin: 'auto',
154155
span_id: expect.any(String),
155156
status: 'internal_error',
156157
trace_id: expect.any(String),
@@ -166,7 +167,7 @@ test('Should report an error event for errors thrown in getServerSideProps in pa
166167
},
167168
start_timestamp: expect.any(Number),
168169
timestamp: expect.any(Number),
169-
transaction: '/customPageExtension',
170+
transaction: 'GET /[param]/customPageExtension',
170171
transaction_info: { source: 'route' },
171172
type: 'transaction',
172173
});

dev-packages/e2e-tests/test-applications/nextjs-app-dir/app/layout.tsx

+27-9
Original file line numberDiff line numberDiff line change
@@ -9,31 +9,49 @@ export default function Layout({ children }: { children: React.ReactNode }) {
99
<h1>Layout (/)</h1>
1010
<ul>
1111
<li>
12-
<Link href="/">/</Link>
12+
<Link href="/" prefetch={false}>
13+
/
14+
</Link>
1315
</li>
1416
<li>
15-
<Link href="/client-component">/client-component</Link>
17+
<Link href="/client-component" prefetch={false}>
18+
/client-component
19+
</Link>
1620
</li>
1721
<li>
18-
<Link href="/client-component/parameter/42">/client-component/parameter/42</Link>
22+
<Link href="/client-component/parameter/42" prefetch={false}>
23+
/client-component/parameter/42
24+
</Link>
1925
</li>
2026
<li>
21-
<Link href="/client-component/parameter/foo/bar/baz">/client-component/parameter/foo/bar/baz</Link>
27+
<Link href="/client-component/parameter/foo/bar/baz" prefetch={false}>
28+
/client-component/parameter/foo/bar/baz
29+
</Link>
2230
</li>
2331
<li>
24-
<Link href="/server-component">/server-component</Link>
32+
<Link href="/server-component" prefetch={false}>
33+
/server-component
34+
</Link>
2535
</li>
2636
<li>
27-
<Link href="/server-component/parameter/42">/server-component/parameter/42</Link>
37+
<Link href="/server-component/parameter/42" prefetch={false}>
38+
/server-component/parameter/42
39+
</Link>
2840
</li>
2941
<li>
30-
<Link href="/server-component/parameter/foo/bar/baz">/server-component/parameter/foo/bar/baz</Link>
42+
<Link href="/server-component/parameter/foo/bar/baz" prefetch={false}>
43+
/server-component/parameter/foo/bar/baz
44+
</Link>
3145
</li>
3246
<li>
33-
<Link href="/not-found">/not-found</Link>
47+
<Link href="/not-found" prefetch={false}>
48+
/not-found
49+
</Link>
3450
</li>
3551
<li>
36-
<Link href="/redirect">/redirect</Link>
52+
<Link href="/redirect" prefetch={false}>
53+
/redirect
54+
</Link>
3755
</li>
3856
</ul>
3957
<SpanContextProvider>{children}</SpanContextProvider>

dev-packages/e2e-tests/test-applications/nextjs-app-dir/middleware.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -20,5 +20,5 @@ export async function middleware(request: NextRequest) {
2020

2121
// See "Matching Paths" below to learn more
2222
export const config = {
23-
matcher: ['/api/endpoint-behind-middleware'],
23+
matcher: ['/api/endpoint-behind-middleware', '/api/endpoint-behind-faulty-middleware'],
2424
};

dev-packages/e2e-tests/test-applications/nextjs-app-dir/pages/api/async-context-edge-endpoint.ts

+10-9
Original file line numberDiff line numberDiff line change
@@ -7,21 +7,22 @@ export const config = {
77
export default async function handler() {
88
// Without a working async context strategy the two spans created by `Sentry.startSpan()` would be nested.
99

10-
const outerSpanPromise = Sentry.withIsolationScope(() => {
11-
return Sentry.startSpan({ name: 'outer-span' }, () => {
12-
return new Promise<void>(resolve => setTimeout(resolve, 300));
13-
});
10+
const outerSpanPromise = Sentry.startSpan({ name: 'outer-span' }, () => {
11+
return new Promise<void>(resolve => setTimeout(resolve, 300));
1412
});
1513

16-
setTimeout(() => {
17-
Sentry.withIsolationScope(() => {
18-
return Sentry.startSpan({ name: 'inner-span' }, () => {
14+
const innerSpanPromise = new Promise<void>(resolve => {
15+
setTimeout(() => {
16+
Sentry.startSpan({ name: 'inner-span' }, () => {
1917
return new Promise<void>(resolve => setTimeout(resolve, 100));
18+
}).then(() => {
19+
resolve();
2020
});
21-
});
22-
}, 100);
21+
}, 100);
22+
});
2323

2424
await outerSpanPromise;
25+
await innerSpanPromise;
2526

2627
return new Response('ok', { status: 200 });
2728
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
import type { NextApiRequest, NextApiResponse } from 'next';
2+
3+
type Data = {
4+
name: string;
5+
};
6+
7+
export default function handler(req: NextApiRequest, res: NextApiResponse<Data>) {
8+
res.status(200).json({ name: 'John Doe' });
9+
}

0 commit comments

Comments
 (0)