-
-
Notifications
You must be signed in to change notification settings - Fork 1.6k
/
wrapServerComponentWithSentry.ts
119 lines (109 loc) · 4.71 KB
/
wrapServerComponentWithSentry.ts
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
110
111
112
113
114
115
116
117
118
119
import {
SEMANTIC_ATTRIBUTE_SENTRY_SOURCE,
SPAN_STATUS_ERROR,
SPAN_STATUS_OK,
Scope,
captureException,
getActiveSpan,
getCapturedScopesOnSpan,
getRootSpan,
handleCallbackErrors,
setCapturedScopesOnSpan,
startSpanManual,
withIsolationScope,
withScope,
} from '@sentry/core';
import { propagationContextFromHeaders, uuid4, winterCGHeadersToDict } from '@sentry/utils';
import { SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN } from '@sentry/core';
import { isNotFoundNavigationError, isRedirectNavigationError } from '../common/nextNavigationErrorUtils';
import type { ServerComponentContext } from '../common/types';
import { flushSafelyWithTimeout } from './utils/responseEnd';
import { commonObjectToIsolationScope, commonObjectToPropagationContext } from './utils/tracingUtils';
import { vercelWaitUntil } from './utils/vercelWaitUntil';
/**
* 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 {
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) => {
const requestTraceId = getActiveSpan()?.spanContext().traceId;
const isolationScope = commonObjectToIsolationScope(context.headers);
const activeSpan = getActiveSpan();
if (activeSpan) {
const rootSpan = getRootSpan(activeSpan);
const { scope } = getCapturedScopesOnSpan(rootSpan);
setCapturedScopesOnSpan(rootSpan, scope ?? new Scope(), isolationScope);
// We mark the root span as an app router span so we can allow-list it in our span processor that would normally filter out all Next.js transactions/spans
rootSpan.setAttribute('sentry.rsc', true);
}
const headersDict = context.headers ? winterCGHeadersToDict(context.headers) : undefined;
isolationScope.setSDKProcessingMetadata({
request: {
headers: headersDict,
},
});
return withIsolationScope(isolationScope, () => {
return withScope(scope => {
scope.setTransactionName(`${componentType} Server Component (${componentRoute})`);
if (process.env.NEXT_RUNTIME === 'edge') {
const propagationContext = commonObjectToPropagationContext(
context.headers,
headersDict?.['sentry-trace']
? propagationContextFromHeaders(headersDict['sentry-trace'], headersDict['baggage'])
: {
traceId: requestTraceId || uuid4(),
spanId: uuid4().substring(16),
},
);
scope.setPropagationContext(propagationContext);
}
return startSpanManual(
{
op: 'function.nextjs',
name: `${componentType} Server Component (${componentRoute})`,
attributes: {
[SEMANTIC_ATTRIBUTE_SENTRY_SOURCE]: 'component',
[SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN]: 'auto.function.nextjs',
},
},
span => {
return handleCallbackErrors(
() => originalFunction.apply(thisArg, args),
error => {
// When you read this code you might think: "Wait a minute, shouldn't we set the status on the root span too?"
// The answer is: "No." - The status of the root span is determined by whatever status code Next.js decides to put on the response.
if (isNotFoundNavigationError(error)) {
// We don't want to report "not-found"s
span.setStatus({ code: SPAN_STATUS_ERROR, message: 'not_found' });
} else if (isRedirectNavigationError(error)) {
// We don't want to report redirects
span.setStatus({ code: SPAN_STATUS_OK });
} else {
span.setStatus({ code: SPAN_STATUS_ERROR, message: 'internal_error' });
captureException(error, {
mechanism: {
handled: false,
},
});
}
},
() => {
span.end();
vercelWaitUntil(flushSafelyWithTimeout());
},
);
},
);
});
});
},
});
}