|
| 1 | +import { captureException } from '@sentry/core'; |
| 2 | +import { getActiveTransaction } from '@sentry/tracing'; |
| 3 | +import { Span } from '@sentry/types'; |
| 4 | + |
1 | 5 | import { DataFetchingFunction } from './types';
|
2 | 6 |
|
3 | 7 | /**
|
4 |
| - * Pass-through wrapper for the original function, used as a first step in eventually wrapping the data-fetching |
5 |
| - * functions with code for tracing. |
| 8 | + * Create a span to track the wrapped function and update transaction name with parameterized route. |
6 | 9 | *
|
7 | 10 | * @template T Types for `getInitialProps`, `getStaticProps`, and `getServerSideProps`
|
8 | 11 | * @param origFunction The user's exported `getInitialProps`, `getStaticProps`, or `getServerSideProps` function
|
9 | 12 | * @param context The context object passed by nextjs to the function
|
| 13 | + * @param route The route currently being served |
10 | 14 | * @returns The result of calling the user's function
|
11 | 15 | */
|
12 |
| -export async function callOriginal<T extends DataFetchingFunction>( |
| 16 | +export async function wrapperCore<T extends DataFetchingFunction>( |
13 | 17 | origFunction: T['fn'],
|
14 | 18 | context: T['context'],
|
| 19 | + route: string, |
15 | 20 | ): Promise<T['result']> {
|
16 |
| - let props; |
| 21 | + const transaction = getActiveTransaction(); |
| 22 | + |
| 23 | + if (transaction) { |
| 24 | + // Pull off any leading underscores we've added in the process of wrapping the function |
| 25 | + const wrappedFunctionName = origFunction.name.replace(/^_*/, ''); |
| 26 | + |
| 27 | + // TODO: Make sure that the given route matches the name of the active transaction (to prevent background data |
| 28 | + // fetching from switching the name to a completely other route) |
| 29 | + transaction.name = route; |
| 30 | + transaction.metadata.source = 'route'; |
| 31 | + |
| 32 | + // Capture the route, since pre-loading, revalidation, etc might mean that this span may happen during another |
| 33 | + // route's transaction |
| 34 | + const span = transaction.startChild({ op: 'nextjs.data', description: `${wrappedFunctionName} (${route})` }); |
| 35 | + |
| 36 | + const props = await callOriginal(origFunction, context, span); |
| 37 | + |
| 38 | + span.finish(); |
17 | 39 |
|
18 |
| - // TODO: Can't figure out how to tell TS that the types are correlated - that a `GSPropsFunction` will only get passed |
19 |
| - // `GSPropsContext` and never, say, `GSSPContext`. That's what wrapping everything in objects and using the generic |
20 |
| - // and pulling the types from the generic rather than specifying them directly was supposed to do, but... no luck. |
21 |
| - // eslint-disable-next-line prefer-const, @typescript-eslint/no-explicit-any |
22 |
| - props = await (origFunction as any)(context); |
| 40 | + return props; |
| 41 | + } |
| 42 | + |
| 43 | + // eslint-disable-next-line @typescript-eslint/no-explicit-any |
| 44 | + return callOriginal(origFunction, context); |
| 45 | +} |
| 46 | + |
| 47 | +/** Call the original function, capturing any errors and finishing the span (if any) in case of error */ |
| 48 | +async function callOriginal<T extends DataFetchingFunction>( |
| 49 | + origFunction: T['fn'], |
| 50 | + context: T['context'], |
| 51 | + span?: Span, |
| 52 | +): Promise<T['result']> { |
| 53 | + try { |
| 54 | + // eslint-disable-next-line prefer-const, @typescript-eslint/no-explicit-any |
| 55 | + return (origFunction as any)(context); |
| 56 | + } catch (err) { |
| 57 | + if (span) { |
| 58 | + span.finish(); |
| 59 | + } |
23 | 60 |
|
24 |
| - return props; |
| 61 | + // TODO Copy more robust error handling over from `withSentry` |
| 62 | + captureException(err); |
| 63 | + throw err; |
| 64 | + } |
25 | 65 | }
|
0 commit comments