Skip to content

Commit fd32a7e

Browse files
authored
fix: make it possible to disable internal SDK integrations (#655)
1 parent 7dbeffb commit fd32a7e

File tree

11 files changed

+118
-19
lines changed

11 files changed

+118
-19
lines changed

.eslintrc.cjs

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
const tsParser = require('@typescript-eslint/parser')
2+
13
module.exports = {
24
extends: [
35
'@nuxtjs/eslint-config',
@@ -15,6 +17,19 @@ module.exports = {
1517
'vue/multi-word-component-names': 'off',
1618
},
1719
overrides: [
20+
{
21+
files: ['*.vue'],
22+
parserOptions: {
23+
parser: {
24+
// Overrides script parser for `<script lang="ts">`
25+
ts: tsParser,
26+
},
27+
},
28+
rules: {
29+
'no-undef': 'off',
30+
'no-unused-vars': 'off',
31+
},
32+
},
1833
{
1934
files: ['*.ts', '*.tsx'],
2035
extends: [

src/options.ts

Lines changed: 20 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -34,10 +34,14 @@ const SERVER_PLUGGABLE_INTEGRATIONS = ['CaptureConsole', 'Debug', 'Dedupe', 'Ext
3434
// External and optional Node.js integration - https://docs.sentry.io/platforms/node/profiling/
3535
export const SERVER_PROFILING_INTEGRATION: keyof ProfilingIntegration = 'ProfilingIntegration'
3636

37-
function filterDisabledIntegrations<T extends AllIntegrations> (integrations: T): (keyof T)[] {
37+
function getEnabledIntegrations<T extends AllIntegrations> (integrations: T): (keyof T)[] {
3838
return getIntegrationsKeys(integrations).filter(key => integrations[key])
3939
}
4040

41+
function getDisabledIntegrationKeys<T extends AllIntegrations> (integrations: T): string[] {
42+
return getIntegrationsKeys(integrations).filter(key => integrations[key] === false) as string[]
43+
}
44+
4145
function getIntegrationsKeys<T extends AllIntegrations> (integrations: T): (keyof T)[] {
4246
return Object.keys(integrations) as (keyof T)[]
4347
}
@@ -158,6 +162,7 @@ export type ResolvedClientOptions = {
158162
BROWSER_PLUGGABLE_INTEGRATIONS: string[]
159163
BROWSER_VUE_INTEGRATIONS: string[]
160164
dev: boolean
165+
DISABLED_INTEGRATION_KEYS: string[]
161166
runtimeConfigKey: string
162167
config: Options
163168
lazy: boolean | LazyConfiguration
@@ -213,13 +218,14 @@ export async function resolveClientOptions (nuxt: Nuxt, moduleOptions: Readonly<
213218
...options.config,
214219
},
215220
clientConfigPath,
221+
DISABLED_INTEGRATION_KEYS: getDisabledIntegrationKeys(options.clientIntegrations),
216222
lazy: options.lazy,
217223
apiMethods,
218224
customClientIntegrations,
219225
logMockCalls: options.logMockCalls, // for mocked only
220226
tracing: options.tracing,
221227
initialize: canInitialize(options),
222-
integrations: filterDisabledIntegrations(options.clientIntegrations)
228+
integrations: getEnabledIntegrations(options.clientIntegrations)
223229
.reduce((res, key) => {
224230
res[key] = options.clientIntegrations[key]
225231
return res
@@ -290,10 +296,10 @@ export async function resolveServerOptions (nuxt: Nuxt, moduleOptions: Readonly<
290296
}
291297
}
292298

293-
options.config.integrations = [
299+
const resolvedIntegrations = [
294300
// Automatically instrument Node.js libraries and frameworks
295301
...(options.tracing ? autoDiscoverNodePerformanceMonitoringIntegrations() : []),
296-
...filterDisabledIntegrations(options.serverIntegrations)
302+
...getEnabledIntegrations(options.serverIntegrations)
297303
.map((name) => {
298304
const opt = options.serverIntegrations[name]
299305
try {
@@ -314,6 +320,16 @@ export async function resolveServerOptions (nuxt: Nuxt, moduleOptions: Readonly<
314320
...customIntegrations,
315321
]
316322

323+
const disabledIntegrationKeys = getDisabledIntegrationKeys(options.serverIntegrations)
324+
325+
// Use a function to be able to filter out default integrations.
326+
options.config.integrations = (defaultIntegrations) => {
327+
return [
328+
...defaultIntegrations.filter(integration => !disabledIntegrationKeys.includes(integration.name)),
329+
...resolvedIntegrations,
330+
]
331+
}
332+
317333
return {
318334
config: {
319335
dsn: options.dsn,

src/templates/client.shared.js

Lines changed: 12 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -24,8 +24,10 @@ if (integrations.length) {%>import { <%= integrations.join(', ') %> } from '~@se
2424
export { init }
2525
export const SentrySdk = { ...CoreSdk, ...BrowserSdk }
2626

27+
/* eslint-disable object-curly-spacing, quote-props, quotes, key-spacing, comma-spacing */
28+
const DISABLED_INTEGRATION_KEYS = <%= serialize(options.DISABLED_INTEGRATION_KEYS) %>
29+
2730
export<%= (options.clientConfigPath || options.customClientIntegrations) ? ' async' : '' %> function getConfig (ctx) {
28-
/* eslint-disable object-curly-spacing, quote-props, quotes, key-spacing, comma-spacing */
2931
const config = {
3032
<%= Object
3133
.entries(options.config)
@@ -39,7 +41,7 @@ export<%= (options.clientConfigPath || options.customClientIntegrations) ? ' asy
3941
<% if (browserIntegrations.length) {%>
4042
const { <%= browserIntegrations.join(', ') %> } = Integrations
4143
<%}%>
42-
config.integrations = [
44+
const resolvedIntegrations = [
4345
<%= Object
4446
.entries(options.integrations)
4547
.filter(([name]) => name !== 'Vue')
@@ -57,7 +59,7 @@ export<%= (options.clientConfigPath || options.customClientIntegrations) ? ' asy
5759
]
5860
<% if (options.tracing) { %>
5961
const { browserTracing, vueOptions, vueRouterInstrumentationOptions, ...tracingOptions } = <%= serialize(options.tracing) %>
60-
config.integrations.push(new BrowserTracing({
62+
resolvedIntegrations.push(new BrowserTracing({
6163
...(ctx.app.router ? { routingInstrumentation: vueRouterInstrumentation(ctx.app.router, vueRouterInstrumentationOptions) } : {}),
6264
...browserTracing,
6365
}))
@@ -72,12 +74,17 @@ export<%= (options.clientConfigPath || options.customClientIntegrations) ? ' asy
7274
<% if (options.customClientIntegrations) { %>
7375
const customIntegrations = await getCustomIntegrations(ctx)
7476
if (Array.isArray(customIntegrations)) {
75-
config.integrations.push(...customIntegrations)
77+
resolvedIntegrations.push(...customIntegrations)
7678
} else {
7779
console.error(`[@nuxtjs/sentry] Invalid value returned from customClientIntegrations plugin. Expected an array, got "${typeof customIntegrations}".`)
7880
}
7981
<% } %>
80-
82+
config.integrations = (defaultIntegrations) => {
83+
return [
84+
...defaultIntegrations.filter(integration => !DISABLED_INTEGRATION_KEYS.includes(integration.name)),
85+
...resolvedIntegrations,
86+
]
87+
}
8188
const runtimeConfigKey = <%= serialize(options.runtimeConfigKey) %>
8289
if (ctx.$config && runtimeConfigKey && ctx.$config[runtimeConfigKey]) {
8390
merge(config, ctx.$config[runtimeConfigKey].config, ctx.$config[runtimeConfigKey].clientConfig)

test/default.test.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@ describe('Smoke test (default)', () => {
2020

2121
beforeAll(async () => {
2222
await localServer.start(TEST_DSN)
23-
const dsn = localServer.getDsn()
23+
const dsn = localServer.getDsn() ?? undefined
2424
nuxt = (await setup(loadConfig(__dirname, 'default', { sentry: { dsn } }, { merge: true }))).nuxt
2525
browser = await createBrowser()
2626
})

test/fixture/typescript/nuxt.config.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,10 @@ const config: NuxtConfig = {
2828
// Integration from @Sentry/browser package.
2929
TryCatch: { eventTarget: false },
3030
Replay: {},
31+
Dedupe: false,
32+
},
33+
serverIntegrations: {
34+
Modules: false,
3135
},
3236
clientConfig: {
3337
// This sets the sample rate to be 10%. You may want this to be 100% while
Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
<template>
2+
<div>
3+
<p>Integrations</p>
4+
<p id="client">
5+
Dedupe: {{ clientDedupeDisabled ? 'DISABLED' : 'ENABLED' }}
6+
</p>
7+
<p id="server">
8+
Modules: {{ serverModulesDisabled ? 'DISABLED' : 'ENABLED' }}
9+
</p>
10+
</div>
11+
</template>
12+
13+
<script lang="ts">
14+
import { defineComponent } from 'vue'
15+
import type { BrowserClient } from '@sentry/browser'
16+
import type { NodeClient } from '@sentry/node'
17+
18+
export default defineComponent({
19+
asyncData ({ $sentry }) {
20+
if (process.server) {
21+
return {
22+
serverModulesDisabled: $sentry.getCurrentHub().getClient<NodeClient>()!.getIntegrationById('Modules') === undefined,
23+
}
24+
}
25+
},
26+
data () {
27+
return {
28+
clientDedupeDisabled: false,
29+
serverModulesDisabled: false,
30+
}
31+
},
32+
created () {
33+
if (process.client) {
34+
this.clientDedupeDisabled = this.$sentry.getCurrentHub().getClient<BrowserClient>()!.getIntegrationById('Dedupe') === undefined
35+
}
36+
},
37+
})
38+
</script>

test/lazy.test.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ import { $$, createBrowser, loadConfig } from './utils'
1010
const __filename = fileURLToPath(import.meta.url)
1111
const __dirname = dirname(__filename)
1212

13-
const { testkit, localServer } = sentryTestkit.default()
13+
const { localServer } = sentryTestkit.default()
1414
const TEST_DSN = 'http://acacaeaccacacacabcaacdacdacadaca@sentry.io/000001'
1515

1616
describe('Smoke test (lazy)', () => {
@@ -19,7 +19,7 @@ describe('Smoke test (lazy)', () => {
1919

2020
beforeAll(async () => {
2121
await localServer.start(TEST_DSN)
22-
const dsn = localServer.getDsn()
22+
const dsn = localServer.getDsn() ?? undefined
2323
nuxt = (await setup(loadConfig(__dirname, 'lazy', { sentry: { dsn } }, { merge: true }))).nuxt
2424
browser = await createBrowser()
2525
})

test/options.test.ts

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
/* eslint-disable @typescript-eslint/no-non-null-assertion */
12
import { describe, test, expect } from 'vitest'
23
import { defu } from 'defu'
34
import { ResolvedClientOptions, ResolvedServerOptions, resolveClientOptions, resolveServerOptions } from '../src/options'
@@ -115,7 +116,10 @@ describe('Resolve Server Options', () => {
115116
logMockCalls: true,
116117
tracing: false,
117118
})
118-
const integrations = Array.isArray(resolvedOptions.config.integrations) ? resolvedOptions.config.integrations : null
119+
expect(resolvedOptions.config.integrations).not.toBeUndefined()
120+
const integrations = Array.isArray(resolvedOptions.config.integrations)
121+
? resolvedOptions.config.integrations
122+
: resolvedOptions.config.integrations!([])
119123
expect(integrations).toBeTruthy()
120124
expect(integrations?.map(integration => integration.name)).toEqual(expect.arrayContaining(['Dedupe', 'ExtraErrorData', 'RewriteFrames', 'Transaction']))
121125
})
@@ -158,7 +162,10 @@ describe('Resolve Server Options', () => {
158162
},
159163
},
160164
})
161-
const integrations = Array.isArray(resolvedOptions.config.integrations) ? resolvedOptions.config.integrations : null
165+
expect(resolvedOptions.config.integrations).not.toBeUndefined()
166+
const integrations = Array.isArray(resolvedOptions.config.integrations)
167+
? resolvedOptions.config.integrations
168+
: resolvedOptions.config.integrations!([])
162169
expect(integrations).toBeTruthy()
163170
expect(integrations?.map(integration => integration.name)).toEqual(expect.arrayContaining(['Http', 'Dedupe', 'ExtraErrorData', 'RewriteFrames', 'Transaction']))
164171
})

test/typescript.test.ts

Lines changed: 14 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -4,8 +4,6 @@ import { dirname } from 'path'
44
import { describe, afterAll, beforeAll, beforeEach, test, expect } from 'vitest'
55
import type { Browser } from 'playwright-chromium'
66
import sentryTestkit from 'sentry-testkit'
7-
// TODO: Until sentry-kit types are fixed
8-
import type { Stacktrace } from '@sentry/node'
97
import type { NuxtConfig } from '@nuxt/types'
108
import { generatePort, setup, url } from '@nuxtjs/module-test-utils'
119
import type { Nuxt } from '../src/kit-shim'
@@ -82,7 +80,7 @@ describe('Smoke test (typescript)', () => {
8280
const reports = testkit.reports()
8381
expect(reports).toHaveLength(1)
8482
expect(reports[0].error?.message).toContain('apiCrash is not defined')
85-
expect(reports[0].error?.stacktrace as Stacktrace).toMatchObject({
83+
expect(reports[0].error?.stacktrace).toMatchObject({
8684
frames: expect.arrayContaining([
8785
expect.objectContaining({
8886
filename: 'app:///api/index.ts',
@@ -102,5 +100,18 @@ describe('Smoke test (typescript)', () => {
102100
expect(reports[0].error?.message).toContain('crash_me is not a function')
103101
})
104102

103+
test('can disable integrations that SDK enables by default', async () => {
104+
const page = await browser.newPage()
105+
await page.goto(url('/disabled-integrations'))
106+
107+
const clientParagraph = await $$('#client', page)
108+
expect(clientParagraph).not.toBeNull()
109+
expect(clientParagraph!.trim()).toBe('Dedupe: DISABLED')
110+
111+
const serverParagraph = await $$('#server', page)
112+
expect(serverParagraph).not.toBeNull()
113+
expect(serverParagraph!.trim()).toBe('Modules: DISABLED')
114+
})
115+
105116
// TODO: Add tests for custom integration. Blocked by various sentry-kit bugs reported in its repo.
106117
})

test/utils.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ import { fileURLToPath } from 'node:url'
22
import initJiti from 'jiti'
33
import { defu } from 'defu'
44
import { join } from 'pathe'
5+
import { NuxtConfig } from '@nuxt/types'
56
import { chromium, Browser, Page } from 'playwright-chromium'
67

78
const jitiImport = initJiti(fileURLToPath(import.meta.url))
@@ -22,7 +23,7 @@ export async function $$ (selector: string, page: Page): Promise<string | null>
2223
return null
2324
}
2425

25-
export function loadConfig (dir: string, fixture: string | null = null, override: Record<string, unknown> = {}, { merge = false } = {}): Record<string, unknown> {
26+
export function loadConfig (dir: string, fixture: string | null = null, override: NuxtConfig = {}, { merge = false } = {}): NuxtConfig {
2627
const fixtureConfig = jitiImport(join(dir, 'fixture', fixture ?? '', 'nuxt.config'))
2728
const config = Object.assign({}, fixtureConfig.default || fixtureConfig)
2829

0 commit comments

Comments
 (0)