Skip to content

Commit

Permalink
feat(nextjs): Add automatic sourcemapping for edge part of the SDK (#…
Browse files Browse the repository at this point in the history
  • Loading branch information
lforst committed Nov 6, 2023
1 parent 2c1b288 commit 9f32c61
Show file tree
Hide file tree
Showing 10 changed files with 69 additions and 43 deletions.
2 changes: 1 addition & 1 deletion packages/bun/src/client.ts
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ export class BunClient extends ServerRuntimeClient<BunClientOptions> {

const clientOptions: ServerRuntimeClientOptions = {
...options,
platform: 'bun',
platform: 'javascript',
runtime: { name: 'bun', version: Bun.version },
serverName: options.serverName || global.process.env.SENTRY_NAME || os.hostname(),
};
Expand Down
4 changes: 2 additions & 2 deletions packages/core/test/lib/serverruntimeclient.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,13 +22,13 @@ describe('ServerRuntimeClient', () => {
describe('_prepareEvent', () => {
test('adds platform to event', () => {
const options = getDefaultClientOptions({ dsn: PUBLIC_DSN });
const client = new ServerRuntimeClient({ ...options, platform: 'edge' });
const client = new ServerRuntimeClient({ ...options, platform: 'blargh' });

const event: Event = {};
const hint: EventHint = {};
(client as any)._prepareEvent(event, hint);

expect(event.platform).toEqual('edge');
expect(event.platform).toEqual('blargh');
});

test('adds server_name to event', () => {
Expand Down
2 changes: 1 addition & 1 deletion packages/deno/src/client.ts
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ export class DenoClient extends ServerRuntimeClient<DenoClientOptions> {

const clientOptions: ServerRuntimeClientOptions = {
...options,
platform: 'deno',
platform: 'javascript',
runtime: { name: 'deno', version: Deno.version.deno },
serverName: options.serverName || getHostName(),
};
Expand Down
4 changes: 2 additions & 2 deletions packages/deno/test/__snapshots__/mod.test.ts.snap
Original file line number Diff line number Diff line change
Expand Up @@ -135,7 +135,7 @@ snapshot[`captureException 1`] = `
},
],
},
platform: "deno",
platform: "javascript",
sdk: {
integrations: [
"InboundFilters",
Expand Down Expand Up @@ -195,7 +195,7 @@ snapshot[`captureMessage 1`] = `
event_id: "{{id}}",
level: "info",
message: "Some error message",
platform: "deno",
platform: "javascript",
sdk: {
integrations: [
"InboundFilters",
Expand Down
29 changes: 13 additions & 16 deletions packages/nextjs/src/config/webpack.ts
Original file line number Diff line number Diff line change
Expand Up @@ -686,24 +686,20 @@ export function getWebpackPluginOptions(
userPluginOptions: Partial<SentryWebpackPluginOptions>,
userSentryOptions: UserSentryOptions,
): SentryWebpackPluginOptions {
const { buildId, isServer, webpack, config, dir: projectDir } = buildContext;
const { buildId, isServer, config, dir: projectDir } = buildContext;
const userNextConfig = config as NextConfigObject;

const distDirAbsPath = path.resolve(projectDir, userNextConfig.distDir || '.next'); // `.next` is the default directory

const isWebpack5 = webpack.version.startsWith('5');
const isServerless = userNextConfig.target === 'experimental-serverless-trace';
const hasSentryProperties = fs.existsSync(path.resolve(projectDir, 'sentry.properties'));
const urlPrefix = '~/_next';

const serverInclude = isServerless
? [{ paths: [`${distDirAbsPath}/serverless/`], urlPrefix: `${urlPrefix}/serverless` }]
: [
{ paths: [`${distDirAbsPath}/server/pages/`], urlPrefix: `${urlPrefix}/server/pages` },
{ paths: [`${distDirAbsPath}/server/app/`], urlPrefix: `${urlPrefix}/server/app` },
].concat(
isWebpack5 ? [{ paths: [`${distDirAbsPath}/server/chunks/`], urlPrefix: `${urlPrefix}/server/chunks` }] : [],
);
: [{ paths: [`${distDirAbsPath}/server/`], urlPrefix: `${urlPrefix}/server` }];

const serverIgnore: string[] = [];

const clientInclude = userSentryOptions.widenClientFileUpload
? [{ paths: [`${distDirAbsPath}/static/chunks`], urlPrefix: `${urlPrefix}/static/chunks` }]
Expand All @@ -712,15 +708,16 @@ export function getWebpackPluginOptions(
{ paths: [`${distDirAbsPath}/static/chunks/app`], urlPrefix: `${urlPrefix}/static/chunks/app` },
];

// Widening the upload scope is necessarily going to lead to us uploading files we don't need to (ones which
// don't include any user code). In order to lessen that where we can, exclude the internal nextjs files we know
// will be there.
const clientIgnore = userSentryOptions.widenClientFileUpload
? ['framework-*', 'framework.*', 'main-*', 'polyfills-*', 'webpack-*']
: [];

const defaultPluginOptions = dropUndefinedKeys({
include: isServer ? serverInclude : clientInclude,
ignore:
isServer || !userSentryOptions.widenClientFileUpload
? []
: // Widening the upload scope is necessarily going to lead to us uploading files we don't need to (ones which
// don't include any user code). In order to lessen that where we can, exclude the internal nextjs files we know
// will be there.
['framework-*', 'framework.*', 'main-*', 'polyfills-*', 'webpack-*'],
ignore: isServer ? serverIgnore : clientIgnore,
url: process.env.SENTRY_URL,
org: process.env.SENTRY_ORG,
project: process.env.SENTRY_PROJECT,
Expand Down Expand Up @@ -976,7 +973,7 @@ function addValueInjectionLoader(

newConfig.module.rules.push(
{
test: /sentry\.server\.config\.(jsx?|tsx?)/,
test: /sentry\.(server|edge)\.config\.(jsx?|tsx?)/,
use: [
{
loader: path.resolve(__dirname, 'loaders/valueInjectionLoader.js'),
Expand Down
30 changes: 30 additions & 0 deletions packages/nextjs/src/edge/index.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,16 @@
import { SDK_VERSION } from '@sentry/core';
import { RewriteFrames } from '@sentry/integrations';
import type { SdkMetadata } from '@sentry/types';
import { addOrUpdateIntegration, escapeStringForRegex, GLOBAL_OBJ } from '@sentry/utils';
import type { VercelEdgeOptions } from '@sentry/vercel-edge';
import { init as vercelEdgeInit } from '@sentry/vercel-edge';

export type EdgeOptions = VercelEdgeOptions;

const globalWithInjectedValues = GLOBAL_OBJ as typeof GLOBAL_OBJ & {
__rewriteFramesDistDir__?: string;
};

/** Inits the Sentry NextJS SDK on the Edge Runtime. */
export function init(options: VercelEdgeOptions = {}): void {
const opts = {
Expand All @@ -23,6 +29,30 @@ export function init(options: VercelEdgeOptions = {}): void {
version: SDK_VERSION,
};

let integrations = opts.integrations || [];

// 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 = globalWithInjectedValues.__rewriteFramesDistDir__;
if (distDirName) {
const distDirAbsPath = distDirName.replace(/(\/|\\)$/, ''); // We strip trailing slashes because "app:///_next" also doesn't have one

// Normally we would use `path.resolve` to obtain the absolute path we will strip from the stack frame to align with
// the uploaded artifacts, however we don't have access to that API in edge so we need to be a bit more lax.
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;
},
});

integrations = addOrUpdateIntegration(defaultRewriteFramesIntegration, integrations);
}

opts.integrations = integrations;

vercelEdgeInit(opts);
}

Expand Down
30 changes: 16 additions & 14 deletions packages/nextjs/src/server/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,7 @@ export function showReportDialog(): void {
}

const globalWithInjectedValues = global as typeof global & {
__rewriteFramesDistDir__: string;
__rewriteFramesDistDir__?: string;
};

// TODO (v8): Remove this
Expand Down Expand Up @@ -119,19 +119,21 @@ function addServerIntegrations(options: NodeOptions): 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 = globalWithInjectedValues.__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;
},
});
integrations = addOrUpdateIntegration(defaultRewriteFramesIntegration, integrations);
const distDirName = globalWithInjectedValues.__rewriteFramesDistDir__;
if (distDirName) {
// 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(distDirName).replace(/(\/|\\)$/, ''); // We strip trailing slashes because "app:///_next" also doesn't have one
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;
},
});
integrations = addOrUpdateIntegration(defaultRewriteFramesIntegration, integrations);
}

const defaultOnUncaughtExceptionIntegration: IntegrationWithExclusionOption = new Integrations.OnUncaughtException({
exitEvenIfOtherHandlersAreRegistered: false,
Expand Down
2 changes: 1 addition & 1 deletion packages/nextjs/test/config/loaders.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -72,7 +72,7 @@ describe('webpack loaders', () => {
});

expect(finalWebpackConfig.module.rules).toContainEqual({
test: /sentry\.server\.config\.(jsx?|tsx?)/,
test: /sentry\.(server|edge)\.config\.(jsx?|tsx?)/,
use: [
{
loader: expect.stringEndingWith('valueInjectionLoader.js'),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -141,8 +141,7 @@ describe('Sentry webpack plugin config', () => {
) as SentryWebpackPlugin;

expect(sentryWebpackPluginInstance.options.include).toEqual([
{ paths: [`${serverBuildContextWebpack4.dir}/.next/server/pages/`], urlPrefix: '~/_next/server/pages' },
{ paths: [`${serverBuildContextWebpack4.dir}/.next/server/app/`], urlPrefix: '~/_next/server/app' },
{ paths: [`${serverBuildContextWebpack4.dir}/.next/server/`], urlPrefix: '~/_next/server' },
]);
});

Expand All @@ -159,9 +158,7 @@ describe('Sentry webpack plugin config', () => {
) as SentryWebpackPlugin;

expect(sentryWebpackPluginInstance.options.include).toEqual([
{ paths: [`${serverBuildContext.dir}/.next/server/pages/`], urlPrefix: '~/_next/server/pages' },
{ paths: [`${serverBuildContext.dir}/.next/server/app/`], urlPrefix: '~/_next/server/app' },
{ paths: [`${serverBuildContext.dir}/.next/server/chunks/`], urlPrefix: '~/_next/server/chunks' },
{ paths: [`${serverBuildContext.dir}/.next/server/`], urlPrefix: '~/_next/server' },
]);
});
});
Expand Down
2 changes: 1 addition & 1 deletion packages/vercel-edge/src/client.ts
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ export class VercelEdgeClient extends ServerRuntimeClient<VercelEdgeClientOption

const clientOptions: ServerRuntimeClientOptions = {
...options,
platform: 'vercel-edge',
platform: 'javascript',
// TODO: Grab version information
runtime: { name: 'vercel-edge' },
serverName: options.serverName || process.env.SENTRY_NAME,
Expand Down

0 comments on commit 9f32c61

Please sign in to comment.