Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(nextjs): Create spans and route parameterization in server-side getInitialProps #5587

Merged
merged 6 commits into from
Aug 17, 2022

Conversation

lforst
Copy link
Member

@lforst lforst commented Aug 16, 2022

Ref: #5505

This PR mainly introduces span creation in the server-side getInitialProps wrapping (except for _app, _document, _error pages) - secondly it does a minor refactor on the generic data fetcher wrapper we have, slightly cleaning up types.

@lforst lforst mentioned this pull request Aug 16, 2022
43 tasks
@github-actions
Copy link
Contributor

github-actions bot commented Aug 16, 2022

size-limit report 📦

Path Size
@sentry/browser - ES5 CDN Bundle (gzipped + minified) 19.38 KB (-0.1% 🔽)
@sentry/browser - ES5 CDN Bundle (minified) 59.99 KB (-0.11% 🔽)
@sentry/browser - ES6 CDN Bundle (gzipped + minified) 17.96 KB (-0.08% 🔽)
@sentry/browser - ES6 CDN Bundle (minified) 52.88 KB (-0.1% 🔽)
@sentry/browser - Webpack (gzipped + minified) 19.73 KB (-0.2% 🔽)
@sentry/browser - Webpack (minified) 64.21 KB (-0.15% 🔽)
@sentry/react - Webpack (gzipped + minified) 19.75 KB (-0.19% 🔽)
@sentry/nextjs Client - Webpack (gzipped + minified) 44.39 KB (-0.7% 🔽)
@sentry/browser + @sentry/tracing - ES5 CDN Bundle (gzipped + minified) 25.89 KB (-0.03% 🔽)
@sentry/browser + @sentry/tracing - ES6 CDN Bundle (gzipped + minified) 24.26 KB (-0.05% 🔽)

