From ce18641656680bfdcc71a51eebf1dea10ca4d16b Mon Sep 17 00:00:00 2001 From: Francesco Novy Date: Wed, 5 Feb 2025 10:33:44 +0100 Subject: [PATCH] feat(core): Allow to configure `disableIntegrations` to opt-out This new top-level config allows to opt-out of any added integration, ensuring it is not actually added. This is mainly designed to opt-out of default integrations, but will also apply to any manually added integration. There are type hints that should help with usage there, but any key is allowed to keep this flexible. Type hints are manually configured (could not find a way to infer this from integrations, as the names are not statically exposed there...) for now. Usage: ```js Sentry.init({ disableIntegrations: { BrowserSession: true, InboundFilters: false } }); ``` Will disable the browserSessionIntegration but not the inboundFilters one. --- packages/aws-serverless/src/sdk.ts | 2 +- packages/browser/src/client.ts | 17 +++++- packages/browser/src/sdk.ts | 2 + packages/bun/src/sdk.ts | 5 +- packages/bun/src/types.ts | 21 ++++++- packages/cloudflare/src/client.ts | 7 ++- packages/cloudflare/src/sdk.ts | 5 +- packages/core/src/integration.ts | 8 ++- packages/core/src/types-hoist/integration.ts | 7 +++ packages/core/src/types-hoist/options.ts | 19 ++++++- packages/core/test/lib/integration.test.ts | 57 +++++++++++++++++++ packages/deno/src/sdk.ts | 9 ++- packages/deno/src/types.ts | 17 +++++- packages/google-cloud-serverless/src/sdk.ts | 2 +- packages/nestjs/src/sdk.ts | 2 +- packages/nextjs/src/client/index.ts | 4 +- .../node/src/integrations/tracing/index.ts | 2 + .../integrations/tracing/vercelai/index.ts | 2 +- packages/node/src/sdk/index.ts | 2 + packages/node/src/types.ts | 43 +++++++++++++- packages/vercel-edge/src/sdk.ts | 9 ++- packages/vercel-edge/src/types.ts | 7 ++- packages/vue/src/types.ts | 2 +- 23 files changed, 226 insertions(+), 25 deletions(-) diff --git a/packages/aws-serverless/src/sdk.ts b/packages/aws-serverless/src/sdk.ts index 34921d24c7ff..23706f0a4463 100644 --- a/packages/aws-serverless/src/sdk.ts +++ b/packages/aws-serverless/src/sdk.ts @@ -71,7 +71,7 @@ export function getDefaultIntegrations(_options: Options): Integration[] { * * @param options Configuration options for the SDK, @see {@link AWSLambdaOptions}. */ -export function init(options: NodeOptions = {}): NodeClient | undefined { +export function init(options: NodeOptions<['Aws', 'AwsLambda']> = {}): NodeClient | undefined { const opts = { _metadata: {} as SdkMetadata, defaultIntegrations: getDefaultIntegrations(options), diff --git a/packages/browser/src/client.ts b/packages/browser/src/client.ts index 0e5b3fb6214c..91f6aac3ed8c 100644 --- a/packages/browser/src/client.ts +++ b/packages/browser/src/client.ts @@ -24,7 +24,22 @@ import type { BrowserTransportOptions } from './transports/types'; * Configuration options for the Sentry Browser SDK. * @see @sentry/core Options for more information. */ -export type BrowserOptions = Options & +export type BrowserOptions = Options< + BrowserTransportOptions, + [ + 'InboundFilters', + 'FunctionToString', + 'BrowserApiErrors', + 'Breadcrumbs', + 'GlobalHandlers', + 'LinkedErrors', + 'Dedupe', + 'HttpContext', + 'BrowserSession', + 'BrowserTracing', + ...AdditionalDefaultIntegrations, + ] +> & BrowserClientReplayOptions & BrowserClientProfilingOptions & { /** diff --git a/packages/browser/src/sdk.ts b/packages/browser/src/sdk.ts index 36dcade62859..9d5f931b11b8 100644 --- a/packages/browser/src/sdk.ts +++ b/packages/browser/src/sdk.ts @@ -32,6 +32,8 @@ export function getDefaultIntegrations(_options: Options): Integration[] { /** * Note: Please make sure this stays in sync with Angular SDK, which re-exports * `getDefaultIntegrations` but with an adjusted set of integrations. + * + * Ensure to keep this in sync with `BrowserOptions`! */ return [ inboundFiltersIntegration(), diff --git a/packages/bun/src/sdk.ts b/packages/bun/src/sdk.ts index 8bded2d492af..385ba164f19c 100644 --- a/packages/bun/src/sdk.ts +++ b/packages/bun/src/sdk.ts @@ -23,7 +23,10 @@ import { bunServerIntegration } from './integrations/bunserver'; import { makeFetchTransport } from './transports'; import type { BunOptions } from './types'; -/** Get the default integrations for the Bun SDK. */ +/** + * Get the default integrations for the Bun SDK. + * Ensure to keep this in sync with `BunOptions`! + */ export function getDefaultIntegrations(_options: Options): Integration[] { // We return a copy of the defaultIntegrations here to avoid mutating this return [ diff --git a/packages/bun/src/types.ts b/packages/bun/src/types.ts index fa0e2171214f..cea7ef4fc16c 100644 --- a/packages/bun/src/types.ts +++ b/packages/bun/src/types.ts @@ -41,7 +41,26 @@ export interface BaseBunOptions { * Configuration options for the Sentry Bun SDK * @see @sentry/core Options for more information. */ -export interface BunOptions extends Options, BaseBunOptions {} +export interface BunOptions + extends Options< + BunTransportOptions, + [ + 'InboundFilters', + 'FunctionToString', + 'LinkedErrors', + 'RequestData', + 'Console', + 'Http', + 'NodeFetch', + 'OnUncaughtException', + 'OnUnhandledRejection', + 'ContextLines', + 'Context', + 'Modules', + 'BunServer', + ] + >, + BaseBunOptions {} /** * Configuration options for the Sentry Bun SDK Client class diff --git a/packages/cloudflare/src/client.ts b/packages/cloudflare/src/client.ts index dd8a0cccdec8..60e72bca52f3 100644 --- a/packages/cloudflare/src/client.ts +++ b/packages/cloudflare/src/client.ts @@ -37,7 +37,12 @@ interface BaseCloudflareOptions {} * * @see @sentry/core Options for more information. */ -export interface CloudflareOptions extends Options, BaseCloudflareOptions {} +export interface CloudflareOptions + extends Options< + CloudflareTransportOptions, + ['Dedupe', 'InboundFilters', 'FunctionToString', 'LinkedErrors', 'Fetch', 'RequestData'] + >, + BaseCloudflareOptions {} /** * Configuration options for the Sentry Cloudflare SDK Client class diff --git a/packages/cloudflare/src/sdk.ts b/packages/cloudflare/src/sdk.ts index 89f3fe99d050..5af758703a4c 100644 --- a/packages/cloudflare/src/sdk.ts +++ b/packages/cloudflare/src/sdk.ts @@ -15,7 +15,10 @@ import { fetchIntegration } from './integrations/fetch'; import { makeCloudflareTransport } from './transport'; import { defaultStackParser } from './vendor/stacktrace'; -/** Get the default integrations for the Cloudflare SDK. */ +/** + * Get the default integrations for the Cloudflare SDK. + * Ensure to keep this in sync with `CloudflareOptions`! + */ export function getDefaultIntegrations(options: CloudflareOptions): Integration[] { const sendDefaultPii = options.sendDefaultPii ?? false; return [ diff --git a/packages/core/src/integration.ts b/packages/core/src/integration.ts index 31d5426d4fbc..152d011b0852 100644 --- a/packages/core/src/integration.ts +++ b/packages/core/src/integration.ts @@ -40,9 +40,12 @@ function filterDuplicates(integrations: Integration[]): Integration[] { } /** Gets integrations to install */ -export function getIntegrationsToSetup(options: Pick): Integration[] { +export function getIntegrationsToSetup( + options: Pick, +): Integration[] { const defaultIntegrations = options.defaultIntegrations || []; const userIntegrations = options.integrations; + const disableIntegrations = options.disableIntegrations || {}; // We flag default instances, so that later we can tell them apart from any user-created instances of the same class defaultIntegrations.forEach((integration: IntegrationWithDefaultInstance) => { @@ -60,6 +63,9 @@ export function getIntegrationsToSetup(options: Pick !disableIntegrations[integration.name]); + return filterDuplicates(integrations); } diff --git a/packages/core/src/types-hoist/integration.ts b/packages/core/src/types-hoist/integration.ts index cc9e4bc580ce..11fbaf6a9681 100644 --- a/packages/core/src/types-hoist/integration.ts +++ b/packages/core/src/types-hoist/integration.ts @@ -48,3 +48,10 @@ export interface Integration { * This is expected to return an integration. */ export type IntegrationFn = (...rest: any[]) => IntegrationType; + +/** + * A map of integration names to true/false. + */ +export type IntegrationsMapping = Record & { + [key in KnownIntegrationNames[number]]?: boolean | undefined; +}; diff --git a/packages/core/src/types-hoist/options.ts b/packages/core/src/types-hoist/options.ts index 8e52b32eacf7..05929c3119e7 100644 --- a/packages/core/src/types-hoist/options.ts +++ b/packages/core/src/types-hoist/options.ts @@ -1,7 +1,7 @@ import type { CaptureContext } from '../scope'; import type { Breadcrumb, BreadcrumbHint } from './breadcrumb'; import type { ErrorEvent, EventHint, TransactionEvent } from './event'; -import type { Integration } from './integration'; +import type { Integration, IntegrationsMapping } from './integration'; import type { TracesSamplerSamplingContext } from './samplingcontext'; import type { SdkMetadata } from './sdkmetadata'; import type { SpanJSON } from './span'; @@ -302,14 +302,27 @@ export interface ClientOptions - extends Omit>, 'integrations' | 'transport' | 'stackParser'> { +export interface Options< + TO extends BaseTransportOptions = BaseTransportOptions, + DefaultIntegrationNames extends string[] = [], +> extends Omit>, 'integrations' | 'transport' | 'stackParser'> { /** * If this is set to false, default integrations will not be added, otherwise this will internally be set to the * recommended default integrations. */ defaultIntegrations?: false | Integration[]; + /** + * Pass a map of integrations that should be explicitly disabled. + * This allows you to e.g. opt out of default integrations easily. + * For example, if you do not want to add the `inboundFiltersIntegration`, you can configure: + * + * ```js + * disableIntegrations: { InboundFilters: true } + * ``` + */ + disableIntegrations?: IntegrationsMapping; + /** * List of integrations that should be installed after SDK was initialized. * Accepts either a list of integrations or a function that receives diff --git a/packages/core/test/lib/integration.test.ts b/packages/core/test/lib/integration.test.ts index aa4be2432699..ea241819b770 100644 --- a/packages/core/test/lib/integration.test.ts +++ b/packages/core/test/lib/integration.test.ts @@ -189,6 +189,63 @@ describe('getIntegrationsToSetup', () => { }); }); + describe('disableIntegrations', () => { + it('works without integrations', () => { + const integrations = getIntegrationsToSetup({ + integrations: [], + disableIntegrations: {}, + }); + + expect(integrations.map(i => i.name)).toEqual([]); + }); + + it('ignores unknown integration names', () => { + const integrations = getIntegrationsToSetup({ + integrations: [new MockIntegration('foo'), new MockIntegration('bar')], + disableIntegrations: { + foo2: true, + bar2: true, + }, + }); + + expect(integrations.map(i => i.name)).toEqual(['foo', 'bar']); + }); + + it('removes default integrations', () => { + const integrations = getIntegrationsToSetup({ + defaultIntegrations: [new MockIntegration('foo'), new MockIntegration('bar'), new MockIntegration('baz')], + disableIntegrations: { + bar: true, + }, + }); + + expect(integrations.map(i => i.name)).toEqual(['foo', 'baz']); + }); + + it('removes default integrations', () => { + const integrations = getIntegrationsToSetup({ + defaultIntegrations: [new MockIntegration('foo'), new MockIntegration('bar'), new MockIntegration('baz')], + disableIntegrations: { + bar: true, + }, + }); + + expect(integrations.map(i => i.name)).toEqual(['foo', 'baz']); + }); + + it('ignores default integrations when setting false or undefined', () => { + const integrations = getIntegrationsToSetup({ + defaultIntegrations: [new MockIntegration('foo'), new MockIntegration('bar'), new MockIntegration('baz')], + disableIntegrations: { + bar: false, + foo: undefined, + }, + }); + + expect(integrations.map(i => i.name)).toEqual(['foo', 'bar', 'baz']); + }); + }); + it('works with empty array', () => { const integrations = getIntegrationsToSetup({ integrations: [], diff --git a/packages/deno/src/sdk.ts b/packages/deno/src/sdk.ts index 25dc550fc353..2f68b437f4e7 100644 --- a/packages/deno/src/sdk.ts +++ b/packages/deno/src/sdk.ts @@ -1,4 +1,4 @@ -import type { Client, Integration, Options, ServerRuntimeClientOptions, StackParser } from '@sentry/core'; +import type { Client, Integration, ServerRuntimeClientOptions, StackParser } from '@sentry/core'; import { createStackParser, dedupeIntegration, @@ -19,8 +19,11 @@ import { normalizePathsIntegration } from './integrations/normalizepaths'; import { makeFetchTransport } from './transports'; import type { DenoOptions } from './types'; -/** Get the default integrations for the Deno SDK. */ -export function getDefaultIntegrations(_options: Options): Integration[] { +/** + * Get the default integrations for the Deno SDK. + * Ensure to keep this in sync with `DenoOptions`! + */ +export function getDefaultIntegrations(_options: DenoOptions): Integration[] { // We return a copy of the defaultIntegrations here to avoid mutating this return [ // Common diff --git a/packages/deno/src/types.ts b/packages/deno/src/types.ts index 422e561bb644..462c33ce3726 100644 --- a/packages/deno/src/types.ts +++ b/packages/deno/src/types.ts @@ -31,7 +31,22 @@ export interface BaseDenoOptions { * Configuration options for the Sentry Deno SDK * @see @sentry/core Options for more information. */ -export interface DenoOptions extends Options, BaseDenoOptions {} +export interface DenoOptions + extends Options< + DenoTransportOptions, + [ + 'InboundFilters', + 'FunctionToString', + 'LinkedErrors', + 'Dedupe', + 'Breadcrumbs', + 'DenoContext', + 'ContextLines', + 'NormalizePaths', + 'GLobalHandlers', + ] + >, + BaseDenoOptions {} /** * Configuration options for the Sentry Deno SDK Client class diff --git a/packages/google-cloud-serverless/src/sdk.ts b/packages/google-cloud-serverless/src/sdk.ts index 03054d4bd1ff..7b570b030ec9 100644 --- a/packages/google-cloud-serverless/src/sdk.ts +++ b/packages/google-cloud-serverless/src/sdk.ts @@ -26,7 +26,7 @@ export function getDefaultIntegrations(_options: Options): Integration[] { /** * @see {@link Sentry.init} */ -export function init(options: NodeOptions = {}): NodeClient | undefined { +export function init(options: NodeOptions<['GoogleCloudHttp', 'GoogleCloudGrpc']> = {}): NodeClient | undefined { const opts = { _metadata: {} as SdkMetadata, defaultIntegrations: getDefaultIntegrations(options), diff --git a/packages/nestjs/src/sdk.ts b/packages/nestjs/src/sdk.ts index d9c00369e8b3..cd9c5d45d317 100644 --- a/packages/nestjs/src/sdk.ts +++ b/packages/nestjs/src/sdk.ts @@ -12,7 +12,7 @@ import { nestIntegration } from './integrations/nest'; /** * Initializes the NestJS SDK */ -export function init(options: NodeOptions | undefined = {}): NodeClient | undefined { +export function init(options: NodeOptions<['Nest']> | undefined = {}): NodeClient | undefined { const opts: NodeOptions = { defaultIntegrations: getDefaultIntegrations(options), ...options, diff --git a/packages/nextjs/src/client/index.ts b/packages/nextjs/src/client/index.ts index 163e29f0b9a7..38f0c51e9312 100644 --- a/packages/nextjs/src/client/index.ts +++ b/packages/nextjs/src/client/index.ts @@ -23,7 +23,7 @@ const globalWithInjectedValues = GLOBAL_OBJ as typeof GLOBAL_OBJ & { declare const __SENTRY_TRACING__: boolean; /** Inits the Sentry NextJS SDK on the browser with the React SDK. */ -export function init(options: BrowserOptions): Client | undefined { +export function init(options: BrowserOptions<['NextjsClientStackFrameNormalization']>): Client | undefined { const opts = { environment: getVercelEnv(true) || process.env.NODE_ENV, defaultIntegrations: getDefaultIntegrations(options), @@ -59,7 +59,7 @@ export function init(options: BrowserOptions): Client | undefined { return client; } -function getDefaultIntegrations(options: BrowserOptions): Integration[] { +function getDefaultIntegrations(options: BrowserOptions<['NextjsClientStackFrameNormalization']>): Integration[] { const customDefaultIntegrations = getReactDefaultIntegrations(options); // This evaluates to true unless __SENTRY_TRACING__ is text-replaced with "false", // in which case everything inside will get tree-shaken away diff --git a/packages/node/src/integrations/tracing/index.ts b/packages/node/src/integrations/tracing/index.ts index 7d06689f250d..d63f91c0c029 100644 --- a/packages/node/src/integrations/tracing/index.ts +++ b/packages/node/src/integrations/tracing/index.ts @@ -22,6 +22,8 @@ import { instrumentVercelAi, vercelAIIntegration } from './vercelai'; /** * With OTEL, all performance integrations will be added, as OTEL only initializes them when the patched package is actually required. + * + * Ensure to keep this in sync with `NodeOptions`! */ export function getAutoPerformanceIntegrations(): Integration[] { return [ diff --git a/packages/node/src/integrations/tracing/vercelai/index.ts b/packages/node/src/integrations/tracing/vercelai/index.ts index 2b2b843d00f7..4170a5393bb0 100644 --- a/packages/node/src/integrations/tracing/vercelai/index.ts +++ b/packages/node/src/integrations/tracing/vercelai/index.ts @@ -9,7 +9,7 @@ export const instrumentVercelAi = generateInstrumentOnce('vercelAI', () => new S const _vercelAIIntegration = (() => { return { - name: 'vercelAI', + name: 'VercelAI', setupOnce() { instrumentVercelAi(); }, diff --git a/packages/node/src/sdk/index.ts b/packages/node/src/sdk/index.ts index 4a4900cf5b60..41414f6d3db2 100644 --- a/packages/node/src/sdk/index.ts +++ b/packages/node/src/sdk/index.ts @@ -47,6 +47,8 @@ function getCjsOnlyIntegrations(): Integration[] { /** * Get default integrations, excluding performance. + * + * Ensure to keep this in sync with `NodeOptions`! */ export function getDefaultIntegrationsWithoutPerformance(): Integration[] { return [ diff --git a/packages/node/src/types.ts b/packages/node/src/types.ts index bf4913688470..f683e6da1c6e 100644 --- a/packages/node/src/types.ts +++ b/packages/node/src/types.ts @@ -136,7 +136,48 @@ export interface BaseNodeOptions { * Configuration options for the Sentry Node SDK * @see @sentry/core Options for more information. */ -export interface NodeOptions extends Options, BaseNodeOptions {} +export interface NodeOptions + extends Options< + NodeTransportOptions, + [ + 'InboundFilters', + 'FunctionToString', + 'LinkedErrors', + 'RequestData', + 'Console', + 'Http', + 'NodeFetch', + 'OnUncaughtException', + 'OnUnhandledRejection', + 'ContextLines', + 'LocalVariables', + 'Context', + 'ChildProcess', + 'ProcessSession', + 'Modules', + 'Express', + 'Fastify', + 'Graphql', + 'Mongo', + 'Mongoose', + 'Mysql', + 'Mysql2', + 'Redis', + 'Postgres', + 'Prisma', + 'Hapi', + 'Koa', + 'Connect', + 'Tedious', + 'GenericPool', + 'Kafka', + 'Amqplib', + 'LruMemoizer', + 'VercelAI', + ...AdditionalDefaultIntegrations, + ] + >, + BaseNodeOptions {} /** * Configuration options for the Sentry Node SDK Client class diff --git a/packages/vercel-edge/src/sdk.ts b/packages/vercel-edge/src/sdk.ts index fb6c524df4b4..e0e1941ef0c4 100644 --- a/packages/vercel-edge/src/sdk.ts +++ b/packages/vercel-edge/src/sdk.ts @@ -6,7 +6,7 @@ import { ATTR_SERVICE_VERSION, SEMRESATTRS_SERVICE_NAMESPACE, } from '@opentelemetry/semantic-conventions'; -import type { Client, Integration, Options } from '@sentry/core'; +import type { Client, Integration } from '@sentry/core'; import { GLOBAL_OBJ, SDK_VERSION, @@ -47,8 +47,11 @@ declare const process: { const nodeStackParser = createStackParser(nodeStackLineParser()); -/** Get the default integrations for the browser SDK. */ -export function getDefaultIntegrations(options: Options): Integration[] { +/** + * Get the default integrations for the VercelEdge SDK. + * Ensure to keep this in sync with `VercelEdgeOptions`! + */ +export function getDefaultIntegrations(options: VercelEdgeOptions): Integration[] { return [ dedupeIntegration(), inboundFiltersIntegration(), diff --git a/packages/vercel-edge/src/types.ts b/packages/vercel-edge/src/types.ts index 2128f23b35a8..8173576b0ebf 100644 --- a/packages/vercel-edge/src/types.ts +++ b/packages/vercel-edge/src/types.ts @@ -62,7 +62,12 @@ export interface BaseVercelEdgeOptions { * Configuration options for the Sentry VercelEdge SDK * @see @sentry/core Options for more information. */ -export interface VercelEdgeOptions extends Options, BaseVercelEdgeOptions {} +export interface VercelEdgeOptions + extends Options< + VercelEdgeTransportOptions, + ['Dedupe', 'InboundFilters', 'FunctionToString', 'LinkedErrors', 'WinterCGFetch', 'RequestData'] + >, + BaseVercelEdgeOptions {} /** * Configuration options for the Sentry VercelEdge SDK Client class diff --git a/packages/vue/src/types.ts b/packages/vue/src/types.ts index 6f61fc4e6104..406a8c9859eb 100644 --- a/packages/vue/src/types.ts +++ b/packages/vue/src/types.ts @@ -56,7 +56,7 @@ export interface VueOptions { tracingOptions?: Partial; } -export type Options = BrowserOptions & VueOptions; +export type Options = BrowserOptions<['Vue']> & VueOptions; /** Vue specific configuration for Tracing Integration */ export interface TracingOptions {