-
-
Notifications
You must be signed in to change notification settings - Fork 1.8k
Expand file tree
/
Copy pathwrapServerComponentWithSentry.ts
More file actions
109 lines (94 loc) · 3.54 KB
/
Copy pathwrapServerComponentWithSentry.ts
File metadata and controls
109 lines (94 loc) · 3.54 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
import {
addTracingExtensions,
captureException,
getCurrentHub,
runWithAsyncContext,
startTransaction,
} from '@sentry/core';
import { addExceptionMechanism, tracingContextFromHeaders } from '@sentry/utils';
import { isNotFoundNavigationError, isRedirectNavigationError } from '../common/nextNavigationErrorUtils';
import type { ServerComponentContext } from '../common/types';
/**
* Wraps an `app` directory server component with Sentry error instrumentation.
*/
// eslint-disable-next-line @typescript-eslint/no-explicit-any
export function wrapServerComponentWithSentry<F extends (...args: any[]) => any>(
appDirComponent: F,
context: ServerComponentContext,
): F {
addTracingExtensions();
const { componentRoute, componentType } = context;
// Even though users may define server components as async functions, for the client bundles
// Next.js will turn them into synchronous functions and it will transform any `await`s into instances of the `use`
// hook. 🤯
return new Proxy(appDirComponent, {
apply: (originalFunction, thisArg, args) => {
return runWithAsyncContext(() => {
const hub = getCurrentHub();
const currentScope = hub.getScope();
let maybePromiseResult;
const { traceparentData, dynamicSamplingContext, propagationContext } = tracingContextFromHeaders(
context.sentryTraceHeader,
context.baggageHeader,
);
currentScope.setPropagationContext(propagationContext);
const transaction = startTransaction({
op: 'function.nextjs',
name: `${componentType} Server Component (${componentRoute})`,
status: 'ok',
...traceparentData,
metadata: {
source: 'component',
dynamicSamplingContext: traceparentData && !dynamicSamplingContext ? {} : dynamicSamplingContext,
},
});
if (currentScope) {
currentScope.setSpan(transaction);
}
const handleErrorCase = (e: unknown): void => {
if (isNotFoundNavigationError(e)) {
// We don't want to report "not-found"s
transaction.setStatus('not_found');
} else if (isRedirectNavigationError(e)) {
// We don't want to report redirects
} else {
transaction.setStatus('internal_error');
captureException(e, scope => {
scope.addEventProcessor(event => {
addExceptionMechanism(event, {
handled: false,
});
return event;
});
return scope;
});
}
transaction.finish();
};
try {
maybePromiseResult = originalFunction.apply(thisArg, args);
} catch (e) {
handleErrorCase(e);
throw e;
}
if (typeof maybePromiseResult === 'object' && maybePromiseResult !== null && 'then' in maybePromiseResult) {
// eslint-disable-next-line @typescript-eslint/no-unsafe-member-access
Promise.resolve(maybePromiseResult).then(
() => {
transaction.finish();
},
e => {
handleErrorCase(e);
},
);
// It is very important that we return the original promise here, because Next.js attaches various properties
// to that promise and will throw if they are not on the returned value.
return maybePromiseResult;
} else {
transaction.finish();
return maybePromiseResult;
}
});
},
});
}