Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
21 changes: 20 additions & 1 deletion packages/nextjs/src/index.server.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import { RewriteFrames } from '@sentry/integrations';
import { configureScope, init as nodeInit, Integrations } from '@sentry/node';
import { configureScope, getCurrentHub, init as nodeInit, Integrations } from '@sentry/node';
import { logger } from '@sentry/utils';

import { instrumentServer } from './utils/instrumentServer';
import { MetadataBuilder } from './utils/metadataBuilder';
Expand All @@ -14,6 +15,17 @@ export { ErrorBoundary, withErrorBoundary } from '@sentry/react';

/** Inits the Sentry NextJS SDK on node. */
export function init(options: NextjsOptions): void {
if (options.debug) {
logger.enable();
}

logger.log('Initializing SDK...');

if (sdkAlreadyInitialized()) {
logger.log('SDK already initialized');
return;
}

const metadataBuilder = new MetadataBuilder(options, ['nextjs', 'node']);
metadataBuilder.addSdkMetadata();
options.environment = options.environment || process.env.NODE_ENV;
Expand All @@ -26,6 +38,13 @@ export function init(options: NextjsOptions): void {
configureScope(scope => {
scope.setTag('runtime', 'node');
});

logger.log('SDK successfully initialized');
}

function sdkAlreadyInitialized(): boolean {
const hub = getCurrentHub();
return !!hub.getClient();
}

const SOURCEMAP_FILENAME_REGEX = /^.*\/\.next\//;
Expand Down
104 changes: 54 additions & 50 deletions packages/nextjs/test/index.server.test.ts
Original file line number Diff line number Diff line change
@@ -1,48 +1,52 @@
import { RewriteFrames } from '@sentry/integrations';
import { Integrations } from '@sentry/node';
import * as SentryNode from '@sentry/node';
import { Integration } from '@sentry/types';
import { getGlobalObject } from '@sentry/utils';

import { init, Scope } from '../src/index.server';
import { NextjsOptions } from '../src/utils/nextjsOptions';

const mockInit = jest.fn();
let configureScopeCallback: (scope: Scope) => void = () => undefined;
const { Integrations } = SentryNode;

jest.mock('@sentry/node', () => {
const actual = jest.requireActual('@sentry/node');
return {
...actual,
init: (options: NextjsOptions) => {
mockInit(options);
},
configureScope: (callback: (scope: Scope) => void) => {
configureScopeCallback = callback;
},
};
});
const global = getGlobalObject();

let configureScopeCallback: (scope: Scope) => void = () => undefined;
jest.spyOn(SentryNode, 'configureScope').mockImplementation(callback => (configureScopeCallback = callback));
const nodeInit = jest.spyOn(SentryNode, 'init');

describe('Server init()', () => {
afterEach(() => {
mockInit.mockClear();
nodeInit.mockClear();
configureScopeCallback = () => undefined;
global.__SENTRY__.hub = undefined;
});

it('inits the Node SDK', () => {
expect(mockInit).toHaveBeenCalledTimes(0);
expect(nodeInit).toHaveBeenCalledTimes(0);
init({});
expect(mockInit).toHaveBeenCalledTimes(1);
expect(mockInit).toHaveBeenLastCalledWith({
_metadata: {
sdk: {
name: 'sentry.javascript.nextjs',
version: expect.any(String),
packages: expect.any(Array),
expect(nodeInit).toHaveBeenCalledTimes(1);
expect(nodeInit).toHaveBeenLastCalledWith(
expect.objectContaining({
_metadata: {
sdk: {
name: 'sentry.javascript.nextjs',
version: expect.any(String),
packages: expect.any(Array),
},
},
},
autoSessionTracking: false,
environment: 'test',
integrations: [expect.any(RewriteFrames)],
});
autoSessionTracking: false,
environment: 'test',
integrations: [expect.any(RewriteFrames)],
}),
);
});

it("doesn't reinitialize the node SDK if already initialized", () => {
expect(nodeInit).toHaveBeenCalledTimes(0);
init({});
expect(nodeInit).toHaveBeenCalledTimes(1);
init({});
expect(nodeInit).toHaveBeenCalledTimes(1);
});

it('sets runtime on scope', () => {
Expand All @@ -57,45 +61,45 @@ describe('Server init()', () => {
it('adds RewriteFrames integration by default', () => {
init({});

const reactInitOptions: NextjsOptions = mockInit.mock.calls[0][0];
expect(reactInitOptions.integrations).toHaveLength(1);
const integrations = reactInitOptions.integrations as Integration[];
const nodeInitOptions: NextjsOptions = nodeInit.mock.calls[0][0]!;
expect(nodeInitOptions.integrations).toHaveLength(1);
const integrations = nodeInitOptions.integrations as Integration[];
expect(integrations[0]).toEqual(expect.any(RewriteFrames));
});

it('adds Http integration by default if tracesSampleRate is set', () => {
init({ tracesSampleRate: 1.0 });

const reactInitOptions: NextjsOptions = mockInit.mock.calls[0][0];
expect(reactInitOptions.integrations).toHaveLength(2);
const integrations = reactInitOptions.integrations as Integration[];
const nodeInitOptions: NextjsOptions = nodeInit.mock.calls[0][0]!;
expect(nodeInitOptions.integrations).toHaveLength(2);
const integrations = nodeInitOptions.integrations as Integration[];
expect(integrations[1]).toEqual(expect.any(Integrations.Http));
});

it('adds Http integration by default if tracesSampler is set', () => {
init({ tracesSampler: () => true });

const reactInitOptions: NextjsOptions = mockInit.mock.calls[0][0];
expect(reactInitOptions.integrations).toHaveLength(2);
const integrations = reactInitOptions.integrations as Integration[];
const nodeInitOptions: NextjsOptions = nodeInit.mock.calls[0][0]!;
expect(nodeInitOptions.integrations).toHaveLength(2);
const integrations = nodeInitOptions.integrations as Integration[];
expect(integrations[1]).toEqual(expect.any(Integrations.Http));
});

it('adds Http integration with tracing true', () => {
init({ tracesSampleRate: 1.0 });
const reactInitOptions: NextjsOptions = mockInit.mock.calls[0][0];
expect(reactInitOptions.integrations).toHaveLength(2);
const nodeInitOptions: NextjsOptions = nodeInit.mock.calls[0][0]!;
expect(nodeInitOptions.integrations).toHaveLength(2);

const integrations = reactInitOptions.integrations as Integration[];
const integrations = nodeInitOptions.integrations as Integration[];
expect((integrations[1] as any)._tracing).toBe(true);
});

it('supports passing integration through options', () => {
init({ tracesSampleRate: 1.0, integrations: [new Integrations.Console()] });
const reactInitOptions: NextjsOptions = mockInit.mock.calls[0][0];
expect(reactInitOptions.integrations).toHaveLength(3);
const nodeInitOptions: NextjsOptions = nodeInit.mock.calls[0][0]!;
expect(nodeInitOptions.integrations).toHaveLength(3);

const integrations = reactInitOptions.integrations as Integration[];
const integrations = nodeInitOptions.integrations as Integration[];
expect(integrations).toEqual([
expect.any(Integrations.Console),
expect.any(RewriteFrames),
Expand All @@ -110,9 +114,9 @@ describe('Server init()', () => {
integrations: [new Integrations.Http({ tracing: false })],
});

const reactInitOptions: NextjsOptions = mockInit.mock.calls[0][0];
expect(reactInitOptions.integrations).toHaveLength(2);
const integrations = reactInitOptions.integrations as Integration[];
const nodeInitOptions: NextjsOptions = nodeInit.mock.calls[0][0]!;
expect(nodeInitOptions.integrations).toHaveLength(2);
const integrations = nodeInitOptions.integrations as Integration[];
expect(integrations[0] as InstanceType<typeof Integrations.Http>).toEqual(
expect.objectContaining({ _breadcrumbs: true, _tracing: true, name: 'Http' }),
);
Expand All @@ -124,9 +128,9 @@ describe('Server init()', () => {
integrations: [new Integrations.Http({ tracing: false })],
});

const reactInitOptions: NextjsOptions = mockInit.mock.calls[0][0];
expect(reactInitOptions.integrations).toHaveLength(2);
const integrations = reactInitOptions.integrations as Integration[];
const nodeInitOptions: NextjsOptions = nodeInit.mock.calls[0][0]!;
expect(nodeInitOptions.integrations).toHaveLength(2);
const integrations = nodeInitOptions.integrations as Integration[];
expect(integrations[0] as InstanceType<typeof Integrations.Http>).toEqual(
expect.objectContaining({ _breadcrumbs: true, _tracing: true, name: 'Http' }),
);
Expand Down