Skip to content

feat(node): Expose functional integrations to replace classes #10356

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 5 commits into from
Jan 31, 2024
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
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` |
Comment on lines +65 to +75
Copy link
Member

@Lms24 Lms24 Jan 30, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think we need to do a better job to call out that these integration chanes also apply to all/most @sentry/node-based packages (serverless, fullstack framework SDKs). Doesn't strictly have to happen now but I'd say we should add them. Probably simplest to list them individually (also, they slightly differ per integration :( )

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

yeah, I thought about this too but this makes the table veeery unwieldly 😓 not sure, maybe we just point it out above? E.g. node also applies to next, remix, sveltekit, ..., browser also applies to react, ...?


## 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://[email protected]/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://[email protected]/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://[email protected]/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://[email protected]/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