Skip to content

Commit de075a4

Browse files
committed
fix requiring server init code
1 parent d5293da commit de075a4

File tree

2 files changed

+54
-12
lines changed

2 files changed

+54
-12
lines changed

packages/nextjs/src/utils/config.ts

Lines changed: 38 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,11 +2,13 @@ import { getSentryRelease } from '@sentry/node';
22
import { logger } from '@sentry/utils';
33
import defaultWebpackPlugin, { SentryCliPluginOptions } from '@sentry/webpack-plugin';
44
import * as SentryWebpackPlugin from '@sentry/webpack-plugin';
5+
import * as fs from 'fs';
6+
import * as path from 'path';
57

68
const SENTRY_CLIENT_CONFIG_FILE = './sentry.client.config.js';
79
const SENTRY_SERVER_CONFIG_FILE = './sentry.server.config.js';
810
// this is where the transpiled/bundled version of `USER_SERVER_CONFIG_FILE` will end up
9-
export const SERVER_INIT_OUTPUT_LOCATION = 'sentry/serverInit';
11+
export const SERVER_SDK_INIT_PATH = 'sentry/initServer.js';
1012

1113
// eslint-disable-next-line @typescript-eslint/no-explicit-any
1214
type PlainObject<T = any> = { [key: string]: T };
@@ -15,7 +17,14 @@ type PlainObject<T = any> = { [key: string]: T };
1517
type WebpackExport = (config: WebpackConfig, options: WebpackOptions) => WebpackConfig;
1618

1719
// The two arguments passed to the exported `webpack` function, as well as the thing it returns
18-
type WebpackConfig = { devtool: string; plugins: PlainObject[]; entry: EntryProperty; output: { path: string } };
20+
type WebpackConfig = {
21+
devtool: string;
22+
plugins: PlainObject[];
23+
entry: EntryProperty;
24+
output: { path: string };
25+
target: string;
26+
context: string;
27+
};
1928
type WebpackOptions = { dev: boolean; isServer: boolean; buildId: string };
2029

2130
// For our purposes, the value for `entry` is either an object, or a function which returns such an object
@@ -87,7 +96,7 @@ const injectSentry = async (origEntryProperty: EntryProperty, isServer: boolean)
8796
// `require()` on the resulting file when we're instrumenting the sesrver. (We can't use a dynamic import there
8897
// because that then forces the user into a particular TS config.)
8998
if (isServer) {
90-
newEntryProperty[SERVER_INIT_OUTPUT_LOCATION] = SENTRY_SERVER_CONFIG_FILE;
99+
newEntryProperty[SERVER_SDK_INIT_PATH] = SENTRY_SERVER_CONFIG_FILE;
91100
}
92101
// On the client, it's sufficient to inject it into the `main` JS code, which is included in every browser page.
93102
else {
@@ -144,6 +153,12 @@ export function withSentryConfig(
144153
}
145154

146155
const newWebpackExport = (config: WebpackConfig, options: WebpackOptions): WebpackConfig => {
156+
// if we're building server code, store the webpack output path as an env variable, so we know where to look for the
157+
// webpack-processed version of `sentry.server.config.js` when we need it
158+
if (config.target === 'node') {
159+
memoizeOutputPath(config);
160+
}
161+
147162
let newConfig = config;
148163

149164
if (typeof providedExports.webpack === 'function') {
@@ -181,3 +196,23 @@ export function withSentryConfig(
181196
webpack: newWebpackExport,
182197
};
183198
}
199+
200+
/**
201+
* Store the future location of the webpack-processed version of `sentry.server.config.js` in a runtime env variable, so
202+
* we know where to find it when we want to initialize the SDK.
203+
*
204+
* @param config The webpack config object for this build
205+
*/
206+
function memoizeOutputPath(config: WebpackConfig): void {
207+
const builtServerSDKInitPath = path.join(config.output.path, SERVER_SDK_INIT_PATH);
208+
const projectDir = config.context;
209+
// next will automatically set variables from `.env.local` in `process.env` at runtime
210+
const envFilePath = path.join(projectDir, '.env.local');
211+
const envVarString = `SENTRY_SERVER_INIT_PATH=${builtServerSDKInitPath}\n`;
212+
213+
if (fs.existsSync(envFilePath)) {
214+
fs.appendFileSync(envFilePath, `\n${envVarString}`);
215+
} else {
216+
fs.writeFileSync(envFilePath, envVarString);
217+
}
218+
}

packages/nextjs/src/utils/instrumentServer.ts

Lines changed: 16 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,9 @@
11
import { deepReadDirSync } from '@sentry/node';
22
import { hasTracingEnabled } from '@sentry/tracing';
33
import { Transaction } from '@sentry/types';
4-
import { fill } from '@sentry/utils';
4+
import { fill, logger } from '@sentry/utils';
55
import * as http from 'http';
66
import { default as createNextServer } from 'next';
7-
import * as path from 'path';
87
import * as url from 'url';
98

109
import * as Sentry from '../index.server';
@@ -45,7 +44,7 @@ type WrappedReqHandler = ReqHandler;
4544
// TODO is it necessary for this to be an object?
4645
const closure: PlainObject = {};
4746

48-
let sdkInitialized = false;
47+
let sdkSetupComplete = false;
4948

5049
/**
5150
* Do the monkeypatching and wrapping necessary to catch errors in page routes. Along the way, as a bonus, grab (and
@@ -60,11 +59,8 @@ export function instrumentServer(): string {
6059
// wrap this getter because it runs before the request handler runs, which gives us a chance to wrap the logger before
6160
// it's called for the first time
6261
fill(nextServerPrototype, 'getServerRequestHandler', makeWrappedHandlerGetter);
63-
if (!sdkInitialized) {
64-
require(path.join(process.cwd(), 'sentry.server.config.js'));
65-
sdkInitialized = true;
66-
}
6762

63+
// TODO replace with an env var, since at this point we don't have a value yet
6864
return closure.projectRootDir;
6965
}
7066

@@ -79,7 +75,18 @@ function makeWrappedHandlerGetter(origHandlerGetter: HandlerGetter): WrappedHand
7975
// We wrap this purely in order to be able to grab data and do further monkeypatching the first time it runs.
8076
// Otherwise, it's just a pass-through to the original method.
8177
const wrappedHandlerGetter = async function(this: NextServer): Promise<ReqHandler> {
82-
if (!closure.wrappingComplete) {
78+
if (!sdkSetupComplete) {
79+
try {
80+
// `SENTRY_SERVER_INIT_PATH` is set at build time, and points to a webpack-processed version of the user's
81+
// `sentry.server.config.js`. Requiring it starts the SDK.
82+
require(process.env.SENTRY_SERVER_INIT_PATH as string);
83+
} catch (err) {
84+
// Log the error but don't bail - we still want the wrapping to happen, in case the user is doing something weird
85+
// and manually calling `Sentry.init()` somewhere else.
86+
logger.error(`[Sentry] Could not initialize SDK. Received error:\n${err}`);
87+
}
88+
89+
// TODO: Replace projectRootDir with env variables
8390
closure.projectRootDir = this.server.dir;
8491
closure.server = this.server;
8592
closure.publicDir = this.server.publicDir;
@@ -91,7 +98,7 @@ function makeWrappedHandlerGetter(origHandlerGetter: HandlerGetter): WrappedHand
9198

9299
fill(serverPrototype, 'handleRequest', makeWrappedReqHandler);
93100

94-
closure.wrappingComplete = true;
101+
sdkSetupComplete = true;
95102
}
96103

97104
return origHandlerGetter.call(this);

0 commit comments

Comments
 (0)