Skip to content

Commit

Permalink
feat(node): Expose functional integrations to replace classes (#10356)
Browse files Browse the repository at this point in the history
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.
  • Loading branch information
mydea committed Jan 31, 2024
1 parent 6b86b3e commit 0c16f21
Show file tree
Hide file tree
Showing 43 changed files with 913 additions and 164 deletions.
67 changes: 39 additions & 28 deletions MIGRATION.md
Original file line number Diff line number Diff line change
Expand Up @@ -34,34 +34,45 @@ integrations from the `Integrations.XXX` hash, is deprecated in favor of using t

The following list shows how integrations should be migrated:

| Old | New | Packages |
| ------------------------- | -------------------------------- | ------------------------------------------------------------------------------------------------------- |
| `new InboundFilters()` | `inboundFiltersIntegration()` | `@sentry/core`, `@sentry/browser`, `@sentry/node`, `@sentry/deno`, `@sentry/bun`, `@sentry/vercel-edge` |
| `new FunctionToString()` | `functionToStringIntegration()` | `@sentry/core`, `@sentry/browser`, `@sentry/node`, `@sentry/deno`, `@sentry/bun`, `@sentry/vercel-edge` |
| `new LinkedErrors()` | `linkedErrorsIntegration()` | `@sentry/core`, `@sentry/browser`, `@sentry/node`, `@sentry/deno`, `@sentry/bun`, `@sentry/vercel-edge` |
| `new ModuleMetadata()` | `moduleMetadataIntegration()` | `@sentry/core`, `@sentry/browser` |
| `new RequestData()` | `requestDataIntegration()` | `@sentry/core`, `@sentry/node`, `@sentry/deno`, `@sentry/bun`, `@sentry/vercel-edge` |
| `new Wasm() ` | `wasmIntegration()` | `@sentry/wasm` |
| `new Replay()` | `replayIntegration()` | `@sentry/browser` |
| `new ReplayCanvas()` | `replayCanvasIntegration()` | `@sentry/browser` |
| `new Feedback()` | `feedbackIntegration()` | `@sentry/browser` |
| `new CaptureConsole()` | `captureConsoleIntegration()` | `@sentry/integrations` |
| `new Debug()` | `debugIntegration()` | `@sentry/integrations` |
| `new Dedupe()` | `dedupeIntegration()` | `@sentry/browser`, `@sentry/integrations`, `@sentry/deno` |
| `new ExtraErrorData()` | `extraErrorDataIntegration()` | `@sentry/integrations` |
| `new ReportingObserver()` | `reportingObserverIntegration()` | `@sentry/integrations` |
| `new RewriteFrames()` | `rewriteFramesIntegration()` | `@sentry/integrations` |
| `new SessionTiming()` | `sessionTimingIntegration()` | `@sentry/integrations` |
| `new HttpClient()` | `httpClientIntegration()` | `@sentry/integrations` |
| `new ContextLines()` | `contextLinesIntegration()` | `@sentry/browser`, `@sentry/deno` |
| `new Breadcrumbs()` | `breadcrumbsIntegration()` | `@sentry/browser`, `@sentry/deno` |
| `new GlobalHandlers()` | `globalHandlersIntegration()` | `@sentry/browser` , `@sentry/deno` |
| `new HttpContext()` | `httpContextIntegration()` | `@sentry/browser` |
| `new TryCatch()` | `browserApiErrorsIntegration()` | `@sentry/browser`, `@sentry/deno` |
| `new VueIntegration()` | `vueIntegration()` | `@sentry/vue` |
| `new DenoContext()` | `denoContextIntegration()` | `@sentry/deno` |
| `new DenoCron()` | `denoCronIntegration()` | `@sentry/deno` |
| `new NormalizePaths()` | `normalizePathsIntegration()` | `@sentry/deno` |
| Old | New | Packages |
| ---------------------------- | ----------------------------------- | ------------------------------------------------------------------------------------------------------- |
| `new InboundFilters()` | `inboundFiltersIntegration()` | `@sentry/core`, `@sentry/browser`, `@sentry/node`, `@sentry/deno`, `@sentry/bun`, `@sentry/vercel-edge` |
| `new FunctionToString()` | `functionToStringIntegration()` | `@sentry/core`, `@sentry/browser`, `@sentry/node`, `@sentry/deno`, `@sentry/bun`, `@sentry/vercel-edge` |
| `new LinkedErrors()` | `linkedErrorsIntegration()` | `@sentry/core`, `@sentry/browser`, `@sentry/node`, `@sentry/deno`, `@sentry/bun`, `@sentry/vercel-edge` |
| `new ModuleMetadata()` | `moduleMetadataIntegration()` | `@sentry/core`, `@sentry/browser` |
| `new RequestData()` | `requestDataIntegration()` | `@sentry/core`, `@sentry/node`, `@sentry/deno`, `@sentry/bun`, `@sentry/vercel-edge` |
| `new Wasm() ` | `wasmIntegration()` | `@sentry/wasm` |
| `new Replay()` | `replayIntegration()` | `@sentry/browser` |
| `new ReplayCanvas()` | `replayCanvasIntegration()` | `@sentry/browser` |
| `new Feedback()` | `feedbackIntegration()` | `@sentry/browser` |
| `new CaptureConsole()` | `captureConsoleIntegration()` | `@sentry/integrations` |
| `new Debug()` | `debugIntegration()` | `@sentry/integrations` |
| `new Dedupe()` | `dedupeIntegration()` | `@sentry/browser`, `@sentry/integrations`, `@sentry/deno` |
| `new ExtraErrorData()` | `extraErrorDataIntegration()` | `@sentry/integrations` |
| `new ReportingObserver()` | `reportingObserverIntegration()` | `@sentry/integrations` |
| `new RewriteFrames()` | `rewriteFramesIntegration()` | `@sentry/integrations` |
| `new SessionTiming()` | `sessionTimingIntegration()` | `@sentry/integrations` |
| `new HttpClient()` | `httpClientIntegration()` | `@sentry/integrations` |
| `new ContextLines()` | `contextLinesIntegration()` | `@sentry/browser`, `@sentry/node`, `@sentry/deno` |
| `new Breadcrumbs()` | `breadcrumbsIntegration()` | `@sentry/browser`, `@sentry/deno` |
| `new GlobalHandlers()` | `globalHandlersIntegration()` | `@sentry/browser` , `@sentry/deno` |
| `new HttpContext()` | `httpContextIntegration()` | `@sentry/browser` |
| `new TryCatch()` | `browserApiErrorsIntegration()` | `@sentry/browser`, `@sentry/deno` |
| `new VueIntegration()` | `vueIntegration()` | `@sentry/vue` |
| `new DenoContext()` | `denoContextIntegration()` | `@sentry/deno` |
| `new DenoCron()` | `denoCronIntegration()` | `@sentry/deno` |
| `new NormalizePaths()` | `normalizePathsIntegration()` | `@sentry/deno` |
| `new Console()` | `consoleIntegration()` | `@sentry/node` |
| `new Context()` | `nodeContextIntegration()` | `@sentry/node` |
| `new Modules()` | `modulesIntegration()` | `@sentry/node` |
| `new OnUncaughtException()` | `onUncaughtExceptionIntegration()` | `@sentry/node` |
| `new OnUnhandledRejection()` | `onUnhandledRejectionIntegration()` | `@sentry/node` |
| `new LocalVariables()` | `localVariablesIntegration()` | `@sentry/node` |
| `new Spotlight()` | `spotlightIntergation()` | `@sentry/node` |
| `new Anr()` | `anrIntergation()` | `@sentry/node` |
| `new Hapi()` | `hapiIntegration()` | `@sentry/node` |
| `new Undici()` | `nativeNodeFetchIntegration()` | `@sentry/node` |
| `new Http()` | `httpIntegration()` | `@sentry/node` |

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

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
import '@sentry/tracing';

import * as http from 'http';
import * as Sentry from '@sentry/node';

Sentry.init({
dsn: 'https://public@dsn.ingest.sentry.io/1337',
release: '1.0',
tracesSampleRate: 1.0,
integrations: [Sentry.httpIntegration({})],
debug: true,
});

// eslint-disable-next-line @typescript-eslint/no-floating-promises
Sentry.startSpan({ name: 'test_transaction' }, async () => {
http.get('http://match-this-url.com/api/v0');
http.get('http://match-this-url.com/api/v1');

// Give it a tick to resolve...
await new Promise(resolve => setTimeout(resolve, 100));
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
import nock from 'nock';

import { TestEnv, assertSentryTransaction } from '../../../../utils';

test('should capture spans for outgoing http requests', async () => {
const match1 = nock('http://match-this-url.com').get('/api/v0').reply(200);
const match2 = nock('http://match-this-url.com').get('/api/v1').reply(200);

const env = await TestEnv.init(__dirname);
const envelope = await env.getEnvelopeRequest({ envelopeType: 'transaction' });

expect(match1.isDone()).toBe(true);
expect(match2.isDone()).toBe(true);

expect(envelope).toHaveLength(3);

assertSentryTransaction(envelope[2], {
transaction: 'test_transaction',
spans: [
{
description: 'GET http://match-this-url.com/api/v0',
op: 'http.client',
origin: 'auto.http.node.http',
status: 'ok',
tags: {
'http.status_code': '200',
},
},
{
description: 'GET http://match-this-url.com/api/v1',
op: 'http.client',
origin: 'auto.http.node.http',
status: 'ok',
tags: {
'http.status_code': '200',
},
},
],
});
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
import '@sentry/tracing';

import * as http from 'http';
import * as Sentry from '@sentry/node';

Sentry.init({
dsn: 'https://public@dsn.ingest.sentry.io/1337',
release: '1.0',
tracesSampleRate: 1.0,
integrations: [Sentry.httpIntegration({ tracing: false })],
});

// eslint-disable-next-line @typescript-eslint/no-floating-promises
Sentry.startSpan({ name: 'test_transaction' }, async () => {
http.get('http://match-this-url.com/api/v0');
http.get('http://match-this-url.com/api/v1');

// Give it a tick to resolve...
await new Promise(resolve => setTimeout(resolve, 100));
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
import nock from 'nock';

import { TestEnv, assertSentryTransaction } from '../../../../utils';

test('should not capture spans for outgoing http requests if tracing is disabled', async () => {
const match1 = nock('http://match-this-url.com').get('/api/v0').reply(200);
const match2 = nock('http://match-this-url.com').get('/api/v1').reply(200);

const env = await TestEnv.init(__dirname);
const envelope = await env.getEnvelopeRequest({ envelopeType: 'transaction' });

expect(match1.isDone()).toBe(true);
expect(match2.isDone()).toBe(true);

expect(envelope).toHaveLength(3);

assertSentryTransaction(envelope[2], {
transaction: 'test_transaction',
spans: [],
});
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
// eslint-disable-next-line @typescript-eslint/no-unused-vars
import '@sentry/tracing';

import * as http from 'http';
import * as Sentry from '@sentry/node';

Sentry.init({
dsn: 'https://public@dsn.ingest.sentry.io/1337',
release: '1.0',
tracesSampleRate: 1.0,
tracePropagationTargets: [/\/v0/, 'v1'],
integrations: [Sentry.httpIntegration({})],
});

Sentry.startSpan({ name: 'test_transaction' }, () => {
http.get('http://match-this-url.com/api/v0');
http.get('http://match-this-url.com/api/v1');
http.get('http://dont-match-this-url.com/api/v2');
http.get('http://dont-match-this-url.com/api/v3');
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
import nock from 'nock';

import { TestEnv, runScenario } from '../../../../utils';

test('httpIntegration should instrument correct requests when tracePropagationTargets option is provided & tracing is enabled', async () => {
const match1 = nock('http://match-this-url.com')
.get('/api/v0')
.matchHeader('baggage', val => typeof val === 'string')
.matchHeader('sentry-trace', val => typeof val === 'string')
.reply(200);

const match2 = nock('http://match-this-url.com')
.get('/api/v1')
.matchHeader('baggage', val => typeof val === 'string')
.matchHeader('sentry-trace', val => typeof val === 'string')
.reply(200);

const match3 = nock('http://dont-match-this-url.com')
.get('/api/v2')
.matchHeader('baggage', val => val === undefined)
.matchHeader('sentry-trace', val => val === undefined)
.reply(200);

const match4 = nock('http://dont-match-this-url.com')
.get('/api/v3')
.matchHeader('baggage', val => val === undefined)
.matchHeader('sentry-trace', val => val === undefined)
.reply(200);

const env = await TestEnv.init(__dirname);
await runScenario(env.url);

env.server.close();
nock.cleanAll();

await new Promise(resolve => env.server.close(resolve));

expect(match1.isDone()).toBe(true);
expect(match2.isDone()).toBe(true);
expect(match3.isDone()).toBe(true);
expect(match4.isDone()).toBe(true);
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
// eslint-disable-next-line @typescript-eslint/no-unused-vars
import '@sentry/tracing';

import * as http from 'http';
import * as Sentry from '@sentry/node';

Sentry.init({
dsn: 'https://public@dsn.ingest.sentry.io/1337',
release: '1.0',
tracePropagationTargets: [/\/v0/, 'v1'],
integrations: [Sentry.httpIntegration({})],
});

Sentry.startSpan({ name: 'test_transaction' }, () => {
http.get('http://match-this-url.com/api/v0');
http.get('http://match-this-url.com/api/v1');
http.get('http://dont-match-this-url.com/api/v2');
http.get('http://dont-match-this-url.com/api/v3');
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
import nock from 'nock';

import { TestEnv, runScenario } from '../../../../utils';

test('httpIntegration should not instrument when tracing is enabled', async () => {
const match1 = nock('http://match-this-url.com')
.get('/api/v0')
.matchHeader('baggage', val => val === undefined)
.matchHeader('sentry-trace', val => val === undefined)
.reply(200);

const match2 = nock('http://match-this-url.com')
.get('/api/v1')
.matchHeader('baggage', val => val === undefined)
.matchHeader('sentry-trace', val => val === undefined)
.reply(200);

const match3 = nock('http://dont-match-this-url.com')
.get('/api/v2')
.matchHeader('baggage', val => val === undefined)
.matchHeader('sentry-trace', val => val === undefined)
.reply(200);

const match4 = nock('http://dont-match-this-url.com')
.get('/api/v3')
.matchHeader('baggage', val => val === undefined)
.matchHeader('sentry-trace', val => val === undefined)
.reply(200);

const env = await TestEnv.init(__dirname);
await runScenario(env.url);

env.server.close();
nock.cleanAll();

await new Promise(resolve => env.server.close(resolve));

expect(match1.isDone()).toBe(true);
expect(match2.isDone()).toBe(true);
expect(match3.isDone()).toBe(true);
expect(match4.isDone()).toBe(true);
});
11 changes: 11 additions & 0 deletions packages/astro/src/index.server.ts
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,17 @@ export {
// eslint-disable-next-line deprecation/deprecation
deepReadDirSync,
Integrations,
consoleIntegration,
onUncaughtExceptionIntegration,
onUnhandledRejectionIntegration,
modulesIntegration,
contextLinesIntegration,
nodeContextIntegration,
localVariablesIntegration,
requestDataIntegration,
functionToStringIntegration,
inboundFiltersIntegration,
linkedErrorsIntegration,
Handlers,
setMeasurement,
getActiveSpan,
Expand Down
Loading

0 comments on commit 0c16f21

Please sign in to comment.