@sentry/nextjs
Version:
Official Sentry SDK for Next.js
404 lines (345 loc) • 18.7 kB
JavaScript
Object.defineProperty(exports, Symbol.toStringTag, { value: 'Module' });
const api = require('@opentelemetry/api');
const semanticConventions = require('@opentelemetry/semantic-conventions');
const core = require('@sentry/core');
const node = require('@sentry/node');
const opentelemetry = require('@sentry/opentelemetry');
const debugBuild = require('../common/debug-build.js');
const devErrorSymbolicationEventProcessor = require('../common/devErrorSymbolicationEventProcessor.js');
const getVercelEnv = require('../common/getVercelEnv.js');
const spanAttributesWithLogicAttached = require('../common/span-attributes-with-logic-attached.js');
const isBuild = require('../common/utils/isBuild.js');
const distDirRewriteFramesIntegration = require('./distDirRewriteFramesIntegration.js');
const _error = require('../common/pages-router-instrumentation/_error.js');
const wrapGetStaticPropsWithSentry = require('../common/pages-router-instrumentation/wrapGetStaticPropsWithSentry.js');
const wrapGetInitialPropsWithSentry = require('../common/pages-router-instrumentation/wrapGetInitialPropsWithSentry.js');
const wrapAppGetInitialPropsWithSentry = require('../common/pages-router-instrumentation/wrapAppGetInitialPropsWithSentry.js');
const wrapDocumentGetInitialPropsWithSentry = require('../common/pages-router-instrumentation/wrapDocumentGetInitialPropsWithSentry.js');
const wrapErrorGetInitialPropsWithSentry = require('../common/pages-router-instrumentation/wrapErrorGetInitialPropsWithSentry.js');
const wrapGetServerSidePropsWithSentry = require('../common/pages-router-instrumentation/wrapGetServerSidePropsWithSentry.js');
const wrapServerComponentWithSentry = require('../common/wrapServerComponentWithSentry.js');
const wrapRouteHandlerWithSentry = require('../common/wrapRouteHandlerWithSentry.js');
const wrapApiHandlerWithSentryVercelCrons = require('../common/pages-router-instrumentation/wrapApiHandlerWithSentryVercelCrons.js');
const wrapMiddlewareWithSentry = require('../common/wrapMiddlewareWithSentry.js');
const wrapPageComponentWithSentry = require('../common/pages-router-instrumentation/wrapPageComponentWithSentry.js');
const wrapGenerationFunctionWithSentry = require('../common/wrapGenerationFunctionWithSentry.js');
const withServerActionInstrumentation = require('../common/withServerActionInstrumentation.js');
const captureRequestError = require('../common/captureRequestError.js');
const wrapApiHandlerWithSentry = require('../common/pages-router-instrumentation/wrapApiHandlerWithSentry.js');
const globalWithInjectedValues = core.GLOBAL_OBJ
;
/**
* A passthrough error boundary for the server that doesn't depend on any react. Error boundaries don't catch SSR errors
* so they should simply be a passthrough.
*/
const ErrorBoundary = (props) => {
if (!props.children) {
return null;
}
if (typeof props.children === 'function') {
return (props.children )();
}
// since Next.js >= 10 requires React ^16.6.0 we are allowed to return children like this here
return props.children ;
};
/**
* A passthrough redux enhancer for the server that doesn't depend on anything from the `@sentry/react` package.
*/
function createReduxEnhancer() {
return (createStore) => createStore;
}
/**
* A passthrough error boundary wrapper for the server that doesn't depend on any react. Error boundaries don't catch
* SSR errors so they should simply be a passthrough.
*/
// eslint-disable-next-line @typescript-eslint/no-explicit-any
function withErrorBoundary(
WrappedComponent,
) {
return WrappedComponent ;
}
/**
* Just a passthrough since we're on the server and showing the report dialog on the server doesn't make any sense.
*/
function showReportDialog() {
return;
}
/** Inits the Sentry NextJS SDK on node. */
function init(options) {
if (isBuild.isBuild()) {
return;
}
const customDefaultIntegrations = node.getDefaultIntegrations(options)
.filter(integration => integration.name !== 'Http')
.concat(
// We are using the HTTP integration without instrumenting incoming HTTP requests because Next.js does that by itself.
node.httpIntegration({
disableIncomingRequestSpans: true,
}),
);
// Turn off Next.js' own fetch instrumentation
// https://github.com/lforst/nextjs-fork/blob/1994fd186defda77ad971c36dc3163db263c993f/packages/next/src/server/lib/patch-fetch.ts#L245
process.env.NEXT_OTEL_FETCH_DISABLED = '1';
// 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.distDirRewriteFramesIntegration({ distDirName }));
}
const opts = {
environment: process.env.SENTRY_ENVIRONMENT || getVercelEnv.getVercelEnv(false) || process.env.NODE_ENV,
release: process.env._sentryRelease || globalWithInjectedValues._sentryRelease,
defaultIntegrations: customDefaultIntegrations,
...options,
};
if (debugBuild.DEBUG_BUILD && opts.debug) {
core.logger.enable();
}
debugBuild.DEBUG_BUILD && core.logger.log('Initializing SDK...');
if (sdkAlreadyInitialized()) {
debugBuild.DEBUG_BUILD && core.logger.log('SDK already initialized');
return;
}
core.applySdkMetadata(opts, 'nextjs', ['nextjs', 'node']);
const client = node.init(opts);
client?.on('beforeSampling', ({ spanAttributes }, samplingDecision) => {
// There are situations where the Next.js Node.js server forwards requests for the Edge Runtime server (e.g. in
// middleware) and this causes spans for Sentry ingest requests to be created. These are not exempt from our tracing
// because we didn't get the chance to do `suppressTracing`, since this happens outside of userland.
// We need to drop these spans.
if (
// eslint-disable-next-line deprecation/deprecation
(typeof spanAttributes[semanticConventions.SEMATTRS_HTTP_TARGET] === 'string' &&
// eslint-disable-next-line deprecation/deprecation
spanAttributes[semanticConventions.SEMATTRS_HTTP_TARGET].includes('sentry_key') &&
// eslint-disable-next-line deprecation/deprecation
spanAttributes[semanticConventions.SEMATTRS_HTTP_TARGET].includes('sentry_client')) ||
(typeof spanAttributes[semanticConventions.ATTR_URL_QUERY] === 'string' &&
spanAttributes[semanticConventions.ATTR_URL_QUERY].includes('sentry_key') &&
spanAttributes[semanticConventions.ATTR_URL_QUERY].includes('sentry_client'))
) {
samplingDecision.decision = false;
}
});
client?.on('spanStart', span => {
const spanAttributes = core.spanToJSON(span).data;
// What we do in this glorious piece of code, is hoist any information about parameterized routes from spans emitted
// by Next.js via the `next.route` attribute, up to the transaction by setting the http.route attribute.
if (typeof spanAttributes?.['next.route'] === 'string') {
const rootSpan = core.getRootSpan(span);
const rootSpanAttributes = core.spanToJSON(rootSpan).data;
// Only hoist the http.route attribute if the transaction doesn't already have it
if (
// eslint-disable-next-line deprecation/deprecation
(rootSpanAttributes?.[semanticConventions.ATTR_HTTP_REQUEST_METHOD] || rootSpanAttributes?.[semanticConventions.SEMATTRS_HTTP_METHOD]) &&
!rootSpanAttributes?.[semanticConventions.ATTR_HTTP_ROUTE]
) {
const route = spanAttributes['next.route'].replace(/\/route$/, '');
rootSpan.updateName(route);
rootSpan.setAttribute(semanticConventions.ATTR_HTTP_ROUTE, route);
// Preserving the original attribute despite internally not depending on it
rootSpan.setAttribute('next.route', route);
}
}
// We want to skip span data inference for any spans generated by Next.js. Reason being that Next.js emits spans
// with patterns (e.g. http.server spans) that will produce confusing data.
if (spanAttributes?.['next.span_type'] !== undefined) {
span.setAttribute(core.SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN, 'auto');
}
// We want to fork the isolation scope for incoming requests
if (spanAttributes?.['next.span_type'] === 'BaseServer.handleRequest' && span === core.getRootSpan(span)) {
const scopes = core.getCapturedScopesOnSpan(span);
const isolationScope = (scopes.isolationScope || core.getIsolationScope()).clone();
const scope = scopes.scope || core.getCurrentScope();
const currentScopesPointer = opentelemetry.getScopesFromContext(api.context.active());
if (currentScopesPointer) {
currentScopesPointer.isolationScope = isolationScope;
}
core.setCapturedScopesOnSpan(span, scope, isolationScope);
}
});
core.getGlobalScope().addEventProcessor(
Object.assign(
(event => {
if (event.type === 'transaction') {
// Filter out transactions for static assets
// This regex matches the default path to the static assets (`_next/static`) and could potentially filter out too many transactions.
// We match `/_next/static/` anywhere in the transaction name because its location may change with the basePath setting.
if (event.transaction?.match(/^GET (\/.*)?\/_next\/static\//)) {
return null;
}
// Filter out transactions for requests to the tunnel route
if (
(globalWithInjectedValues._sentryRewritesTunnelPath &&
event.transaction === `POST ${globalWithInjectedValues._sentryRewritesTunnelPath}`) ||
(process.env._sentryRewritesTunnelPath &&
event.transaction === `POST ${process.env._sentryRewritesTunnelPath}`)
) {
return null;
}
// Filter out requests to resolve source maps for stack frames in dev mode
if (event.transaction?.match(/\/__nextjs_original-stack-frame/)) {
return null;
}
// Filter out /404 transactions which seem to be created excessively
if (
// Pages router
event.transaction === '/404' ||
// App router (could be "GET /404", "POST /404", ...)
event.transaction?.match(/^(GET|HEAD|POST|PUT|DELETE|CONNECT|OPTIONS|TRACE|PATCH) \/(404|_not-found)$/)
) {
return null;
}
// Filter transactions that we explicitly want to drop.
if (event.contexts?.trace?.data?.[spanAttributesWithLogicAttached.TRANSACTION_ATTR_SHOULD_DROP_TRANSACTION]) {
return null;
}
// Next.js 13 sometimes names the root transactions like this containing useless tracing.
if (event.transaction === 'NextServer.getRequestHandler') {
return null;
}
// Next.js 13 is not correctly picking up tracing data for trace propagation so we use a back-fill strategy
if (typeof event.contexts?.trace?.data?.[spanAttributesWithLogicAttached.TRANSACTION_ATTR_SENTRY_TRACE_BACKFILL] === 'string') {
const traceparentData = core.extractTraceparentData(
event.contexts.trace.data[spanAttributesWithLogicAttached.TRANSACTION_ATTR_SENTRY_TRACE_BACKFILL],
);
if (traceparentData?.parentSampled === false) {
return null;
}
}
return event;
} else {
return event;
}
}) ,
{ id: 'NextLowQualityTransactionsFilter' },
),
);
core.getGlobalScope().addEventProcessor(
Object.assign(
((event, hint) => {
if (event.type !== undefined) {
return event;
}
const originalException = hint.originalException;
const isPostponeError =
typeof originalException === 'object' &&
originalException !== null &&
'$$typeof' in originalException &&
originalException.$$typeof === Symbol.for('react.postpone');
if (isPostponeError) {
// Postpone errors are used for partial-pre-rendering (PPR)
return null;
}
// We don't want to capture suspense errors as they are simply used by React/Next.js for control flow
const exceptionMessage = event.exception?.values?.[0]?.value;
if (
exceptionMessage?.includes('Suspense Exception: This is not a real error!') ||
exceptionMessage?.includes('Suspense Exception: This is not a real error, and should not leak')
) {
return null;
}
return event;
}) ,
{ id: 'DropReactControlFlowErrors' },
),
);
// 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 => {
// Enhance route handler transactions
if (
event.type === 'transaction' &&
event.contexts?.trace?.data?.['next.span_type'] === 'BaseServer.handleRequest'
) {
event.contexts.trace.data[core.SEMANTIC_ATTRIBUTE_SENTRY_OP] = 'http.server';
event.contexts.trace.op = 'http.server';
if (event.transaction) {
event.transaction = core.stripUrlQueryAndFragment(event.transaction);
}
// eslint-disable-next-line deprecation/deprecation
const method = event.contexts.trace.data[semanticConventions.SEMATTRS_HTTP_METHOD];
// eslint-disable-next-line deprecation/deprecation
const target = event.contexts?.trace?.data?.[semanticConventions.SEMATTRS_HTTP_TARGET];
const route = event.contexts.trace.data[semanticConventions.ATTR_HTTP_ROUTE] || event.contexts.trace.data['next.route'];
if (typeof method === 'string' && typeof route === 'string') {
const cleanRoute = route.replace(/\/route$/, '');
event.transaction = `${method} ${cleanRoute}`;
event.contexts.trace.data[core.SEMANTIC_ATTRIBUTE_SENTRY_SOURCE] = 'route';
// Preserve next.route in case it did not get hoisted
event.contexts.trace.data['next.route'] = cleanRoute;
}
// backfill transaction name for pages that would otherwise contain unparameterized routes
if (event.contexts.trace.data[spanAttributesWithLogicAttached.TRANSACTION_ATTR_SENTRY_ROUTE_BACKFILL] && event.transaction !== 'GET /_app') {
event.transaction = `${method} ${event.contexts.trace.data[spanAttributesWithLogicAttached.TRANSACTION_ATTR_SENTRY_ROUTE_BACKFILL]}`;
}
// Next.js overrides transaction names for page loads that throw an error
// but we want to keep the original target name
if (event.transaction === 'GET /_error' && target) {
event.transaction = `${method ? `${method} ` : ''}${target}`;
}
}
// Next.js 13 is not correctly picking up tracing data for trace propagation so we use a back-fill strategy
if (
event.type === 'transaction' &&
typeof event.contexts?.trace?.data?.[spanAttributesWithLogicAttached.TRANSACTION_ATTR_SENTRY_TRACE_BACKFILL] === 'string'
) {
const traceparentData = core.extractTraceparentData(event.contexts.trace.data[spanAttributesWithLogicAttached.TRANSACTION_ATTR_SENTRY_TRACE_BACKFILL]);
if (traceparentData?.traceId) {
event.contexts.trace.trace_id = traceparentData.traceId;
}
if (traceparentData?.parentSpanId) {
event.contexts.trace.parent_span_id = traceparentData.parentSpanId;
}
}
});
if (process.env.NODE_ENV === 'development') {
core.getGlobalScope().addEventProcessor(devErrorSymbolicationEventProcessor.devErrorSymbolicationEventProcessor);
}
try {
// @ts-expect-error `process.turbopack` is a magic string that will be replaced by Next.js
if (process.turbopack) {
core.getGlobalScope().setTag('turbopack', true);
}
} catch {
// Noop
// The statement above can throw because process is not defined on the client
}
debugBuild.DEBUG_BUILD && core.logger.log('SDK successfully initialized');
return client;
}
function sdkAlreadyInitialized() {
return !!core.getClient();
}
exports.captureUnderscoreErrorException = _error.captureUnderscoreErrorException;
exports.wrapGetStaticPropsWithSentry = wrapGetStaticPropsWithSentry.wrapGetStaticPropsWithSentry;
exports.wrapGetInitialPropsWithSentry = wrapGetInitialPropsWithSentry.wrapGetInitialPropsWithSentry;
exports.wrapAppGetInitialPropsWithSentry = wrapAppGetInitialPropsWithSentry.wrapAppGetInitialPropsWithSentry;
exports.wrapDocumentGetInitialPropsWithSentry = wrapDocumentGetInitialPropsWithSentry.wrapDocumentGetInitialPropsWithSentry;
exports.wrapErrorGetInitialPropsWithSentry = wrapErrorGetInitialPropsWithSentry.wrapErrorGetInitialPropsWithSentry;
exports.wrapGetServerSidePropsWithSentry = wrapGetServerSidePropsWithSentry.wrapGetServerSidePropsWithSentry;
exports.wrapServerComponentWithSentry = wrapServerComponentWithSentry.wrapServerComponentWithSentry;
exports.wrapRouteHandlerWithSentry = wrapRouteHandlerWithSentry.wrapRouteHandlerWithSentry;
exports.wrapApiHandlerWithSentryVercelCrons = wrapApiHandlerWithSentryVercelCrons.wrapApiHandlerWithSentryVercelCrons;
exports.wrapMiddlewareWithSentry = wrapMiddlewareWithSentry.wrapMiddlewareWithSentry;
exports.wrapPageComponentWithSentry = wrapPageComponentWithSentry.wrapPageComponentWithSentry;
exports.wrapGenerationFunctionWithSentry = wrapGenerationFunctionWithSentry.wrapGenerationFunctionWithSentry;
exports.withServerActionInstrumentation = withServerActionInstrumentation.withServerActionInstrumentation;
exports.captureRequestError = captureRequestError.captureRequestError;
exports.wrapApiHandlerWithSentry = wrapApiHandlerWithSentry.wrapApiHandlerWithSentry;
exports.ErrorBoundary = ErrorBoundary;
exports.createReduxEnhancer = createReduxEnhancer;
exports.init = init;
exports.showReportDialog = showReportDialog;
exports.withErrorBoundary = withErrorBoundary;
Object.prototype.hasOwnProperty.call(node, '__proto__') &&
!Object.prototype.hasOwnProperty.call(exports, '__proto__') &&
Object.defineProperty(exports, '__proto__', {
enumerable: true,
value: node['__proto__']
});
Object.keys(node).forEach(k => {
if (k !== 'default' && !Object.prototype.hasOwnProperty.call(exports, k)) exports[k] = node[k];
});
//# sourceMappingURL=index.js.map