-
-
Notifications
You must be signed in to change notification settings - Fork 1.6k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat(nextjs): Create spans in serverside
getInitialProps
- Loading branch information
Showing
7 changed files
with
123 additions
and
99 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file was deleted.
Oops, something went wrong.
14 changes: 9 additions & 5 deletions
14
packages/nextjs/src/config/wrappers/withSentryGetInitialProps.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,14 +1,18 @@ | ||
import { GIProps } from './types'; | ||
import { NextPage } from 'next'; | ||
|
||
import { callDataFetcherTraced } from './wrapperUtils'; | ||
|
||
type GetInitialProps = Required<NextPage<unknown>>['getInitialProps']; | ||
|
||
/** | ||
* Create a wrapped version of the user's exported `getInitialProps` function | ||
* | ||
* @param origGIProps: The user's `getInitialProps` function | ||
* @param origGetInitialProps: The user's `getInitialProps` function | ||
* @param origGIPropsHost: The user's object on which `getInitialProps` lives (used for `this`) | ||
* @returns A wrapped version of the function | ||
*/ | ||
export function withSentryGetInitialProps(origGIProps: GIProps['fn']): GIProps['wrappedFn'] { | ||
return async function (this: unknown, ...args: Parameters<GIProps['fn']>) { | ||
return await origGIProps.call(this, ...args); | ||
export function withSentryGetInitialProps(origGetInitialProps: GetInitialProps, route: string): GetInitialProps { | ||
return function (...getInitialPropsArguments: Parameters<GetInitialProps>): ReturnType<GetInitialProps> { | ||
return callDataFetcherTraced(origGetInitialProps, getInitialPropsArguments, { route, op: 'getInitialProps' }); | ||
}; | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,46 +1,58 @@ | ||
import { getActiveTransaction } from '@sentry/tracing'; | ||
|
||
import { DataFetchingFunction } from './types'; | ||
|
||
/** | ||
* Create a span to track the wrapped function and update transaction name with parameterized route. | ||
* Call a data fetcher and trace it. Only traces the function if there is an active transaction on the scope. | ||
* | ||
* @template T Types for `getInitialProps`, `getStaticProps`, and `getServerSideProps` | ||
* @param origFunction The user's exported `getInitialProps`, `getStaticProps`, or `getServerSideProps` function | ||
* @param context The context object passed by nextjs to the function | ||
* @returns The result of calling the user's function | ||
* We only do the following until we move transaction creation into this function: When called, the wrapped function | ||
* will also update the name of the active transaction with a parameterized route provided via the `options` argument. | ||
*/ | ||
export async function wrapperCore<T extends DataFetchingFunction>(options: { | ||
origFunction: T['fn']; | ||
context: T['context']; | ||
route: string; | ||
op: string; | ||
}): Promise<T['result']> { | ||
const { origFunction, context, route, op } = options; | ||
export function callDataFetcherTraced<F extends (...args: any[]) => Promise<any> | any>( | ||
origFunction: F, | ||
origFunctionArgs: Parameters<F>, | ||
options: { | ||
route: string; | ||
op: string; | ||
}, | ||
): ReturnType<F> { | ||
const { route, op } = options; | ||
|
||
const transaction = getActiveTransaction(); | ||
|
||
if (transaction) { | ||
// TODO: Make sure that the given route matches the name of the active transaction (to prevent background data | ||
// fetching from switching the name to a completely other route) | ||
transaction.name = route; | ||
transaction.metadata.source = 'route'; | ||
|
||
// Capture the route, since pre-loading, revalidation, etc might mean that this span may happen during another | ||
// route's transaction | ||
const span = transaction.startChild({ op, data: { route } }); | ||
|
||
// TODO: Can't figure out how to tell TS that the types are correlated - that a `GSPropsFunction` will only get passed | ||
// `GSPropsContext` and never, say, `GSSPContext`. That's what wrapping everything in objects and using the generic | ||
// and pulling the types from the generic rather than specifying them directly was supposed to do, but... no luck. | ||
// eslint-disable-next-line prefer-const, @typescript-eslint/no-explicit-any | ||
const props = await (origFunction as any)(context); | ||
|
||
span.finish(); | ||
|
||
return props; | ||
if (!transaction) { | ||
return origFunction(...origFunctionArgs); | ||
} | ||
|
||
// eslint-disable-next-line @typescript-eslint/no-explicit-any | ||
return (origFunction as any)(context); | ||
// TODO: Make sure that the given route matches the name of the active transaction (to prevent background data | ||
// fetching from switching the name to a completely other route) -- We'll probably switch to creating a transaction | ||
// right here so making that check will probabably not even be necessary. | ||
// 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. | ||
// We will probably need to put | ||
transaction.name = route; | ||
transaction.metadata.source = 'route'; | ||
|
||
// Capture the route, since pre-loading, revalidation, etc might mean that this span may happen during another | ||
// route's transaction | ||
const span = transaction.startChild({ op, data: { route } }); | ||
|
||
const result = origFunction(...origFunctionArgs); | ||
|
||
// 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 | ||
// function and the wrapped one. | ||
// This wraps `result`, which is potentially a Promise, into a Promise. | ||
// If `result` is a non-Promise, the callback of `then` is immediately called and the span is finished. | ||
// If `result` is a Promise, the callback of `then` is only called when `result` resolves | ||
void Promise.resolve(result).then( | ||
() => { | ||
span.finish(); | ||
}, | ||
err => { | ||
// TODO: Can we somehow associate the error with the span? | ||
span.finish(); | ||
throw err; | ||
}, | ||
); | ||
|
||
return result; | ||
} |