-
-
Notifications
You must be signed in to change notification settings - Fork 1.8k
Expand file tree
/
Copy pathindex.ts
More file actions
222 lines (186 loc) · 8.21 KB
/
index.ts
File metadata and controls
222 lines (186 loc) · 8.21 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
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
// import/export got a false positive, and affects most of our index barrel files
// can be removed once following issue is fixed: https://github.com/import-js/eslint-plugin-import/issues/703
/* eslint-disable import/export */
import { context, createContextKey } from '@opentelemetry/api';
import {
applySdkMetadata,
type EventProcessor,
getCapturedScopesOnSpan,
getCurrentScope,
getGlobalScope,
getIsolationScope,
getRootSpan,
GLOBAL_OBJ,
registerSpanErrorInstrumentation,
type Scope,
SEMANTIC_ATTRIBUTE_SENTRY_OP,
SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN,
SEMANTIC_ATTRIBUTE_SENTRY_SOURCE,
setCapturedScopesOnSpan,
spanToJSON,
stripUrlQueryAndFragment,
} from '@sentry/core';
import type { VercelEdgeOptions } from '@sentry/vercel-edge';
import { getDefaultIntegrations, init as vercelEdgeInit } from '@sentry/vercel-edge';
import { DEBUG_BUILD } from '../common/debug-build';
import { ATTR_NEXT_SPAN_TYPE } from '../common/nextSpanAttributes';
import { TRANSACTION_ATTR_SHOULD_DROP_TRANSACTION } from '../common/span-attributes-with-logic-attached';
import { addHeadersAsAttributes } from '../common/utils/addHeadersAsAttributes';
import { dropMiddlewareTunnelRequests } from '../common/utils/dropMiddlewareTunnelRequests';
import { isBuild } from '../common/utils/isBuild';
import { flushSafelyWithTimeout, waitUntil } from '../common/utils/responseEnd';
import { setUrlProcessingMetadata } from '../common/utils/setUrlProcessingMetadata';
import { distDirRewriteFramesIntegration } from './distDirRewriteFramesIntegration';
export * from '@sentry/vercel-edge';
export * from '../common';
export { captureUnderscoreErrorException } from '../common/pages-router-instrumentation/_error';
// Override core span methods with Next.js-specific implementations that support Cache Components
export { startSpan, startSpanManual, startInactiveSpan } from '../common/utils/nextSpan';
export { wrapApiHandlerWithSentry } from './wrapApiHandlerWithSentry';
export type EdgeOptions = VercelEdgeOptions;
type CurrentScopes = {
scope: Scope;
isolationScope: Scope;
};
// This key must match `@sentry/opentelemetry`'s `SENTRY_SCOPES_CONTEXT_KEY`.
// We duplicate it here so the Edge bundle does not need to import the full `@sentry/opentelemetry` package.
const SENTRY_SCOPES_CONTEXT_KEY = createContextKey('sentry_scopes');
type ContextWithGetValue = {
getValue(key: unknown): unknown;
};
function getScopesFromContext(otelContext: unknown): CurrentScopes | undefined {
if (!otelContext || typeof otelContext !== 'object') {
return undefined;
}
const maybeContext = otelContext as Partial<ContextWithGetValue>;
if (typeof maybeContext.getValue !== 'function') {
return undefined;
}
return maybeContext.getValue(SENTRY_SCOPES_CONTEXT_KEY) as CurrentScopes | undefined;
}
const globalWithInjectedValues = GLOBAL_OBJ as typeof GLOBAL_OBJ & {
_sentryRewriteFramesDistDir?: string;
_sentryRelease?: string;
_sentryRewritesTunnelPath?: string;
};
/** Inits the Sentry NextJS SDK on the Edge Runtime. */
export function init(options: VercelEdgeOptions = {}): void {
registerSpanErrorInstrumentation();
if (isBuild()) {
return;
}
if (!DEBUG_BUILD && options.debug) {
// eslint-disable-next-line no-console
console.warn(
'[@sentry/nextjs] You have enabled `debug: true`, but Sentry debug logging was removed from your bundle (likely via `withSentryConfig({ disableLogger: true })` / `webpack.treeshake.removeDebugLogging: true`). Set that option to `false` to see Sentry debug output.',
);
}
const customDefaultIntegrations = getDefaultIntegrations(options);
// This value is injected at build time, based on the output directory specified in the build config. Though a default
// is set there, we set it here as well, just in case something has gone wrong with the injection.
const distDirName = process.env._sentryRewriteFramesDistDir || globalWithInjectedValues._sentryRewriteFramesDistDir;
if (distDirName) {
customDefaultIntegrations.push(distDirRewriteFramesIntegration({ distDirName }));
}
const opts = {
defaultIntegrations: customDefaultIntegrations,
release: process.env._sentryRelease || globalWithInjectedValues._sentryRelease,
...options,
};
applySdkMetadata(opts, 'nextjs', ['nextjs', 'vercel-edge']);
const client = vercelEdgeInit(opts);
client?.on('spanStart', span => {
const spanAttributes = spanToJSON(span).data;
const rootSpan = getRootSpan(span);
const isRootSpan = span === rootSpan;
dropMiddlewareTunnelRequests(span, spanAttributes);
// Mark all spans generated by Next.js as 'auto'
if (spanAttributes?.['next.span_type'] !== undefined) {
span.setAttribute(SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN, 'auto');
}
// Make sure middleware spans get the right op
if (spanAttributes?.['next.span_type'] === 'Middleware.execute') {
span.setAttribute(SEMANTIC_ATTRIBUTE_SENTRY_OP, 'http.server.middleware');
span.setAttribute(SEMANTIC_ATTRIBUTE_SENTRY_SOURCE, 'url');
}
// We want to fork the isolation scope for incoming requests
if (spanAttributes?.[ATTR_NEXT_SPAN_TYPE] === 'BaseServer.handleRequest' && isRootSpan) {
const scopes = getCapturedScopesOnSpan(span);
const isolationScope = (scopes.isolationScope || getIsolationScope()).clone();
const scope = scopes.scope || getCurrentScope();
const currentScopesPointer = getScopesFromContext(context.active());
if (currentScopesPointer) {
currentScopesPointer.isolationScope = isolationScope;
}
setCapturedScopesOnSpan(span, scope, isolationScope);
}
if (isRootSpan) {
// todo: check if we can set request headers for edge on sdkProcessingMetadata
const headers = getIsolationScope().getScopeData().sdkProcessingMetadata?.normalizedRequest?.headers;
addHeadersAsAttributes(headers, rootSpan);
}
});
// Use the preprocessEvent hook instead of an event processor, so that the users event processors receive the most
// up-to-date value, but also so that the logic that detects changes to the transaction names to set the source to
// "custom", doesn't trigger.
client?.on('preprocessEvent', event => {
// The otel auto inference will clobber the transaction name because the span has an http.target
if (
event.type === 'transaction' &&
event.contexts?.trace?.data?.['next.span_type'] === 'Middleware.execute' &&
event.contexts?.trace?.data?.['next.span_name']
) {
if (event.transaction) {
// Older nextjs versions pass the full url appended to the middleware name, which results in high cardinality transaction names.
// We want to remove the url from the name here.
const spanName = event.contexts.trace.data['next.span_name'];
if (typeof spanName === 'string') {
const match = spanName.match(/^middleware (GET|POST|PUT|DELETE|PATCH|HEAD|OPTIONS)/);
if (match) {
const normalizedName = `middleware ${match[1]}`;
event.transaction = normalizedName;
} else {
event.transaction = stripUrlQueryAndFragment(event.contexts.trace.data['next.span_name']);
}
}
}
}
setUrlProcessingMetadata(event);
});
client?.on('spanEnd', span => {
if (span === getRootSpan(span)) {
waitUntil(flushSafelyWithTimeout());
}
});
getGlobalScope().addEventProcessor(
Object.assign(
(event => {
// Filter transactions that we explicitly want to drop.
if (event.type === 'transaction') {
if (event.contexts?.trace?.data?.[TRANSACTION_ATTR_SHOULD_DROP_TRANSACTION]) {
return null;
}
return event;
} else {
return event;
}
}) satisfies EventProcessor,
{ id: 'NextLowQualityTransactionsFilter' },
),
);
try {
// @ts-expect-error `process.turbopack` is a magic string that will be replaced by Next.js
if (process.turbopack) {
getGlobalScope().setTag('turbopack', true);
}
} catch {
// Noop
// The statement above can throw because process is not defined on the client
}
}
/**
* Just a passthrough in case this is imported from the client.
*/
export function withSentryConfig<T>(exportedUserNextConfig: T): T {
return exportedUserNextConfig;
}