Skip to content

Commit a806f00

Browse files
committed
STASH
1 parent 11b1fbd commit a806f00

File tree

6 files changed

+195
-9
lines changed

6 files changed

+195
-9
lines changed

.github/workflows/build.yml

+3-3
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,9 @@
11
name: 'Build & Test'
22
on:
33
push:
4-
branches:
5-
- master
6-
- release/**
4+
# branches:
5+
# - master
6+
# - release/**
77
pull_request:
88
workflow_dispatch:
99
inputs:

packages/nextjs/src/config/wrappers/wrapperUtils.ts

+9
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,15 @@ function setTransactionOnRequest(transaction: Transaction, req: IncomingMessage)
2727
req._sentryTransaction = transaction;
2828
}
2929

30+
// function getRequestFromTransaction(transaction: Transaction): IncomingMessage | undefined {
31+
// return req._sentryTransaction;
32+
// }
33+
//
34+
// function setTransactionOnRequest(transaction: Transaction, req: IncomingMessage): void {
35+
// req._sentryTransaction = transaction;
36+
// }
37+
38+
// TODO: Make this work with flushing in serverless functions
3039
function autoEndTransactionOnResponseEnd(transaction: Transaction, res: ServerResponse): void {
3140
fill(res, 'end', (originalEnd: ServerResponse['end']) => {
3241
return function (this: unknown, ...endArguments: Parameters<ServerResponse['end']>) {
+176
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,176 @@
1+
import type { Carrier, Scope } from '@sentry/hub';
2+
import { getHubFromCarrier, getMainCarrier } from '@sentry/hub';
3+
import { RewriteFrames } from '@sentry/integrations';
4+
import { addRequestDataToEvent, configureScope, getCurrentHub, init as nodeInit, Integrations } from '@sentry/node';
5+
import { hasTracingEnabled } from '@sentry/tracing';
6+
import type { EventProcessor } from '@sentry/types';
7+
import type { CrossPlatformRequest } from '@sentry/utils';
8+
import { escapeStringForRegex, logger } from '@sentry/utils';
9+
import * as domainModule from 'domain';
10+
import * as path from 'path';
11+
12+
import { isBuild } from './utils/isBuild';
13+
import { buildMetadata } from './utils/metadata';
14+
import type { NextjsOptions } from './utils/nextjsOptions';
15+
import { addOrUpdateIntegration } from './utils/userIntegrations';
16+
17+
export * from '@sentry/node';
18+
export { captureUnderscoreErrorException } from './utils/_error';
19+
20+
// Here we want to make sure to only include what doesn't have browser specifics
21+
// because or SSR of next.js we can only use this.
22+
export { ErrorBoundary, showReportDialog, withErrorBoundary } from '@sentry/react';
23+
24+
type GlobalWithDistDir = typeof global & { __rewriteFramesDistDir__: string };
25+
const domain = domainModule as typeof domainModule & { active: (domainModule.Domain & Carrier) | null };
26+
27+
const isVercel = !!process.env.VERCEL;
28+
29+
/** Inits the Sentry NextJS SDK on node. */
30+
export function init(options: NextjsOptions): void {
31+
let eventProcessors: EventProcessor[] = [];
32+
33+
if (__DEBUG_BUILD__ && options.debug) {
34+
logger.enable();
35+
}
36+
37+
__DEBUG_BUILD__ && logger.log('Initializing SDK...');
38+
39+
if (sdkAlreadyInitialized()) {
40+
__DEBUG_BUILD__ && logger.log('SDK already initialized');
41+
return;
42+
}
43+
44+
buildMetadata(options, ['nextjs', 'node']);
45+
options.environment = options.environment || process.env.NODE_ENV;
46+
addServerIntegrations(options);
47+
// Right now we only capture frontend sessions for Next.js
48+
options.autoSessionTracking = false;
49+
50+
// In an ideal world, this init function would be called before any requests are handled. That way, every domain we
51+
// use to wrap a request would inherit its scope and client from the global hub. In practice, however, handling the
52+
// first request is what causes us to initialize the SDK, as the init code is injected into `_app` and all API route
53+
// handlers, and those are only accessed in the course of handling a request. As a result, we're already in a domain
54+
// when `init` is called. In order to compensate for this and mimic the ideal world scenario, we stash the active
55+
// domain, run `init` as normal, and then restore the domain afterwards, copying over data from the main hub as if we
56+
// really were inheriting.
57+
const activeDomain = domain.active;
58+
domain.active = null;
59+
60+
nodeInit(options);
61+
62+
configureScope(scope => {
63+
scope.setTag('runtime', 'node');
64+
if (isVercel) {
65+
scope.setTag('vercel', true);
66+
}
67+
68+
eventProcessors = addEventProcessors(scope);
69+
});
70+
71+
if (activeDomain) {
72+
const globalHub = getHubFromCarrier(getMainCarrier());
73+
const domainHub = getHubFromCarrier(activeDomain);
74+
75+
// apply the changes made by `nodeInit` to the domain's hub also
76+
domainHub.bindClient(globalHub.getClient());
77+
domainHub.getScope()?.update(globalHub.getScope());
78+
// `scope.update()` doesn't copy over event processors, so we have to add them manually
79+
eventProcessors.forEach(processor => {
80+
domainHub.getScope()?.addEventProcessor(processor);
81+
});
82+
83+
// restore the domain hub as the current one
84+
domain.active = activeDomain;
85+
}
86+
87+
__DEBUG_BUILD__ && logger.log('SDK successfully initialized');
88+
}
89+
90+
function sdkAlreadyInitialized(): boolean {
91+
const hub = getCurrentHub();
92+
return !!hub.getClient();
93+
}
94+
95+
function addServerIntegrations(options: NextjsOptions): void {
96+
// This value is injected at build time, based on the output directory specified in the build config. Though a default
97+
// is set there, we set it here as well, just in case something has gone wrong with the injection.
98+
const distDirName = (global as GlobalWithDistDir).__rewriteFramesDistDir__ || '.next';
99+
// nextjs always puts the build directory at the project root level, which is also where you run `next start` from, so
100+
// we can read in the project directory from the currently running process
101+
const distDirAbsPath = path.resolve(process.cwd(), distDirName);
102+
const SOURCEMAP_FILENAME_REGEX = new RegExp(escapeStringForRegex(distDirAbsPath));
103+
104+
const defaultRewriteFramesIntegration = new RewriteFrames({
105+
iteratee: frame => {
106+
frame.filename = frame.filename?.replace(SOURCEMAP_FILENAME_REGEX, 'app:///_next');
107+
return frame;
108+
},
109+
});
110+
111+
if (options.integrations) {
112+
options.integrations = addOrUpdateIntegration(defaultRewriteFramesIntegration, options.integrations);
113+
} else {
114+
options.integrations = [defaultRewriteFramesIntegration];
115+
}
116+
117+
if (hasTracingEnabled(options)) {
118+
const defaultHttpTracingIntegration = new Integrations.Http({ tracing: true });
119+
options.integrations = addOrUpdateIntegration(defaultHttpTracingIntegration, options.integrations, {
120+
Http: { keyPath: '_tracing', value: true },
121+
});
122+
}
123+
}
124+
125+
function addEventProcessors(scope: Scope): EventProcessor[] {
126+
// Note: If you add an event processor here, you have to add it to the array that's returned also
127+
const filterTransactions: EventProcessor = event => {
128+
return event.type === 'transaction' && event.transaction === '/404' ? null : event;
129+
};
130+
131+
const addRequestData: EventProcessor = event => {
132+
const req = event.sdkProcessingMetadata?.req as CrossPlatformRequest;
133+
134+
addRequestDataToEvent(event, req, { include: 'get options from user' });
135+
136+
return event;
137+
};
138+
139+
// Assign an `id` property to each event processor so that our logger and error messages can refer to it by name
140+
filterTransactions.id = 'FilterTransactions';
141+
addRequestData.id = 'AddRequestData';
142+
143+
scope.addEventProcessor(filterTransactions);
144+
scope.addEventProcessor(addRequestData);
145+
146+
return [filterTransactions, addRequestData];
147+
}
148+
149+
export type { SentryWebpackPluginOptions } from './config/types';
150+
export { withSentryConfig } from './config';
151+
export { isBuild } from './utils/isBuild';
152+
export {
153+
withSentryGetServerSideProps,
154+
withSentryGetStaticProps,
155+
withSentryServerSideGetInitialProps,
156+
withSentryServerSideAppGetInitialProps,
157+
withSentryServerSideDocumentGetInitialProps,
158+
withSentryServerSideErrorGetInitialProps,
159+
} from './config/wrappers';
160+
export { withSentry } from './utils/withSentry';
161+
162+
// Wrap various server methods to enable error monitoring and tracing. (Note: This only happens for non-Vercel
163+
// deployments, because the current method of doing the wrapping a) crashes Next 12 apps deployed to Vercel and
164+
// b) doesn't work on those apps anyway. We also don't do it during build, because there's no server running in that
165+
// phase.)
166+
if (!isVercel && !isBuild()) {
167+
// Dynamically require the file because even importing from it causes Next 12 to crash on Vercel.
168+
// In environments where the JS file doesn't exist, such as testing, import the TS file.
169+
try {
170+
// eslint-disable-next-line @typescript-eslint/no-var-requires
171+
const { instrumentServer } = require('./utils/instrumentServer.js');
172+
instrumentServer();
173+
} catch (err) {
174+
__DEBUG_BUILD__ && logger.warn(`Error: Unable to instrument server for tracing. Got ${err}.`);
175+
}
176+
}

