Skip to content

Commit 6829c8c

Browse files
authored
feat(nextjs): Auto enable node http integration on server (#3675)
Enable the HTTP integration from `@sentry/node` to allow for the sentry-trace header to be attached to outgoing server requests. This adds support for distributed tracing.
1 parent e99f41d commit 6829c8c

File tree

2 files changed

+145
-1
lines changed

2 files changed

+145
-1
lines changed

packages/nextjs/src/index.server.ts

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import { RewriteFrames } from '@sentry/integrations';
2-
import { configureScope, init as nodeInit } from '@sentry/node';
2+
import { configureScope, init as nodeInit, Integrations } from '@sentry/node';
33

44
import { instrumentServer } from './utils/instrumentServer';
55
import { MetadataBuilder } from './utils/metadataBuilder';
@@ -37,12 +37,20 @@ const defaultRewriteFramesIntegration = new RewriteFrames({
3737
},
3838
});
3939

40+
const defaultHttpTracingIntegration = new Integrations.Http({ tracing: true });
41+
4042
function addServerIntegrations(options: NextjsOptions): void {
4143
if (options.integrations) {
4244
options.integrations = addIntegration(defaultRewriteFramesIntegration, options.integrations);
4345
} else {
4446
options.integrations = [defaultRewriteFramesIntegration];
4547
}
48+
49+
if (options.tracesSampleRate !== undefined || options.tracesSampler !== undefined) {
50+
options.integrations = addIntegration(defaultHttpTracingIntegration, options.integrations, {
51+
Http: { keyPath: '_tracing', value: true },
52+
});
53+
}
4654
}
4755

4856
export { withSentryConfig } from './utils/config';
Lines changed: 136 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,136 @@
1+
import { RewriteFrames } from '@sentry/integrations';
2+
import { Integrations } from '@sentry/node';
3+
import { Integration } from '@sentry/types';
4+
5+
import { init, Scope } from '../src/index.server';
6+
import { NextjsOptions } from '../src/utils/nextjsOptions';
7+
8+
const mockInit = jest.fn();
9+
let configureScopeCallback: (scope: Scope) => void = () => undefined;
10+
11+
jest.mock('@sentry/node', () => {
12+
const actual = jest.requireActual('@sentry/node');
13+
return {
14+
...actual,
15+
init: (options: NextjsOptions) => {
16+
mockInit(options);
17+
},
18+
configureScope: (callback: (scope: Scope) => void) => {
19+
configureScopeCallback = callback;
20+
},
21+
};
22+
});
23+
24+
describe('Server init()', () => {
25+
afterEach(() => {
26+
mockInit.mockClear();
27+
configureScopeCallback = () => undefined;
28+
});
29+
30+
it('inits the Node SDK', () => {
31+
expect(mockInit).toHaveBeenCalledTimes(0);
32+
init({});
33+
expect(mockInit).toHaveBeenCalledTimes(1);
34+
expect(mockInit).toHaveBeenLastCalledWith({
35+
_metadata: {
36+
sdk: {
37+
name: 'sentry.javascript.nextjs',
38+
version: expect.any(String),
39+
packages: expect.any(Array),
40+
},
41+
},
42+
autoSessionTracking: false,
43+
environment: 'test',
44+
integrations: [expect.any(RewriteFrames)],
45+
});
46+
});
47+
48+
it('sets runtime on scope', () => {
49+
const mockScope = new Scope();
50+
init({});
51+
configureScopeCallback(mockScope);
52+
// @ts-ignore need access to protected _tags attribute
53+
expect(mockScope._tags).toEqual({ runtime: 'node' });
54+
});
55+
56+
describe('integrations', () => {
57+
it('adds RewriteFrames integration by default', () => {
58+
init({});
59+
60+
const reactInitOptions: NextjsOptions = mockInit.mock.calls[0][0];
61+
expect(reactInitOptions.integrations).toHaveLength(1);
62+
const integrations = reactInitOptions.integrations as Integration[];
63+
expect(integrations[0]).toEqual(expect.any(RewriteFrames));
64+
});
65+
66+
it('adds Http integration by default if tracesSampleRate is set', () => {
67+
init({ tracesSampleRate: 1.0 });
68+
69+
const reactInitOptions: NextjsOptions = mockInit.mock.calls[0][0];
70+
expect(reactInitOptions.integrations).toHaveLength(2);
71+
const integrations = reactInitOptions.integrations as Integration[];
72+
expect(integrations[1]).toEqual(expect.any(Integrations.Http));
73+
});
74+
75+
it('adds Http integration by default if tracesSampler is set', () => {
76+
init({ tracesSampler: () => true });
77+
78+
const reactInitOptions: NextjsOptions = mockInit.mock.calls[0][0];
79+
expect(reactInitOptions.integrations).toHaveLength(2);
80+
const integrations = reactInitOptions.integrations as Integration[];
81+
expect(integrations[1]).toEqual(expect.any(Integrations.Http));
82+
});
83+
84+
it('adds Http integration with tracing true', () => {
85+
init({ tracesSampleRate: 1.0 });
86+
const reactInitOptions: NextjsOptions = mockInit.mock.calls[0][0];
87+
expect(reactInitOptions.integrations).toHaveLength(2);
88+
89+
const integrations = reactInitOptions.integrations as Integration[];
90+
expect((integrations[1] as any)._tracing).toBe(true);
91+
});
92+
93+
it('supports passing integration through options', () => {
94+
init({ tracesSampleRate: 1.0, integrations: [new Integrations.Console()] });
95+
const reactInitOptions: NextjsOptions = mockInit.mock.calls[0][0];
96+
expect(reactInitOptions.integrations).toHaveLength(3);
97+
98+
const integrations = reactInitOptions.integrations as Integration[];
99+
expect(integrations).toEqual([
100+
expect.any(Integrations.Console),
101+
expect.any(RewriteFrames),
102+
expect.any(Integrations.Http),
103+
]);
104+
});
105+
106+
describe('custom Http integration', () => {
107+
it('sets tracing to true if tracesSampleRate is set', () => {
108+
init({
109+
tracesSampleRate: 1.0,
110+
integrations: [new Integrations.Http({ tracing: false })],
111+
});
112+
113+
const reactInitOptions: NextjsOptions = mockInit.mock.calls[0][0];
114+
expect(reactInitOptions.integrations).toHaveLength(2);
115+
const integrations = reactInitOptions.integrations as Integration[];
116+
expect(integrations[0] as InstanceType<typeof Integrations.Http>).toEqual(
117+
expect.objectContaining({ _breadcrumbs: true, _tracing: true, name: 'Http' }),
118+
);
119+
});
120+
121+
it('sets tracing to true if tracesSampler is set', () => {
122+
init({
123+
tracesSampler: () => true,
124+
integrations: [new Integrations.Http({ tracing: false })],
125+
});
126+
127+
const reactInitOptions: NextjsOptions = mockInit.mock.calls[0][0];
128+
expect(reactInitOptions.integrations).toHaveLength(2);
129+
const integrations = reactInitOptions.integrations as Integration[];
130+
expect(integrations[0] as InstanceType<typeof Integrations.Http>).toEqual(
131+
expect.objectContaining({ _breadcrumbs: true, _tracing: true, name: 'Http' }),
132+
);
133+
});
134+
});
135+
});
136+
});

0 commit comments

Comments
 (0)