UNPKG

@sentry/nextjs

Version:
404 lines (345 loc) 18.7 kB
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