packages/nextjs/src/index.server.ts

+2-2
Original file line numberDiff line numberDiff line change
@@ -144,8 +144,8 @@ if (!isVercel && !isBuild()) {
144144
// In environments where the JS file doesn't exist, such as testing, import the TS file.
145145
try {
146146
// eslint-disable-next-line @typescript-eslint/no-var-requires
147-
const { instrumentServer } = require('./utils/instrumentServer.js');
148-
instrumentServer();
147+
// const { instrumentServer } = require('./utils/instrumentServer.js');
148+
// instrumentServer();
149149
} catch (err) {
150150
__DEBUG_BUILD__ && logger.warn(`Error: Unable to instrument server for tracing. Got ${err}.`);
151151
}

packages/nextjs/src/utils/instrumentServer.ts

+2-2
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
/* eslint-disable max-lines */
22
import {
3-
addRequestDataToEvent,
3+
// addRequestDataToEvent,
44
captureException,
55
configureScope,
66
deepReadDirSync,
@@ -244,7 +244,7 @@ function makeWrappedReqHandler(origReqHandler: ReqHandler): WrappedReqHandler {
244244
const currentScope = getCurrentHub().getScope();
245245

246246
if (currentScope) {
247-
currentScope.addEventProcessor(event => addRequestDataToEvent(event, nextReq));
247+
// currentScope.addEventProcessor(event => addRequestDataToEvent(event, nextReq));
248248

249249
// We only want to record page and API requests
250250
if (hasTracingEnabled() && shouldTraceRequest(nextReq.url, publicDirFiles)) {

packages/nextjs/src/utils/withSentry.ts

+3-2
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
1-
import { addRequestDataToEvent, captureException, flush, getCurrentHub, startTransaction } from '@sentry/node';
1+
import { captureException, flush, getCurrentHub, startTransaction } from '@sentry/node';
2+
// import { addRequestDataToEvent, captureException, flush, getCurrentHub, startTransaction } from '@sentry/node';
23
import { extractTraceparentData, hasTracingEnabled } from '@sentry/tracing';
34
import { Transaction } from '@sentry/types';
45
import {
@@ -41,7 +42,7 @@ export const withSentry = (origHandler: NextApiHandler): WrappedNextApiHandler =
4142
const currentScope = getCurrentHub().getScope();
4243

4344
if (currentScope) {
44-
currentScope.addEventProcessor(event => addRequestDataToEvent(event, req));
45+
// currentScope.addEventProcessor(event => addRequestDataToEvent(event, req));
4546

4647
if (hasTracingEnabled()) {
4748
// If there is a trace header set, extract the data from it (parentSpanId, traceId, and sampling decision)

0 commit comments

Comments
 (0)