-
-
Notifications
You must be signed in to change notification settings - Fork 1.8k
Expand file tree
/
Copy pathindex.ts
More file actions
207 lines (176 loc) · 8.01 KB
/
index.ts
File metadata and controls
207 lines (176 loc) · 8.01 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
// 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 } from '@opentelemetry/api';
import {
applySdkMetadata,
type EventProcessor,
getCapturedScopesOnSpan,
getCurrentScope,
getGlobalScope,
getIsolationScope,
getRootSpan,
GLOBAL_OBJ,
registerSpanErrorInstrumentation,
SEMANTIC_ATTRIBUTE_SENTRY_OP,
SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN,
SEMANTIC_ATTRIBUTE_SENTRY_SOURCE,
setCapturedScopesOnSpan,
spanToJSON,
stripUrlQueryAndFragment,
} from '@sentry/core';
import { getScopesFromContext } from '@sentry/opentelemetry';
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, isCloudflareWaitUntilAvailable, 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;
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 }));
}
// Detect if running on OpenNext/Cloudflare
const isRunningOnCloudflare = isCloudflareWaitUntilAvailable();
const opts: VercelEdgeOptions = {
defaultIntegrations: customDefaultIntegrations,
environment: options.environment || process.env.SENTRY_ENVIRONMENT,
release: process.env._sentryRelease || globalWithInjectedValues._sentryRelease,
...options,
// Override runtime to 'cloudflare' when running on OpenNext/Cloudflare
...(isRunningOnCloudflare && { runtime: { name: 'cloudflare' } }),
};
// Use appropriate SDK metadata based on the runtime environment
if (isRunningOnCloudflare) {
applySdkMetadata(opts, 'nextjs', ['nextjs', 'cloudflare']);
} else {
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;
}