@lforst lforst changed the base branch from master to kmclb-nextjs-add-spans-and-routes-in-datafetch-wrappers August 16, 2022 15:32
@lforst lforst force-pushed the lforst-instrument-get-initial-props branch from 2ab7218 to ba8deb5 Compare August 16, 2022 15:39
@lforst lforst requested a review from lobsterkatie August 16, 2022 15:41
@lforst lforst marked this pull request as ready for review August 16, 2022 15:41
@lforst lforst changed the title feat(nextjs): Create spans in serverside getInitialProps feat(nextjs): Create spans and route parameterization in server-side getInitialProps Aug 16, 2022
},
err => {
// TODO: Can we somehow associate the error with the span?
span.finish();
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We can associate the error with the specific span if the span is on the scope.

We should set span status here though.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The span won't be on the scope, as it's one of (eventually) potentially many running simultaneously inside the transaction. But don't we have some function that associates errors to transactions? (I can dig it up later if you don't know what it's called off the top of your head.)

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is how we do error linking in the product:

// We want to set the trace context for normal events only if there isn't already
// a trace context on the event. There is a product feature in place where we link
// errors with transaction and it relies on that.
if (this._span) {
event.contexts = { trace: this._span.getTraceContext(), ...event.contexts };
const transactionName = this._span.transaction && this._span.transaction.name;
if (transactionName) {
event.tags = { transaction: transactionName, ...event.tags };
}
}

packages/nextjs/src/config/loaders/dataFetchersLoader.ts Outdated Show resolved Hide resolved
origGetServerSideProps: GetServerSideProps,
route: string,
): GetServerSideProps {
return function (...getServerSidePropsArguments: Parameters<GetServerSideProps>): ReturnType<GetServerSideProps> {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Next has actual types for these, which is what's embedded in the hacky types objects I was using hoping I could get TS to be smart about the fact that they all went together. Any reason not to import them here, directly from next, and use them?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's just safer. Assume Next.js released that makes getServerSideProps accept an additional argument. The current implementation of the wrapper... breaks. It doesn't pass the new argument. The Parameters is the most elegant way to type this. I wouldn't change this.

Copy link
Member

@lobsterkatie lobsterkatie Aug 16, 2022

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Okay, your call. I do see your point when it comes to the arguments, since you're gathering them into an array, and you're probably right - as stable as this is, who needs another thing to update if nextjs changes their mind and radically rewrites everything (which they have been known to do).

packages/nextjs/src/config/wrappers/wrapperUtils.ts Outdated Show resolved Hide resolved
Comment on lines 40 to 41
// We do the following instead of `await`-ing the return value of `origFunction`, because that would require us to
// make this function async which might in turn create a mismatch of function signatures between the original
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This feels overly complicated to me. Whether the user provides a sync or an async function, all three of the data fetchers are set up to accept a promise:

getInitialProps: https://github.com/vercel/next.js/blob/de7aa2d6e486c40b8be95a1327639cbed75a8782/packages/next/shared/lib/utils.ts#L20

getStaticProps: https://github.com/vercel/next.js/blob/de7aa2d6e486c40b8be95a1327639cbed75a8782/packages/next/types/index.d.ts#L117-L123

getServerSideProps: https://github.com/vercel/next.js/blob/de7aa2d6e486c40b8be95a1327639cbed75a8782/packages/next/types/index.d.ts#L170-L176

So I think it's fine that we're always creating an async function. To handle the case of errors, we can just use a try-catch-finally. (It's a good thought - I'll update my PR to include it also.)

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't think this is overly complicated. If we have the opportunity not to change the function signature, why would we change it? It's not about what the Framework accepts, it's about not changing what the user provides. I don't really see the value behind adjusting this.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nextjs is going to have to await it, regardless, just in case it's async, though.

To me, the question isn't why would we change it but why shouldn't we change it, if it makes no difference to nextjs and it allows us to us to simplify our code? Regardless of whether the alternative is "overly" complicated, it is certainly more complicated than simply doing await original(...).

Having the simpler version is good for its own sake, but it will also make error handling simpler, because it'll allow us to just use try-catch, and it brings us in line with what withSentry already does. (Eventually, I'd like to meld that into this mix also.)

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Alright. Changed in f45ceb6

@lforst lforst requested a review from lobsterkatie August 16, 2022 18:37
export async function callDataFetcherTraced<F extends (...args: any[]) => Promise<any> | any>(
origFunction: F,
origFunctionArgs: Parameters<F>,
options: {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

what do you think about making this the SpanContext instead of options? we would make route -> description.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is blocked on #5564. I have to see what we're gonna do with op and description there before I make any more changes here.

Honestly, I wouldn't read too much into this PR. We're likely gonna scrap this wrapper anyways because it's too generic to be used across different data fetching methods.

@lobsterkatie lobsterkatie force-pushed the kmclb-nextjs-add-spans-and-routes-in-datafetch-wrappers branch 3 times, most recently from 01300bd to 84cd837 Compare August 16, 2022 22:45
Base automatically changed from kmclb-nextjs-add-spans-and-routes-in-datafetch-wrappers to master August 16, 2022 23:00
@lforst lforst force-pushed the lforst-instrument-get-initial-props branch from f45ceb6 to d996be7 Compare August 17, 2022 07:06
@lforst lforst requested a review from Lms24 August 17, 2022 07:38
Copy link
Member

@Lms24 Lms24 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This LGTM. Let's get this in to unblock the release. As you wrote, this will probably change again in the future, so we can revisit stuff then.

@@ -111,6 +111,21 @@ export default function wrapDataFetchersLoader(this: LoaderThis<LoaderOptions>,
// We know one or the other will be defined, depending on the version of webpack being used
const { projectDir, pagesDir } = 'getOptions' in this ? this.getOptions() : this.query;

// Get the parameterized route name from this page's filepath
const parameterizedRouteName = path
Copy link
Member

@Lms24 Lms24 Aug 17, 2022

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nice chaining approach! I like it! (I wish it'd work as nicely in all our SDKs)
(EDIT, I'm referring to the lines below 115, I just botched the line selection in the review :D)

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Props go to Katie.

Comment on lines +29 to +30
// Logic will be: If there is no active transaction, start one with correct name and source. If there is an active
// transaction, create a child span with correct name and source.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Just for me to get this right: If we already have a transaction, this means that we started it beforehand in a previous data fetcher function (iirc you said that getInitialProps can e.g. be executed before getServerSideProps), right?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Right now it's started in instrumentServer.ts - but actually, that's not working so there never is a transaction. Soooo.... this PR currently doesn't do anything lol.

Copy link
Member

@lobsterkatie lobsterkatie Aug 17, 2022

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah, something is weird, because there IS in fact a transaction in our integration tests, otherwise they wouldn't pass. And there's a transaction if you run the server inside of the debugger. But seemingly not if you just run the server straight. It's very mysterious.

@lforst lforst merged commit 2f6deeb into master Aug 17, 2022
@lforst lforst deleted the lforst-instrument-get-initial-props branch August 17, 2022 10:21
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

4 participants