-
-
Notifications
You must be signed in to change notification settings - Fork 1.5k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
1 parent
11b1fbd
commit a806f00
Showing
6 changed files
with
195 additions
and
9 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,176 @@ | ||
import type { Carrier, Scope } from '@sentry/hub'; | ||
import { getHubFromCarrier, getMainCarrier } from '@sentry/hub'; | ||
import { RewriteFrames } from '@sentry/integrations'; | ||
import { addRequestDataToEvent, configureScope, getCurrentHub, init as nodeInit, Integrations } from '@sentry/node'; | ||
import { hasTracingEnabled } from '@sentry/tracing'; | ||
import type { EventProcessor } from '@sentry/types'; | ||
import type { CrossPlatformRequest } from '@sentry/utils'; | ||
import { escapeStringForRegex, logger } from '@sentry/utils'; | ||
import * as domainModule from 'domain'; | ||
import * as path from 'path'; | ||
|
||
import { isBuild } from './utils/isBuild'; | ||
import { buildMetadata } from './utils/metadata'; | ||
import type { NextjsOptions } from './utils/nextjsOptions'; | ||
import { addOrUpdateIntegration } from './utils/userIntegrations'; | ||
|
||
export * from '@sentry/node'; | ||
export { captureUnderscoreErrorException } from './utils/_error'; | ||
|
||
// Here we want to make sure to only include what doesn't have browser specifics | ||
// because or SSR of next.js we can only use this. | ||
export { ErrorBoundary, showReportDialog, withErrorBoundary } from '@sentry/react'; | ||
|
||
type GlobalWithDistDir = typeof global & { __rewriteFramesDistDir__: string }; | ||
const domain = domainModule as typeof domainModule & { active: (domainModule.Domain & Carrier) | null }; | ||
|
||
const isVercel = !!process.env.VERCEL; | ||
|
||
/** Inits the Sentry NextJS SDK on node. */ | ||
export function init(options: NextjsOptions): void { | ||
let eventProcessors: EventProcessor[] = []; | ||
|
||
if (__DEBUG_BUILD__ && options.debug) { | ||
logger.enable(); | ||
} | ||
|
||
__DEBUG_BUILD__ && logger.log('Initializing SDK...'); | ||
|
||
if (sdkAlreadyInitialized()) { | ||
__DEBUG_BUILD__ && logger.log('SDK already initialized'); | ||
return; | ||
} | ||
|
||
buildMetadata(options, ['nextjs', 'node']); | ||
options.environment = options.environment || process.env.NODE_ENV; | ||
addServerIntegrations(options); | ||
// Right now we only capture frontend sessions for Next.js | ||
options.autoSessionTracking = false; | ||
|
||
// In an ideal world, this init function would be called before any requests are handled. That way, every domain we | ||
// use to wrap a request would inherit its scope and client from the global hub. In practice, however, handling the | ||
// first request is what causes us to initialize the SDK, as the init code is injected into `_app` and all API route | ||
// handlers, and those are only accessed in the course of handling a request. As a result, we're already in a domain | ||
// when `init` is called. In order to compensate for this and mimic the ideal world scenario, we stash the active | ||
// domain, run `init` as normal, and then restore the domain afterwards, copying over data from the main hub as if we | ||
// really were inheriting. | ||
const activeDomain = domain.active; | ||
domain.active = null; | ||
|
||
nodeInit(options); | ||
|
||
configureScope(scope => { | ||
scope.setTag('runtime', 'node'); | ||
if (isVercel) { | ||
scope.setTag('vercel', true); | ||
} | ||
|
||
eventProcessors = addEventProcessors(scope); | ||
}); | ||
|
||
if (activeDomain) { | ||
const globalHub = getHubFromCarrier(getMainCarrier()); | ||
const domainHub = getHubFromCarrier(activeDomain); | ||
|
||
// apply the changes made by `nodeInit` to the domain's hub also | ||
domainHub.bindClient(globalHub.getClient()); | ||
domainHub.getScope()?.update(globalHub.getScope()); | ||
// `scope.update()` doesn't copy over event processors, so we have to add them manually | ||
eventProcessors.forEach(processor => { | ||
domainHub.getScope()?.addEventProcessor(processor); | ||
}); | ||
|
||
// restore the domain hub as the current one | ||
domain.active = activeDomain; | ||
} | ||
|
||
__DEBUG_BUILD__ && logger.log('SDK successfully initialized'); | ||
} | ||
|
||
function sdkAlreadyInitialized(): boolean { | ||
const hub = getCurrentHub(); | ||
return !!hub.getClient(); | ||
} | ||
|
||
function addServerIntegrations(options: NextjsOptions): void { | ||
// 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 = (global as GlobalWithDistDir).__rewriteFramesDistDir__ || '.next'; | ||
// nextjs always puts the build directory at the project root level, which is also where you run `next start` from, so | ||
// we can read in the project directory from the currently running process | ||
const distDirAbsPath = path.resolve(process.cwd(), distDirName); | ||
const SOURCEMAP_FILENAME_REGEX = new RegExp(escapeStringForRegex(distDirAbsPath)); | ||
|
||
const defaultRewriteFramesIntegration = new RewriteFrames({ | ||
iteratee: frame => { | ||
frame.filename = frame.filename?.replace(SOURCEMAP_FILENAME_REGEX, 'app:///_next'); | ||
return frame; | ||
}, | ||
}); | ||
|
||
if (options.integrations) { | ||
options.integrations = addOrUpdateIntegration(defaultRewriteFramesIntegration, options.integrations); | ||
} else { | ||
options.integrations = [defaultRewriteFramesIntegration]; | ||
} | ||
|
||
if (hasTracingEnabled(options)) { | ||
const defaultHttpTracingIntegration = new Integrations.Http({ tracing: true }); | ||
options.integrations = addOrUpdateIntegration(defaultHttpTracingIntegration, options.integrations, { | ||
Http: { keyPath: '_tracing', value: true }, | ||
}); | ||
} | ||
} | ||
|
||
function addEventProcessors(scope: Scope): EventProcessor[] { | ||
// Note: If you add an event processor here, you have to add it to the array that's returned also | ||
const filterTransactions: EventProcessor = event => { | ||
return event.type === 'transaction' && event.transaction === '/404' ? null : event; | ||
}; | ||
|
||
const addRequestData: EventProcessor = event => { | ||
const req = event.sdkProcessingMetadata?.req as CrossPlatformRequest; | ||
|
||
addRequestDataToEvent(event, req, { include: 'get options from user' }); | ||
|
||
return event; | ||
}; | ||
|
||
// Assign an `id` property to each event processor so that our logger and error messages can refer to it by name | ||
filterTransactions.id = 'FilterTransactions'; | ||
addRequestData.id = 'AddRequestData'; | ||
|
||
scope.addEventProcessor(filterTransactions); | ||
scope.addEventProcessor(addRequestData); | ||
|
||
return [filterTransactions, addRequestData]; | ||
} | ||
|
||
export type { SentryWebpackPluginOptions } from './config/types'; | ||
export { withSentryConfig } from './config'; | ||
export { isBuild } from './utils/isBuild'; | ||
export { | ||
withSentryGetServerSideProps, | ||
withSentryGetStaticProps, | ||
withSentryServerSideGetInitialProps, | ||
withSentryServerSideAppGetInitialProps, | ||
withSentryServerSideDocumentGetInitialProps, | ||
withSentryServerSideErrorGetInitialProps, | ||
} from './config/wrappers'; | ||
export { withSentry } from './utils/withSentry'; | ||
|
||
// Wrap various server methods to enable error monitoring and tracing. (Note: This only happens for non-Vercel | ||
// deployments, because the current method of doing the wrapping a) crashes Next 12 apps deployed to Vercel and | ||
// b) doesn't work on those apps anyway. We also don't do it during build, because there's no server running in that | ||
// phase.) | ||
if (!isVercel && !isBuild()) { | ||
// Dynamically require the file because even importing from it causes Next 12 to crash on Vercel. | ||
// In environments where the JS file doesn't exist, such as testing, import the TS file. | ||
try { | ||
// eslint-disable-next-line @typescript-eslint/no-var-requires | ||
const { instrumentServer } = require('./utils/instrumentServer.js'); | ||
instrumentServer(); | ||
} catch (err) { | ||
__DEBUG_BUILD__ && logger.warn(`Error: Unable to instrument server for tracing. Got ${err}.`); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters