Skip to content

Commit 0c16f21

Browse files
authored
feat(node): Expose functional integrations to replace classes (#10356)
This updates the general (non-tracing) node integrations to be functional. For node & undici, the replacements are slightly different: * `new Http()` --> `httpIntegration()`: In contrast to the class integration, this will create spans by default if tracing is enabled. While at it, this also "fixes" that if `tracing: false` is set, no spans will be created. * `new Undici()` --> `nativeNodeFetchIntegration()`: Renamed this for consistency, and added a `tracing` option similar to http to allow to disable span creation. We can't really deprecate `Integrations.xxx` yet until we have replacements for the tracing integrations 😬 So that would also be a todo left.
1 parent 6b86b3e commit 0c16f21

File tree

43 files changed

+913
-164
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

43 files changed

+913
-164
lines changed

MIGRATION.md

Lines changed: 39 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -34,34 +34,45 @@ integrations from the `Integrations.XXX` hash, is deprecated in favor of using t
3434

3535
The following list shows how integrations should be migrated:
3636

37-
| Old | New | Packages |
38-
| ------------------------- | -------------------------------- | ------------------------------------------------------------------------------------------------------- |
39-
| `new InboundFilters()` | `inboundFiltersIntegration()` | `@sentry/core`, `@sentry/browser`, `@sentry/node`, `@sentry/deno`, `@sentry/bun`, `@sentry/vercel-edge` |
40-
| `new FunctionToString()` | `functionToStringIntegration()` | `@sentry/core`, `@sentry/browser`, `@sentry/node`, `@sentry/deno`, `@sentry/bun`, `@sentry/vercel-edge` |
41-
| `new LinkedErrors()` | `linkedErrorsIntegration()` | `@sentry/core`, `@sentry/browser`, `@sentry/node`, `@sentry/deno`, `@sentry/bun`, `@sentry/vercel-edge` |
42-
| `new ModuleMetadata()` | `moduleMetadataIntegration()` | `@sentry/core`, `@sentry/browser` |
43-
| `new RequestData()` | `requestDataIntegration()` | `@sentry/core`, `@sentry/node`, `@sentry/deno`, `@sentry/bun`, `@sentry/vercel-edge` |
44-
| `new Wasm() ` | `wasmIntegration()` | `@sentry/wasm` |
45-
| `new Replay()` | `replayIntegration()` | `@sentry/browser` |
46-
| `new ReplayCanvas()` | `replayCanvasIntegration()` | `@sentry/browser` |
47-
| `new Feedback()` | `feedbackIntegration()` | `@sentry/browser` |
48-
| `new CaptureConsole()` | `captureConsoleIntegration()` | `@sentry/integrations` |
49-
| `new Debug()` | `debugIntegration()` | `@sentry/integrations` |
50-
| `new Dedupe()` | `dedupeIntegration()` | `@sentry/browser`, `@sentry/integrations`, `@sentry/deno` |
51-
| `new ExtraErrorData()` | `extraErrorDataIntegration()` | `@sentry/integrations` |
52-
| `new ReportingObserver()` | `reportingObserverIntegration()` | `@sentry/integrations` |
53-
| `new RewriteFrames()` | `rewriteFramesIntegration()` | `@sentry/integrations` |
54-
| `new SessionTiming()` | `sessionTimingIntegration()` | `@sentry/integrations` |
55-
| `new HttpClient()` | `httpClientIntegration()` | `@sentry/integrations` |
56-
| `new ContextLines()` | `contextLinesIntegration()` | `@sentry/browser`, `@sentry/deno` |
57-
| `new Breadcrumbs()` | `breadcrumbsIntegration()` | `@sentry/browser`, `@sentry/deno` |
58-
| `new GlobalHandlers()` | `globalHandlersIntegration()` | `@sentry/browser` , `@sentry/deno` |
59-
| `new HttpContext()` | `httpContextIntegration()` | `@sentry/browser` |
60-
| `new TryCatch()` | `browserApiErrorsIntegration()` | `@sentry/browser`, `@sentry/deno` |
61-
| `new VueIntegration()` | `vueIntegration()` | `@sentry/vue` |
62-
| `new DenoContext()` | `denoContextIntegration()` | `@sentry/deno` |
63-
| `new DenoCron()` | `denoCronIntegration()` | `@sentry/deno` |
64-
| `new NormalizePaths()` | `normalizePathsIntegration()` | `@sentry/deno` |
37+
| Old | New | Packages |
38+
| ---------------------------- | ----------------------------------- | ------------------------------------------------------------------------------------------------------- |
39+
| `new InboundFilters()` | `inboundFiltersIntegration()` | `@sentry/core`, `@sentry/browser`, `@sentry/node`, `@sentry/deno`, `@sentry/bun`, `@sentry/vercel-edge` |
40+
| `new FunctionToString()` | `functionToStringIntegration()` | `@sentry/core`, `@sentry/browser`, `@sentry/node`, `@sentry/deno`, `@sentry/bun`, `@sentry/vercel-edge` |
41+
| `new LinkedErrors()` | `linkedErrorsIntegration()` | `@sentry/core`, `@sentry/browser`, `@sentry/node`, `@sentry/deno`, `@sentry/bun`, `@sentry/vercel-edge` |
42+
| `new ModuleMetadata()` | `moduleMetadataIntegration()` | `@sentry/core`, `@sentry/browser` |
43+
| `new RequestData()` | `requestDataIntegration()` | `@sentry/core`, `@sentry/node`, `@sentry/deno`, `@sentry/bun`, `@sentry/vercel-edge` |
44+
| `new Wasm() ` | `wasmIntegration()` | `@sentry/wasm` |
45+
| `new Replay()` | `replayIntegration()` | `@sentry/browser` |
46+
| `new ReplayCanvas()` | `replayCanvasIntegration()` | `@sentry/browser` |
47+
| `new Feedback()` | `feedbackIntegration()` | `@sentry/browser` |
48+
| `new CaptureConsole()` | `captureConsoleIntegration()` | `@sentry/integrations` |
49+
| `new Debug()` | `debugIntegration()` | `@sentry/integrations` |
50+
| `new Dedupe()` | `dedupeIntegration()` | `@sentry/browser`, `@sentry/integrations`, `@sentry/deno` |
51+
| `new ExtraErrorData()` | `extraErrorDataIntegration()` | `@sentry/integrations` |
52+
| `new ReportingObserver()` | `reportingObserverIntegration()` | `@sentry/integrations` |
53+
| `new RewriteFrames()` | `rewriteFramesIntegration()` | `@sentry/integrations` |
54+
| `new SessionTiming()` | `sessionTimingIntegration()` | `@sentry/integrations` |
55+
| `new HttpClient()` | `httpClientIntegration()` | `@sentry/integrations` |
56+
| `new ContextLines()` | `contextLinesIntegration()` | `@sentry/browser`, `@sentry/node`, `@sentry/deno` |
57+
| `new Breadcrumbs()` | `breadcrumbsIntegration()` | `@sentry/browser`, `@sentry/deno` |
58+
| `new GlobalHandlers()` | `globalHandlersIntegration()` | `@sentry/browser` , `@sentry/deno` |
59+
| `new HttpContext()` | `httpContextIntegration()` | `@sentry/browser` |
60+
| `new TryCatch()` | `browserApiErrorsIntegration()` | `@sentry/browser`, `@sentry/deno` |
61+
| `new VueIntegration()` | `vueIntegration()` | `@sentry/vue` |
62+
| `new DenoContext()` | `denoContextIntegration()` | `@sentry/deno` |
63+
| `new DenoCron()` | `denoCronIntegration()` | `@sentry/deno` |
64+
| `new NormalizePaths()` | `normalizePathsIntegration()` | `@sentry/deno` |
65+
| `new Console()` | `consoleIntegration()` | `@sentry/node` |
66+
| `new Context()` | `nodeContextIntegration()` | `@sentry/node` |
67+
| `new Modules()` | `modulesIntegration()` | `@sentry/node` |
68+
| `new OnUncaughtException()` | `onUncaughtExceptionIntegration()` | `@sentry/node` |
69+
| `new OnUnhandledRejection()` | `onUnhandledRejectionIntegration()` | `@sentry/node` |
70+
| `new LocalVariables()` | `localVariablesIntegration()` | `@sentry/node` |
71+
| `new Spotlight()` | `spotlightIntergation()` | `@sentry/node` |
72+
| `new Anr()` | `anrIntergation()` | `@sentry/node` |
73+
| `new Hapi()` | `hapiIntegration()` | `@sentry/node` |
74+
| `new Undici()` | `nativeNodeFetchIntegration()` | `@sentry/node` |
75+
| `new Http()` | `httpIntegration()` | `@sentry/node` |
6576

6677
## Deprecate `hub.bindClient()` and `makeMain()`
6778

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
import '@sentry/tracing';
2+
3+
import * as http from 'http';
4+
import * as Sentry from '@sentry/node';
5+
6+
Sentry.init({
7+
dsn: 'https://[email protected]/1337',
8+
release: '1.0',
9+
tracesSampleRate: 1.0,
10+
integrations: [Sentry.httpIntegration({})],
11+
debug: true,
12+
});
13+
14+
// eslint-disable-next-line @typescript-eslint/no-floating-promises
15+
Sentry.startSpan({ name: 'test_transaction' }, async () => {
16+
http.get('http://match-this-url.com/api/v0');
17+
http.get('http://match-this-url.com/api/v1');
18+
19+
// Give it a tick to resolve...
20+
await new Promise(resolve => setTimeout(resolve, 100));
21+
});
Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
import nock from 'nock';
2+
3+
import { TestEnv, assertSentryTransaction } from '../../../../utils';
4+
5+
test('should capture spans for outgoing http requests', async () => {
6+
const match1 = nock('http://match-this-url.com').get('/api/v0').reply(200);
7+
const match2 = nock('http://match-this-url.com').get('/api/v1').reply(200);
8+
9+
const env = await TestEnv.init(__dirname);
10+
const envelope = await env.getEnvelopeRequest({ envelopeType: 'transaction' });
11+
12+
expect(match1.isDone()).toBe(true);
13+
expect(match2.isDone()).toBe(true);
14+
15+
expect(envelope).toHaveLength(3);
16+
17+
assertSentryTransaction(envelope[2], {
18+
transaction: 'test_transaction',
19+
spans: [
20+
{
21+
description: 'GET http://match-this-url.com/api/v0',
22+
op: 'http.client',
23+
origin: 'auto.http.node.http',
24+
status: 'ok',
25+
tags: {
26+
'http.status_code': '200',
27+
},
28+
},
29+
{
30+
description: 'GET http://match-this-url.com/api/v1',
31+
op: 'http.client',
32+
origin: 'auto.http.node.http',
33+
status: 'ok',
34+
tags: {
35+
'http.status_code': '200',
36+
},
37+
},
38+
],
39+
});
40+
});
Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
import '@sentry/tracing';
2+
3+
import * as http from 'http';
4+
import * as Sentry from '@sentry/node';
5+
6+
Sentry.init({
7+
dsn: 'https://[email protected]/1337',
8+
release: '1.0',
9+
tracesSampleRate: 1.0,
10+
integrations: [Sentry.httpIntegration({ tracing: false })],
11+
});
12+
13+
// eslint-disable-next-line @typescript-eslint/no-floating-promises
14+
Sentry.startSpan({ name: 'test_transaction' }, async () => {
15+
http.get('http://match-this-url.com/api/v0');
16+
http.get('http://match-this-url.com/api/v1');
17+
18+
// Give it a tick to resolve...
19+
await new Promise(resolve => setTimeout(resolve, 100));
20+
});
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
import nock from 'nock';
2+
3+
import { TestEnv, assertSentryTransaction } from '../../../../utils';
4+
5+
test('should not capture spans for outgoing http requests if tracing is disabled', async () => {
6+
const match1 = nock('http://match-this-url.com').get('/api/v0').reply(200);
7+
const match2 = nock('http://match-this-url.com').get('/api/v1').reply(200);
8+
9+
const env = await TestEnv.init(__dirname);
10+
const envelope = await env.getEnvelopeRequest({ envelopeType: 'transaction' });
11+
12+
expect(match1.isDone()).toBe(true);
13+
expect(match2.isDone()).toBe(true);
14+
15+
expect(envelope).toHaveLength(3);
16+
17+
assertSentryTransaction(envelope[2], {
18+
transaction: 'test_transaction',
19+
spans: [],
20+
});
21+
});
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
// eslint-disable-next-line @typescript-eslint/no-unused-vars
2+
import '@sentry/tracing';
3+
4+
import * as http from 'http';
5+
import * as Sentry from '@sentry/node';
6+
7+
Sentry.init({
8+
dsn: 'https://[email protected]/1337',
9+
release: '1.0',
10+
tracesSampleRate: 1.0,
11+
tracePropagationTargets: [/\/v0/, 'v1'],
12+
integrations: [Sentry.httpIntegration({})],
13+
});
14+
15+
Sentry.startSpan({ name: 'test_transaction' }, () => {
16+
http.get('http://match-this-url.com/api/v0');
17+
http.get('http://match-this-url.com/api/v1');
18+
http.get('http://dont-match-this-url.com/api/v2');
19+
http.get('http://dont-match-this-url.com/api/v3');
20+
});
Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
import nock from 'nock';
2+
3+
import { TestEnv, runScenario } from '../../../../utils';
4+
5+
test('httpIntegration should instrument correct requests when tracePropagationTargets option is provided & tracing is enabled', async () => {
6+
const match1 = nock('http://match-this-url.com')
7+
.get('/api/v0')
8+
.matchHeader('baggage', val => typeof val === 'string')
9+
.matchHeader('sentry-trace', val => typeof val === 'string')
10+
.reply(200);
11+
12+
const match2 = nock('http://match-this-url.com')
13+
.get('/api/v1')
14+
.matchHeader('baggage', val => typeof val === 'string')
15+
.matchHeader('sentry-trace', val => typeof val === 'string')
16+
.reply(200);
17+
18+
const match3 = nock('http://dont-match-this-url.com')
19+
.get('/api/v2')
20+
.matchHeader('baggage', val => val === undefined)
21+
.matchHeader('sentry-trace', val => val === undefined)
22+
.reply(200);
23+
24+
const match4 = nock('http://dont-match-this-url.com')
25+
.get('/api/v3')
26+
.matchHeader('baggage', val => val === undefined)
27+
.matchHeader('sentry-trace', val => val === undefined)
28+
.reply(200);
29+
30+
const env = await TestEnv.init(__dirname);
31+
await runScenario(env.url);
32+
33+
env.server.close();
34+
nock.cleanAll();
35+
36+
await new Promise(resolve => env.server.close(resolve));
37+
38+
expect(match1.isDone()).toBe(true);
39+
expect(match2.isDone()).toBe(true);
40+
expect(match3.isDone()).toBe(true);
41+
expect(match4.isDone()).toBe(true);
42+
});
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
// eslint-disable-next-line @typescript-eslint/no-unused-vars
2+
import '@sentry/tracing';
3+
4+
import * as http from 'http';
5+
import * as Sentry from '@sentry/node';
6+
7+
Sentry.init({
8+
dsn: 'https://[email protected]/1337',
9+
release: '1.0',
10+
tracePropagationTargets: [/\/v0/, 'v1'],
11+
integrations: [Sentry.httpIntegration({})],
12+
});
13+
14+
Sentry.startSpan({ name: 'test_transaction' }, () => {
15+
http.get('http://match-this-url.com/api/v0');
16+
http.get('http://match-this-url.com/api/v1');
17+
http.get('http://dont-match-this-url.com/api/v2');
18+
http.get('http://dont-match-this-url.com/api/v3');
19+
});
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
import nock from 'nock';
2+
3+
import { TestEnv, runScenario } from '../../../../utils';
4+
5+
test('httpIntegration should not instrument when tracing is enabled', async () => {
6+
const match1 = nock('http://match-this-url.com')
7+
.get('/api/v0')
8+
.matchHeader('baggage', val => val === undefined)
9+
.matchHeader('sentry-trace', val => val === undefined)
10+
.reply(200);
11+
12+
const match2 = nock('http://match-this-url.com')
13+
.get('/api/v1')
14+
.matchHeader('baggage', val => val === undefined)
15+
.matchHeader('sentry-trace', val => val === undefined)
16+
.reply(200);
17+
18+
const match3 = nock('http://dont-match-this-url.com')
19+
.get('/api/v2')
20+
.matchHeader('baggage', val => val === undefined)
21+
.matchHeader('sentry-trace', val => val === undefined)
22+
.reply(200);
23+
24+
const match4 = nock('http://dont-match-this-url.com')
25+
.get('/api/v3')
26+
.matchHeader('baggage', val => val === undefined)
27+
.matchHeader('sentry-trace', val => val === undefined)
28+
.reply(200);
29+
30+
const env = await TestEnv.init(__dirname);
31+
await runScenario(env.url);
32+
33+
env.server.close();
34+
nock.cleanAll();
35+
36+
await new Promise(resolve => env.server.close(resolve));
37+
38+
expect(match1.isDone()).toBe(true);
39+
expect(match2.isDone()).toBe(true);
40+
expect(match3.isDone()).toBe(true);
41+
expect(match4.isDone()).toBe(true);
42+
});

packages/astro/src/index.server.ts

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -70,6 +70,17 @@ export {
7070
// eslint-disable-next-line deprecation/deprecation
7171
deepReadDirSync,
7272
Integrations,
73+
consoleIntegration,
74+
onUncaughtExceptionIntegration,
75+
onUnhandledRejectionIntegration,
76+
modulesIntegration,
77+
contextLinesIntegration,
78+
nodeContextIntegration,
79+
localVariablesIntegration,
80+
requestDataIntegration,
81+
functionToStringIntegration,
82+
inboundFiltersIntegration,
83+
linkedErrorsIntegration,
7384
Handlers,
7485
setMeasurement,
7586
getActiveSpan,

0 commit comments

Comments
 (0)