feat(nextjs): Add performance monitoring to server components#7242
feat(nextjs): Add performance monitoring to server components#7242
Conversation
…server-components
This comment was marked as outdated.
This comment was marked as outdated.
| const sentryRequest = https.request( | ||
| sentryIngestUrl, | ||
| { headers: proxyRequest.headers, method: proxyRequest.method }, | ||
| sentryResponse => { | ||
| sentryResponse.addListener('data', (chunk: Buffer) => { | ||
| proxyResponse.write(chunk, 'binary'); | ||
| sentryResponseChunks.push(chunk); | ||
| }); | ||
|
|
||
| sentryResponse.addListener('end', () => { | ||
| eventCallbackListeners.forEach(listener => { | ||
| const rawProxyRequestBody = Buffer.concat(proxyRequestChunks).toString(); | ||
| const rawSentryResponseBody = Buffer.concat(sentryResponseChunks).toString(); | ||
|
|
||
| const data: SentryRequestCallbackData = { | ||
| envelope: parseEnvelope(rawProxyRequestBody, new TextEncoder(), new TextDecoder()), | ||
| rawProxyRequestBody, | ||
| rawSentryResponseBody, | ||
| sentryResponseStatusCode: sentryResponse.statusCode, | ||
| }; | ||
|
|
||
| listener(Buffer.from(JSON.stringify(data)).toString('base64')); | ||
| }); | ||
| proxyResponse.end(); | ||
| }); | ||
|
|
||
| sentryResponse.addListener('error', err => { | ||
| throw err; | ||
| }); | ||
|
|
||
| proxyResponse.writeHead(sentryResponse.statusCode || 500, sentryResponse.headers); | ||
| }, | ||
| ); |
Check failure
Code scanning / CodeQL
Server-side request forgery
| let appDirPath: string; | ||
| if (fs.existsSync(path.join(projectDir, 'app')) && fs.lstatSync(path.join(projectDir, 'app')).isDirectory()) { | ||
| appDirPath = path.join(projectDir, 'app'); | ||
| } else { | ||
| appDirPath = path.join(projectDir, 'src', 'app'); | ||
| } |
There was a problem hiding this comment.
This actually caused a bug and was painful af to find....
There was a problem hiding this comment.
Turns out tests help at finding bugs. Who would have thought 🤔
…ment-server-components
AbhiPrasad
left a comment
There was a problem hiding this comment.
Are there links to what this looks like? Does backend <-> frontend tracing work? Screenshots in the PR would also suffice.
| pageExtensionRegex: string; | ||
| excludeServerRoutes: Array<RegExp | string>; | ||
| wrappingTargetKind: 'page' | 'api-route' | 'middleware' | 'page-server-component'; | ||
| wrappingTargetKind: 'page' | 'api-route' | 'middleware' | 'server-component'; |
There was a problem hiding this comment.
lowest l possible: Would be nice if this was a string enum
| import { startEventProxyServer, waitForTransaction } from '../../test-utils/event-proxy-server'; | ||
|
|
||
| startEventProxyServer({ | ||
| port: 27496, |
There was a problem hiding this comment.
are these ports random? Should they not be?
There was a problem hiding this comment.
It's kinda tricky. For Next.js we need to know the port of the proxy server at runtime AND at build time. Playwright (and implicitly the proxy server) are started after the project is built (obviously).
Instead of writing some convoluted logic that picks a port and then passes it everywhere, for now, I just opted to pick some port statically. Of course, this is somewhat error-prone but usually, it is just a "write once and forget" kinda thing.
|
|
||
| Sentry.init({ | ||
| dsn: process.env.NEXT_PUBLIC_E2E_TEST_DSN, | ||
| tunnel: 'http://localhost:27496/', // proxy server |
There was a problem hiding this comment.
l: maybe we can inject the tunnel in via a environmental variable?
| const transactionEvent = await serverComponentTransactionPromise; | ||
| const transactionEventId = transactionEvent.event_id; | ||
|
|
||
| console.log(`Polling for server component transaction eventId: ${transactionEventId}`); |
There was a problem hiding this comment.
m: Could we have waitForTransaction emit these logs? I'd like to keep the test surface as clean as possible.
There was a problem hiding this comment.
That in particular won't work since we use waitForTransaction to get the transactionEventId. If it is about the logging statement I can just remove it.
There was a problem hiding this comment.
But we can just log it out once the serverComponentTransactionPromise resolves internally?
Ideally the tests themselves have no logs. And if it does, it's optional debug logging that can be turned on.
|
|
||
| const transaction = startTransaction({ | ||
| op: 'function.nextjs', | ||
| name: `${componentType} Server Component (${componentRoute})`, |
There was a problem hiding this comment.
I wonder if this is the name we should choose for this 🤔
Maybe we try to have the componentRoute at the start?
There was a problem hiding this comment.
I tried to pick something that makes it immediately obvious what the transaction is measuring. Right now it is already super confusing to see API route transactions like GET /api/my/route intertwined with pageload transactions like /my/route.
IMO having the transacitons show up as Page Server Component (/user/[id]) and Layout Server Component (/user/[id]) is very clear.
There was a problem hiding this comment.
Yeah that's fair, makes sense to me.
|
|
||
| const currentScope = getCurrentHub().getScope(); | ||
| if (currentScope) { | ||
| currentScope.setSpan(transaction); |
There was a problem hiding this comment.
I wonder if you throw some React.withProfiler calls around child components if they'll show up attached as spans!
There was a problem hiding this comment.
We could try it out at some point. Right now, Sentry.withProfiler is still littered with browser code which is a big nono in server components.
@AbhiPrasad Nope doesn't work, unfortunately. As soon as we use the More info on static vs dynamic: https://beta.nextjs.org/docs/rendering/static-and-dynamic-rendering |
|
Could we inject in a meta tag for backend -> frontend tracing?
We can monkey patch this away :))) |
AbhiPrasad
left a comment
There was a problem hiding this comment.
Only thing here is if there is a lot of value here is there is not many child spans on the transaction.
The interesting aspect when looking at server component traces is gonna be how long the fetches or db queries inside the component take. Right now we're missing the fetch instrumentation but as soon as that is done the transactions will be super useful. |
With this in mind IMO instrumenting server-side fetch is should be higher prio if we want this to be adopted/used. |
This PR adds performance monitoring to Next.js 13 server components.
The majority of changes is adding E2E tests for this.
Resolves #7176