From b9804e9902cd8ff63f9e7c200d484ec9343ff373 Mon Sep 17 00:00:00 2001 From: Andrei <168741329+andreiborza@users.noreply.github.com> Date: Tue, 22 Sep 2026 15:11:46 +0200 Subject: [PATCH 01/21] chore(onboarding): Prepare JavaScript SDK v11 integration From 218bc1e795dbd546d22c1106fa7a09282ac4e484 Mon Sep 17 00:00:00 2001 From: Martin Sonnberger Date: Tue, 22 Sep 2026 15:14:19 +0200 Subject: [PATCH 02/21] feat(onboarding): Update JS browser onboarding for SDK v11 (#123878) SDK v11 removes the browser AI instrumentation helpers. Browser-only projects now use the existing unsupported-platform views in Agent Monitoring and Conversations, with a link to the browser manual-instrumentation guide and copyable setup instructions. Their unused onboarding steps, registrations, and tests are removed. Meta-frameworks and Bun use the shared Node onboarding directly. When browser and supported server projects are selected together, onboarding prefers a supported project. Plain JavaScript profiling examples explicitly use `profileLifecycle: "trace"` and enable tracing when profiling is selected alone. The docs also correct the `tracesSampler` reference and specify that the logs instructions without `enableLogs: true` require SDK version 10.71.0 or later. --- static/app/data/platformCategories.tsx | 20 +- static/app/gettingStartedDocs/bun/index.tsx | 2 +- .../javascript-angular/index.tsx | 4 - .../javascript-astro/index.tsx | 5 +- .../javascript-ember/index.tsx | 4 - .../javascript-gatsby/index.tsx | 4 - .../javascript-nextjs/index.tsx | 5 +- .../javascript-nuxt/index.tsx | 5 +- .../javascript-react-router/index.tsx | 5 +- .../javascript-react/index.tsx | 4 - .../javascript-remix/index.tsx | 5 +- .../javascript-solid/index.tsx | 4 - .../javascript-solidstart/index.tsx | 5 +- .../javascript-svelte/index.tsx | 4 - .../javascript-sveltekit/index.tsx | 5 +- .../javascript-tanstackstart-react/index.tsx | 5 +- .../javascript-vue/index.tsx | 4 - .../javascript/agentMonitoring.spec.tsx | 105 ---- .../javascript/agentMonitoring.tsx | 496 ------------------ .../gettingStartedDocs/javascript/index.tsx | 2 - .../gettingStartedDocs/javascript/logs.tsx | 4 +- .../javascript/onboarding.spec.tsx | 25 +- .../javascript/performance.tsx | 4 +- .../javascript/profiling.tsx | 7 +- .../gettingStartedDocs/javascript/utils.tsx | 11 +- .../explore/conversations/onboarding.spec.tsx | 53 ++ .../explore/conversations/onboarding.tsx | 12 +- .../insights/pages/agents/onboarding.spec.tsx | 49 ++ .../insights/pages/agents/onboarding.tsx | 8 +- 29 files changed, 184 insertions(+), 682 deletions(-) delete mode 100644 static/app/gettingStartedDocs/javascript/agentMonitoring.spec.tsx delete mode 100644 static/app/gettingStartedDocs/javascript/agentMonitoring.tsx diff --git a/static/app/data/platformCategories.tsx b/static/app/data/platformCategories.tsx index 32cf419714d4..8275c27036e0 100644 --- a/static/app/data/platformCategories.tsx +++ b/static/app/data/platformCategories.tsx @@ -856,16 +856,7 @@ export const featureFlagDrawerPlatforms: readonly PlatformKey[] = [ 'react-native', ]; -export const agentMonitoringPlatforms: ReadonlySet = new Set([ - ...platformKeys.filter(id => id.startsWith('javascript')), - ...platformKeys.filter(id => id.startsWith('node')), - ...platformKeys.filter(id => id.startsWith('python')), - 'deno', - 'bun', - 'php-laravel', -]); - -export const javascriptMetaFrameworks: readonly PlatformKey[] = [ +const javascriptMetaFrameworks: readonly PlatformKey[] = [ 'javascript-astro', 'javascript-nextjs', 'javascript-nuxt', @@ -876,6 +867,15 @@ export const javascriptMetaFrameworks: readonly PlatformKey[] = [ 'javascript-tanstackstart-react', ] as const; +export const agentMonitoringPlatforms: ReadonlySet = new Set([ + ...javascriptMetaFrameworks, + ...platformKeys.filter(id => id.startsWith('node')), + ...platformKeys.filter(id => id.startsWith('python')), + 'deno', + 'bun', + 'php-laravel', +]); + export const mcpMonitoringPlatforms: ReadonlySet = new Set([ ...javascriptMetaFrameworks, ...platformKeys.filter(id => id.startsWith('node')), diff --git a/static/app/gettingStartedDocs/bun/index.tsx b/static/app/gettingStartedDocs/bun/index.tsx index 7197a58ad87d..88c563395030 100644 --- a/static/app/gettingStartedDocs/bun/index.tsx +++ b/static/app/gettingStartedDocs/bun/index.tsx @@ -1,9 +1,9 @@ import type {Docs} from 'sentry/components/onboarding/gettingStartedDoc/types'; -import {agentMonitoring} from 'sentry/gettingStartedDocs/javascript/agentMonitoring'; import { feedbackOnboardingJsLoader, replayOnboardingJsLoader, } from 'sentry/gettingStartedDocs/javascript/jsLoader'; +import {agentMonitoring} from 'sentry/gettingStartedDocs/node/agentMonitoring'; import {featureFlag} from 'sentry/gettingStartedDocs/node/featureFlag'; import {getNodeLogsOnboarding} from 'sentry/gettingStartedDocs/node/utils'; diff --git a/static/app/gettingStartedDocs/javascript-angular/index.tsx b/static/app/gettingStartedDocs/javascript-angular/index.tsx index ae82c935a408..f64c8880a91e 100644 --- a/static/app/gettingStartedDocs/javascript-angular/index.tsx +++ b/static/app/gettingStartedDocs/javascript-angular/index.tsx @@ -1,5 +1,4 @@ import type {Docs} from 'sentry/components/onboarding/gettingStartedDoc/types'; -import {agentMonitoring} from 'sentry/gettingStartedDocs/javascript/agentMonitoring'; import {featureFlag} from 'sentry/gettingStartedDocs/javascript/featureFlag'; import {logs} from 'sentry/gettingStartedDocs/javascript/logs'; import {metrics} from 'sentry/gettingStartedDocs/javascript/metrics'; @@ -33,7 +32,4 @@ export const docs: Docs = { docsPlatform: 'angular', packageName: '@sentry/angular', }), - agentMonitoringOnboarding: agentMonitoring({ - packageName: '@sentry/angular', - }), }; diff --git a/static/app/gettingStartedDocs/javascript-astro/index.tsx b/static/app/gettingStartedDocs/javascript-astro/index.tsx index eb400a5f9741..91b1fc333828 100644 --- a/static/app/gettingStartedDocs/javascript-astro/index.tsx +++ b/static/app/gettingStartedDocs/javascript-astro/index.tsx @@ -1,9 +1,9 @@ import type {Docs} from 'sentry/components/onboarding/gettingStartedDoc/types'; -import {agentMonitoring} from 'sentry/gettingStartedDocs/javascript/agentMonitoring'; import {featureFlag} from 'sentry/gettingStartedDocs/javascript/featureFlag'; import {logsFullStack} from 'sentry/gettingStartedDocs/javascript/logs'; import {metricsFullStack} from 'sentry/gettingStartedDocs/javascript/metrics'; import {profilingFullStack} from 'sentry/gettingStartedDocs/javascript/profiling'; +import {agentMonitoring} from 'sentry/gettingStartedDocs/node/agentMonitoring'; import {crashReport} from './crashReport'; import {feedback} from './feedback'; @@ -34,8 +34,7 @@ export const docs: Docs = { }), agentMonitoringOnboarding: agentMonitoring({ packageName: '@sentry/astro', - clientConfigFileName: 'sentry.client.config.(ts|js)', - serverConfigFileName: 'sentry.server.config.(ts|js)', + configFileName: 'sentry.server.config.(ts|js)', }), mcpOnboarding: mcp, }; diff --git a/static/app/gettingStartedDocs/javascript-ember/index.tsx b/static/app/gettingStartedDocs/javascript-ember/index.tsx index 80ea857e9c60..6a5270c5e1f9 100644 --- a/static/app/gettingStartedDocs/javascript-ember/index.tsx +++ b/static/app/gettingStartedDocs/javascript-ember/index.tsx @@ -1,5 +1,4 @@ import type {Docs} from 'sentry/components/onboarding/gettingStartedDoc/types'; -import {agentMonitoring} from 'sentry/gettingStartedDocs/javascript/agentMonitoring'; import {featureFlag} from 'sentry/gettingStartedDocs/javascript/featureFlag'; import {logs} from 'sentry/gettingStartedDocs/javascript/logs'; import {metrics} from 'sentry/gettingStartedDocs/javascript/metrics'; @@ -32,7 +31,4 @@ export const docs: Docs = { docsPlatform: 'ember', packageName: '@sentry/ember', }), - agentMonitoringOnboarding: agentMonitoring({ - packageName: '@sentry/ember', - }), }; diff --git a/static/app/gettingStartedDocs/javascript-gatsby/index.tsx b/static/app/gettingStartedDocs/javascript-gatsby/index.tsx index dfb3c678cd31..00ef26bd76d0 100644 --- a/static/app/gettingStartedDocs/javascript-gatsby/index.tsx +++ b/static/app/gettingStartedDocs/javascript-gatsby/index.tsx @@ -1,5 +1,4 @@ import type {Docs} from 'sentry/components/onboarding/gettingStartedDoc/types'; -import {agentMonitoring} from 'sentry/gettingStartedDocs/javascript/agentMonitoring'; import {featureFlag} from 'sentry/gettingStartedDocs/javascript/featureFlag'; import {logs} from 'sentry/gettingStartedDocs/javascript/logs'; import {metrics} from 'sentry/gettingStartedDocs/javascript/metrics'; @@ -32,7 +31,4 @@ export const docs: Docs = { docsPlatform: 'gatsby', packageName: '@sentry/gatsby', }), - agentMonitoringOnboarding: agentMonitoring({ - packageName: '@sentry/gatsby', - }), }; diff --git a/static/app/gettingStartedDocs/javascript-nextjs/index.tsx b/static/app/gettingStartedDocs/javascript-nextjs/index.tsx index bec949fe6f7a..338dca5773cf 100644 --- a/static/app/gettingStartedDocs/javascript-nextjs/index.tsx +++ b/static/app/gettingStartedDocs/javascript-nextjs/index.tsx @@ -1,9 +1,9 @@ import type {Docs} from 'sentry/components/onboarding/gettingStartedDoc/types'; -import {agentMonitoring} from 'sentry/gettingStartedDocs/javascript/agentMonitoring'; import {featureFlag} from 'sentry/gettingStartedDocs/javascript/featureFlag'; import {logsFullStack} from 'sentry/gettingStartedDocs/javascript/logs'; import {metricsFullStack} from 'sentry/gettingStartedDocs/javascript/metrics'; import {profilingFullStack} from 'sentry/gettingStartedDocs/javascript/profiling'; +import {agentMonitoring} from 'sentry/gettingStartedDocs/node/agentMonitoring'; import {tct} from 'sentry/locale'; import {crashReport} from './crashReport'; @@ -89,8 +89,7 @@ export const docs: Docs = { }), agentMonitoringOnboarding: agentMonitoring({ packageName: '@sentry/nextjs', - clientConfigFileName: 'instrumentation-client.ts', - serverConfigFileName: 'sentry.server.config.(ts|js)', + configFileName: 'sentry.server.config.(ts|js)', }), mcpOnboarding: mcp, }; diff --git a/static/app/gettingStartedDocs/javascript-nuxt/index.tsx b/static/app/gettingStartedDocs/javascript-nuxt/index.tsx index c691a95726d6..5a78a2a4392e 100644 --- a/static/app/gettingStartedDocs/javascript-nuxt/index.tsx +++ b/static/app/gettingStartedDocs/javascript-nuxt/index.tsx @@ -1,9 +1,9 @@ import type {Docs} from 'sentry/components/onboarding/gettingStartedDoc/types'; -import {agentMonitoring} from 'sentry/gettingStartedDocs/javascript/agentMonitoring'; import {featureFlag} from 'sentry/gettingStartedDocs/javascript/featureFlag'; import {logsFullStack} from 'sentry/gettingStartedDocs/javascript/logs'; import {metricsFullStack} from 'sentry/gettingStartedDocs/javascript/metrics'; import {profiling} from 'sentry/gettingStartedDocs/javascript/profiling'; +import {agentMonitoring} from 'sentry/gettingStartedDocs/node/agentMonitoring'; import {crashReport} from './crashReport'; import {feedback} from './feedback'; @@ -33,8 +33,7 @@ export const docs: Docs = { }), agentMonitoringOnboarding: agentMonitoring({ packageName: '@sentry/nuxt', - clientConfigFileName: 'sentry.client.config.(ts|js)', - serverConfigFileName: 'sentry.server.config.(ts|js)', + configFileName: 'sentry.server.config.(ts|js)', }), mcpOnboarding: mcp, }; diff --git a/static/app/gettingStartedDocs/javascript-react-router/index.tsx b/static/app/gettingStartedDocs/javascript-react-router/index.tsx index 438e7c599ee6..15fa9880bf45 100644 --- a/static/app/gettingStartedDocs/javascript-react-router/index.tsx +++ b/static/app/gettingStartedDocs/javascript-react-router/index.tsx @@ -1,8 +1,8 @@ import type {Docs} from 'sentry/components/onboarding/gettingStartedDoc/types'; -import {agentMonitoring} from 'sentry/gettingStartedDocs/javascript/agentMonitoring'; import {logsFullStack} from 'sentry/gettingStartedDocs/javascript/logs'; import {metricsFullStack} from 'sentry/gettingStartedDocs/javascript/metrics'; import {profilingFullStack} from 'sentry/gettingStartedDocs/javascript/profiling'; +import {agentMonitoring} from 'sentry/gettingStartedDocs/node/agentMonitoring'; import {crashReport} from './crashReport'; import {feedback} from './feedback'; @@ -26,8 +26,7 @@ export const docs: Docs = { }), agentMonitoringOnboarding: agentMonitoring({ packageName: '@sentry/react-router', - clientConfigFileName: 'entry.client.tsx', - serverConfigFileName: 'instrument.server.mjs', + configFileName: 'instrument.server.mjs', }), logsOnboarding: logsFullStack({ docsPlatform: 'react-router', diff --git a/static/app/gettingStartedDocs/javascript-react/index.tsx b/static/app/gettingStartedDocs/javascript-react/index.tsx index d4eb67338d20..84360f94a7f5 100644 --- a/static/app/gettingStartedDocs/javascript-react/index.tsx +++ b/static/app/gettingStartedDocs/javascript-react/index.tsx @@ -1,5 +1,4 @@ import type {Docs} from 'sentry/components/onboarding/gettingStartedDoc/types'; -import {agentMonitoring} from 'sentry/gettingStartedDocs/javascript/agentMonitoring'; import {featureFlag} from 'sentry/gettingStartedDocs/javascript/featureFlag'; import {logs} from 'sentry/gettingStartedDocs/javascript/logs'; import {metrics} from 'sentry/gettingStartedDocs/javascript/metrics'; @@ -34,7 +33,4 @@ export const docs: Docs = { docsPlatform: 'react', packageName: '@sentry/react', }), - agentMonitoringOnboarding: agentMonitoring({ - packageName: '@sentry/react', - }), }; diff --git a/static/app/gettingStartedDocs/javascript-remix/index.tsx b/static/app/gettingStartedDocs/javascript-remix/index.tsx index e1faf0e65d1c..aded74709ea8 100644 --- a/static/app/gettingStartedDocs/javascript-remix/index.tsx +++ b/static/app/gettingStartedDocs/javascript-remix/index.tsx @@ -1,9 +1,9 @@ import type {Docs} from 'sentry/components/onboarding/gettingStartedDoc/types'; -import {agentMonitoring} from 'sentry/gettingStartedDocs/javascript/agentMonitoring'; import {featureFlag} from 'sentry/gettingStartedDocs/javascript/featureFlag'; import {logsFullStack} from 'sentry/gettingStartedDocs/javascript/logs'; import {metricsFullStack} from 'sentry/gettingStartedDocs/javascript/metrics'; import {profilingFullStack} from 'sentry/gettingStartedDocs/javascript/profiling'; +import {agentMonitoring} from 'sentry/gettingStartedDocs/node/agentMonitoring'; import {crashReport} from './crashReport'; import {feedback} from './feedback'; @@ -30,8 +30,7 @@ export const docs: Docs = { }), agentMonitoringOnboarding: agentMonitoring({ packageName: '@sentry/remix', - clientConfigFileName: 'entry.client.tsx', - serverConfigFileName: 'instrument.server.mjs', + configFileName: 'instrument.server.mjs', }), logsOnboarding: logsFullStack({ docsPlatform: 'remix', diff --git a/static/app/gettingStartedDocs/javascript-solid/index.tsx b/static/app/gettingStartedDocs/javascript-solid/index.tsx index 866855cccc82..e14e48828d95 100644 --- a/static/app/gettingStartedDocs/javascript-solid/index.tsx +++ b/static/app/gettingStartedDocs/javascript-solid/index.tsx @@ -1,5 +1,4 @@ import type {Docs} from 'sentry/components/onboarding/gettingStartedDoc/types'; -import {agentMonitoring} from 'sentry/gettingStartedDocs/javascript/agentMonitoring'; import {featureFlag} from 'sentry/gettingStartedDocs/javascript/featureFlag'; import {logs} from 'sentry/gettingStartedDocs/javascript/logs'; import {metrics} from 'sentry/gettingStartedDocs/javascript/metrics'; @@ -32,7 +31,4 @@ export const docs: Docs = { docsPlatform: 'solid', packageName: '@sentry/solid', }), - agentMonitoringOnboarding: agentMonitoring({ - packageName: '@sentry/solid', - }), }; diff --git a/static/app/gettingStartedDocs/javascript-solidstart/index.tsx b/static/app/gettingStartedDocs/javascript-solidstart/index.tsx index c9317686432e..b0447b4ca47b 100644 --- a/static/app/gettingStartedDocs/javascript-solidstart/index.tsx +++ b/static/app/gettingStartedDocs/javascript-solidstart/index.tsx @@ -1,9 +1,9 @@ import type {Docs} from 'sentry/components/onboarding/gettingStartedDoc/types'; -import {agentMonitoring} from 'sentry/gettingStartedDocs/javascript/agentMonitoring'; import {featureFlag} from 'sentry/gettingStartedDocs/javascript/featureFlag'; import {logsFullStack} from 'sentry/gettingStartedDocs/javascript/logs'; import {metricsFullStack} from 'sentry/gettingStartedDocs/javascript/metrics'; import {profiling} from 'sentry/gettingStartedDocs/javascript/profiling'; +import {agentMonitoring} from 'sentry/gettingStartedDocs/node/agentMonitoring'; import {crashReport} from './crashReport'; import {feedback} from './feedback'; @@ -25,8 +25,7 @@ export const docs: Docs = { }), agentMonitoringOnboarding: agentMonitoring({ packageName: '@sentry/solidstart', - clientConfigFileName: 'src/entry-client.tsx', - serverConfigFileName: 'instrument.server.mjs', + configFileName: 'instrument.server.mjs', }), logsOnboarding: logsFullStack({ docsPlatform: 'solidstart', diff --git a/static/app/gettingStartedDocs/javascript-svelte/index.tsx b/static/app/gettingStartedDocs/javascript-svelte/index.tsx index 1395e6ae70f3..642e1c3fd9ee 100644 --- a/static/app/gettingStartedDocs/javascript-svelte/index.tsx +++ b/static/app/gettingStartedDocs/javascript-svelte/index.tsx @@ -1,5 +1,4 @@ import type {Docs} from 'sentry/components/onboarding/gettingStartedDoc/types'; -import {agentMonitoring} from 'sentry/gettingStartedDocs/javascript/agentMonitoring'; import {featureFlag} from 'sentry/gettingStartedDocs/javascript/featureFlag'; import {logs} from 'sentry/gettingStartedDocs/javascript/logs'; import {metrics} from 'sentry/gettingStartedDocs/javascript/metrics'; @@ -32,7 +31,4 @@ export const docs: Docs = { docsPlatform: 'svelte', packageName: '@sentry/svelte', }), - agentMonitoringOnboarding: agentMonitoring({ - packageName: '@sentry/svelte', - }), }; diff --git a/static/app/gettingStartedDocs/javascript-sveltekit/index.tsx b/static/app/gettingStartedDocs/javascript-sveltekit/index.tsx index 2de1cc536b79..444044b865ab 100644 --- a/static/app/gettingStartedDocs/javascript-sveltekit/index.tsx +++ b/static/app/gettingStartedDocs/javascript-sveltekit/index.tsx @@ -1,9 +1,9 @@ import type {Docs} from 'sentry/components/onboarding/gettingStartedDoc/types'; -import {agentMonitoring} from 'sentry/gettingStartedDocs/javascript/agentMonitoring'; import {featureFlag} from 'sentry/gettingStartedDocs/javascript/featureFlag'; import {logsFullStack} from 'sentry/gettingStartedDocs/javascript/logs'; import {metricsFullStack} from 'sentry/gettingStartedDocs/javascript/metrics'; import {profilingFullStack} from 'sentry/gettingStartedDocs/javascript/profiling'; +import {agentMonitoring} from 'sentry/gettingStartedDocs/node/agentMonitoring'; import {crashReport} from './crashReport'; import {feedback} from './feedback'; @@ -26,8 +26,7 @@ export const docs: Docs = { }), agentMonitoringOnboarding: agentMonitoring({ packageName: '@sentry/sveltekit', - clientConfigFileName: 'src/hooks.client.(js|ts)', - serverConfigFileName: 'instrumentation.server.js', + configFileName: 'instrumentation.server.js', }), logsOnboarding: logsFullStack({ docsPlatform: 'sveltekit', diff --git a/static/app/gettingStartedDocs/javascript-tanstackstart-react/index.tsx b/static/app/gettingStartedDocs/javascript-tanstackstart-react/index.tsx index edc468149de4..e8545b4c1b18 100644 --- a/static/app/gettingStartedDocs/javascript-tanstackstart-react/index.tsx +++ b/static/app/gettingStartedDocs/javascript-tanstackstart-react/index.tsx @@ -1,8 +1,8 @@ import type {Docs} from 'sentry/components/onboarding/gettingStartedDoc/types'; -import {agentMonitoring} from 'sentry/gettingStartedDocs/javascript/agentMonitoring'; import {logsFullStack} from 'sentry/gettingStartedDocs/javascript/logs'; import {metricsFullStack} from 'sentry/gettingStartedDocs/javascript/metrics'; import {profilingFullStack} from 'sentry/gettingStartedDocs/javascript/profiling'; +import {agentMonitoring} from 'sentry/gettingStartedDocs/node/agentMonitoring'; import {mcp} from './mcp'; import {onboarding} from './onboarding'; @@ -18,8 +18,7 @@ export const docs: Docs = { }), agentMonitoringOnboarding: agentMonitoring({ packageName: '@sentry/tanstackstart-react', - clientConfigFileName: 'src/instrument.client.ts', - serverConfigFileName: 'app/ssr.tsx', + configFileName: 'app/ssr.tsx', }), logsOnboarding: logsFullStack({ docsPlatform: 'tanstackstart-react', diff --git a/static/app/gettingStartedDocs/javascript-vue/index.tsx b/static/app/gettingStartedDocs/javascript-vue/index.tsx index b9502c1b5f43..5fd4f24f6dd6 100644 --- a/static/app/gettingStartedDocs/javascript-vue/index.tsx +++ b/static/app/gettingStartedDocs/javascript-vue/index.tsx @@ -1,5 +1,4 @@ import type {Docs} from 'sentry/components/onboarding/gettingStartedDoc/types'; -import {agentMonitoring} from 'sentry/gettingStartedDocs/javascript/agentMonitoring'; import {featureFlag} from 'sentry/gettingStartedDocs/javascript/featureFlag'; import {logs} from 'sentry/gettingStartedDocs/javascript/logs'; import {metrics} from 'sentry/gettingStartedDocs/javascript/metrics'; @@ -33,7 +32,4 @@ export const docs: Docs = { docsPlatform: 'vue', packageName: '@sentry/vue', }), - agentMonitoringOnboarding: agentMonitoring({ - packageName: '@sentry/vue', - }), }; diff --git a/static/app/gettingStartedDocs/javascript/agentMonitoring.spec.tsx b/static/app/gettingStartedDocs/javascript/agentMonitoring.spec.tsx deleted file mode 100644 index 6c17180664fc..000000000000 --- a/static/app/gettingStartedDocs/javascript/agentMonitoring.spec.tsx +++ /dev/null @@ -1,105 +0,0 @@ -import type { - DocsParams, - OnboardingStep, -} from 'sentry/components/onboarding/gettingStartedDoc/types'; -import {reactNodeToText} from 'sentry/components/onboarding/utils/stepsToMarkdown'; -import {agentMonitoring} from 'sentry/gettingStartedDocs/javascript/agentMonitoring'; - -function makeParams(platformOptions: Record = {}): DocsParams { - return { - dsn: {public: 'https://public@o1.ingest.sentry.io/1'}, - platformOptions, - // A meta-framework platform: these render via the JS agent monitoring config - // but still surface the full Node integration list, including Cloudflare-only - // SDKs. - platformKey: 'javascript-nextjs', - project: {id: '1', slug: 'project-slug', platform: 'javascript-nextjs'}, - isProfilingSelected: false, - isLogsSelected: false, - isFeedbackSelected: false, - isMetricsSelected: false, - isPerformanceSelected: true, - isReplaySelected: false, - sourcePackageRegistries: {isLoading: false, data: undefined}, - } as unknown as DocsParams; -} - -function collectCode(steps: OnboardingStep[]): string { - const codes: string[] = []; - for (const step of steps) { - for (const block of step.content ?? []) { - if (block.type !== 'code') { - continue; - } - if ('tabs' in block) { - block.tabs.forEach(tab => codes.push(tab.code)); - } else { - codes.push(block.code); - } - } - } - return codes.join('\n\n'); -} - -function collectText(steps: OnboardingStep[]): string { - const parts: string[] = []; - for (const step of steps) { - for (const block of step.content ?? []) { - if (block.type === 'text') { - parts.push(reactNodeToText(block.text)); - } - } - } - return parts.join(' '); -} - -describe('javascript agentMonitoring onboarding', () => { - const config = agentMonitoring(); - - // Workers AI and the Cloudflare Agents SDK only run on Cloudflare Workers, so - // even on a meta-framework platform they must show the Node package's - // Cloudflare setup rather than the browser Sentry.init flow. - describe('Cloudflare-only SDKs reuse the Node Cloudflare setup', () => { - it('wraps the Worker with Sentry.withSentry for Workers AI', () => { - const code = collectCode( - config.configure( - makeParams({integration: 'workers_ai', deploymentTarget: 'cloudflare'}) - ) - ); - - expect(code).toContain('Sentry.withSentry('); - expect(code).toContain('import * as Sentry from "@sentry/cloudflare"'); - expect(code).not.toContain('Sentry.init('); - }); - - it('wraps the agent class with instrumentAgentWithSentry for the Agents SDK', () => { - const code = collectCode( - config.configure( - makeParams({integration: 'cloudflare_agents', deploymentTarget: 'cloudflare'}) - ) - ); - - expect(code).toContain('Sentry.instrumentAgentWithSentry('); - expect(code).toContain('import * as Sentry from "@sentry/cloudflare"'); - expect(code).not.toContain('Sentry.init('); - }); - - it('installs @sentry/cloudflare at the Agents SDK minimum version', () => { - const steps = config.install( - makeParams({integration: 'cloudflare_agents', deploymentTarget: 'cloudflare'}) - ); - - expect(collectCode(steps)).toContain('npm install @sentry/cloudflare'); - expect(collectText(steps)).toContain('10.69.0'); - }); - - it('verifies the Agents SDK by triggering the agent, not a browser LLM call', () => { - const steps = config.verify( - makeParams({integration: 'cloudflare_agents', deploymentTarget: 'cloudflare'}) - ); - - expect(collectText(steps)).toContain('Trigger your agent'); - expect(collectText(steps)).not.toContain('calling your LLM'); - }); - }); -}); diff --git a/static/app/gettingStartedDocs/javascript/agentMonitoring.tsx b/static/app/gettingStartedDocs/javascript/agentMonitoring.tsx deleted file mode 100644 index 31cd71db370b..000000000000 --- a/static/app/gettingStartedDocs/javascript/agentMonitoring.tsx +++ /dev/null @@ -1,496 +0,0 @@ -import {ExternalLink} from '@sentry/scraps/link'; - -import type { - ContentBlock, - DocsParams, - OnboardingConfig, -} from 'sentry/components/onboarding/gettingStartedDoc/types'; -import {StepType} from 'sentry/components/onboarding/gettingStartedDoc/types'; -import {javascriptMetaFrameworks} from 'sentry/data/platformCategories'; -import {allPlatforms as platforms} from 'sentry/data/platforms'; -import { - eveOnboarding, - getAgentIntegration, - getInstallStep, - getManualConfigureStep, - getMinRequiredVersion, - mastraOnboarding, - MIN_REQUIRED_VERSION, - agentMonitoring as nodeAgentMonitoring, -} from 'sentry/gettingStartedDocs/node/agentMonitoring'; -import {getImport} from 'sentry/gettingStartedDocs/node/utils'; -import {t, tct} from 'sentry/locale'; -import {SdkUpdateAlert} from 'sentry/views/insights/pages/agents/components/sdkUpdateAlert'; -import {AgentIntegration} from 'sentry/views/insights/pages/agents/utils/agentIntegrations'; - -// Meta-frameworks currently have a technical limitation: our server-side integrations do not work, -// even when added manually. Users must use Sentry’s helper functions or manually instrument their -// code, for example with `Sentry.startSpan`. -function getMetaFrameworkAlert({ - params, - functionName, -}: { - functionName: string; - params: DocsParams; -}): ContentBlock[] { - const isMetaFramework = javascriptMetaFrameworks.includes(params.platformKey); - - if (!isMetaFramework) { - return []; - } - return [ - { - type: 'alert', - alertType: 'info', - text: tct( - 'For [platformName] applications using all runtimes, you need to manually wrap your client instance with [functionName]. See instructions below.', - { - platformName: - platforms.find(p => p.id === params.platformKey)?.name ?? params.platformKey, - functionName: {functionName}, - } - ), - }, - ]; -} - -function getClientSideConfig({ - integration, - params, - sentryImport, - configFileName, -}: { - integration: AgentIntegration; - params: DocsParams; - sentryImport: string; - configFileName?: string; -}): ContentBlock[] { - const initConfig: ContentBlock[] = [ - { - type: 'text', - text: t('Import and initialize the Sentry SDK:'), - }, - { - type: 'code', - tabs: [ - { - label: configFileName ?? 'JavaScript', - language: 'javascript', - code: `${sentryImport} - -Sentry.init({ - dsn: "${params.dsn.public}", - // Tracing must be enabled for agent monitoring to work - tracesSampleRate: 1.0, - dataCollection: { - // Control data collection of LLMs and tools. - // For more info visit: https://docs.sentry.io/platforms/javascript/data-management/data-collected/ - // genAI: { inputs: false, outputs: false }, - }, -});`, - }, - ], - }, - ]; - - if (integration === AgentIntegration.LANGGRAPH) { - return [ - ...getMetaFrameworkAlert({ - params, - functionName: 'instrumentLangGraph', - }), - ...initConfig, - { - type: 'text', - text: tct( - 'Then follow the [manualSpanCreationDoc:manual custom spans] to instrument your AI calls, or use the [code:instrumentLangGraph] helper:', - { - manualSpanCreationDoc: ( - - ), - code: , - } - ), - }, - { - type: 'code', - tabs: [ - { - label: 'JavaScript', - language: 'javascript', - code: `${sentryImport} -import { createAgent } from "langchain"; - -// Setting the agent name helps Sentry identify and group agent activity -const agent = createAgent({ - model: "openai:gpt-5.4", - tools: [], - name: "joke_agent", -}); - -Sentry.instrumentLangGraph(agent, { - recordInputs: true, - recordOutputs: true, -}); - -const result = await agent.invoke({ - messages: [ - { role: "system", content: "You are a helpful assistant." }, - { role: "user", content: "Tell me a joke" }, - ], -}); - -const messages = result.messages; -const lastMessage = messages[messages.length - 1]; -const text = lastMessage.content; - `, - }, - ], - }, - ]; - } - - if (integration === AgentIntegration.LANGCHAIN) { - return [ - ...getMetaFrameworkAlert({ - params, - functionName: 'createLangChainCallbackHandler', - }), - ...initConfig, - { - type: 'text', - text: tct( - 'Then follow the [manualSpanCreationDoc:manual custom spans] to instrument your AI calls, or use the [code:createLangChainCallbackHandler] helper:', - { - manualSpanCreationDoc: ( - - ), - code: , - } - ), - }, - { - type: 'code', - tabs: [ - { - label: 'JavaScript', - language: 'javascript', - code: `${sentryImport} -import { ChatOpenAI } from "@langchain/openai"; -import { HumanMessage, SystemMessage } from "@langchain/core/messages"; - - -// Create a LangChain callback handler -const callbackHandler = Sentry.createLangChainCallbackHandler({ - recordInputs: true, // Optional: record input prompts/messages - recordOutputs: true, // Optional: record output responses -}); - -const chatModel = new ChatOpenAI({ - modelName: "gpt-5.4", - // WARNING: Never expose API keys in browser code - apiKey: "OPENAI_API_KEY", -}); - -const messages = [ - new SystemMessage("You are a helpful assistant."), - new HumanMessage("Tell me a joke"), -]; - -const response = await chatModel.invoke(messages, { - callbacks: [callbackHandler], -}); -const text = response.content; - `, - }, - ], - }, - ]; - } - - if (integration === AgentIntegration.GOOGLE_GENAI) { - return [ - ...getMetaFrameworkAlert({ - params, - functionName: 'instrumentGoogleGenAIClient', - }), - ...initConfig, - { - type: 'text', - text: tct( - 'Then follow the [manualSpanCreationDoc:manual custom spans] to instrument your AI calls, or use the [code:instrumentGoogleGenAIClient] helper:', - { - manualSpanCreationDoc: ( - - ), - code: , - } - ), - }, - { - type: 'code', - tabs: [ - { - label: 'JavaScript', - language: 'javascript', - code: `${sentryImport} -import { GoogleGenAI } from "@google/genai"; - -// WARNING: Never expose API keys in browser code -const genAI = new GoogleGenAI({apiKey: "GEMINI_API_KEY"}); - -const client = Sentry.instrumentGoogleGenAIClient(genAI, { - recordInputs: true, - recordOutputs: true, -}); - -const response = await client.models.generateContent({ - model: 'gemini-3-flash-preview', - contents: 'Why is the sky blue?', -}); -console.log(response.text); - `, - }, - ], - }, - ]; - } - - if (integration === AgentIntegration.ANTHROPIC) { - return [ - ...getMetaFrameworkAlert({ - params, - functionName: 'instrumentAnthropicAiClient', - }), - ...initConfig, - { - type: 'text', - text: tct( - 'Then follow the [manualSpanCreationDoc:manual custom spans] to instrument your AI calls, or use the [code:instrumentAnthropicAiClient] helper:', - { - manualSpanCreationDoc: ( - - ), - code: , - } - ), - }, - { - type: 'code', - tabs: [ - { - label: 'JavaScript', - language: 'javascript', - code: `${sentryImport} -import Anthropic from "@anthropic-ai/sdk"; - -// WARNING: Never expose API keys in browser code -const anthropic = new Anthropic({apiKey: "ANTHROPIC_API_KEY"}); - -const client = Sentry.instrumentAnthropicAiClient(anthropic, { - recordInputs: true, - recordOutputs: true, -}); - -const msg = await client.messages.create({ - model: "claude-sonnet-4-6", - messages: [{role: "user", content: "Tell me a joke"}], -}); - `, - }, - ], - }, - ]; - } - - if (integration === AgentIntegration.OPENAI) { - return [ - ...getMetaFrameworkAlert({ - params, - functionName: 'instrumentOpenAiClient', - }), - ...initConfig, - { - type: 'text', - text: tct( - 'Then follow the [manualSpanCreationDoc:manual custom spans] to instrument your AI calls, or use the [code:instrumentOpenAiClient] helper:', - { - manualSpanCreationDoc: ( - - ), - code: , - } - ), - }, - { - type: 'code', - tabs: [ - { - label: 'JavaScript', - language: 'javascript', - code: `${sentryImport} -import OpenAI from "openai"; - -// WARNING: Never expose API keys in browser code -const openai = new OpenAI({apiKey: "OPENAI_API_KEY"}); - -const client = Sentry.instrumentOpenAiClient(openai, { - recordInputs: true, - recordOutputs: true, -}); - -const response = await client.responses.create({ - model: "gpt-5.4", - input: "Tell me a joke", -}); - `, - }, - ], - }, - ]; - } - - return initConfig; -} - -/** - * This function is primarily intended for browser-only instructions. - * However, due to current technical limitations under investigation, - * some meta frameworks also rely on it. - * - * Since these frameworks support Vercel AI / Mastra options, - * we return server-side instructions from the Node.js agent - * monitoring function as well. - */ -export function agentMonitoring({ - packageName = '@sentry/browser', - clientConfigFileName, - serverConfigFileName, -}: { - clientConfigFileName?: string; - packageName?: `@sentry/${string}`; - serverConfigFileName?: string; -} = {}): OnboardingConfig { - return { - introduction: params => ( - - ), - install: params => - getInstallStep(params, { - packageName, - minVersion: MIN_REQUIRED_VERSION, - }), - configure: params => { - const selected = getAgentIntegration(params); - - // The Vercel AI SDK (generateText, streamText) is server-side only to prevent API key exposure. - // Flue is likewise a server-side framework (with its own blueprint setup). - // Both reuse the Node.js instructions rather than the client-side config below. - // These options are only available in meta frameworks. - if (selected === AgentIntegration.VERCEL_AI || selected === AgentIntegration.FLUE) { - return nodeAgentMonitoring({ - packageName, - configFileName: serverConfigFileName, - }).configure(params); - } - - const importMode = 'esm-only'; - - if (selected === AgentIntegration.MANUAL) { - return getManualConfigureStep(params, { - packageName, - importMode, - configFileName: clientConfigFileName, - }); - } - - if (selected === AgentIntegration.MASTRA) { - return mastraOnboarding.configure(params); - } - - // Eve is a Node/server-side framework, so it reuses the Node setup. - if (selected === AgentIntegration.EVE) { - return eveOnboarding.configure(params); - } - - // Workers AI and the Cloudflare Agents SDK only run on Cloudflare Workers. - // Selecting either pins the runtime to Cloudflare, so reuse the Node - // package's Cloudflare setup instead of the browser init flow. - if ( - selected === AgentIntegration.WORKERS_AI || - selected === AgentIntegration.CLOUDFLARE_AGENTS - ) { - return nodeAgentMonitoring({ - packageName, - configFileName: serverConfigFileName, - }).configure(params); - } - - return [ - { - title: t('Configure'), - content: getClientSideConfig({ - integration: selected, - sentryImport: getImport(packageName, importMode).join('\n'), - params, - configFileName: clientConfigFileName, - }), - }, - ]; - }, - verify: params => { - const selected = getAgentIntegration(params); - - // The Vercel AI SDK (generateText, streamText) is server-side only to prevent API key exposure. - // Flue is likewise a server-side framework (with its own blueprint setup). - // Both reuse the Node.js instructions rather than the client-side config below. - // These options are only available in meta frameworks. - if (selected === AgentIntegration.VERCEL_AI || selected === AgentIntegration.FLUE) { - return nodeAgentMonitoring({ - packageName, - configFileName: serverConfigFileName, - }).verify(params); - } - - if (selected === AgentIntegration.MASTRA) { - return mastraOnboarding.verify(params); - } - - // Eve is a Node/server-side framework, so it reuses the Node setup. - if (selected === AgentIntegration.EVE) { - return eveOnboarding.verify(params); - } - - // Workers AI and the Cloudflare Agents SDK only run on Cloudflare Workers. - // Selecting either pins the runtime to Cloudflare, so reuse the Node - // package's Cloudflare verification instead of the browser flow. - if ( - selected === AgentIntegration.WORKERS_AI || - selected === AgentIntegration.CLOUDFLARE_AGENTS - ) { - return nodeAgentMonitoring({ - packageName, - configFileName: serverConfigFileName, - }).verify(params); - } - - return [ - { - type: StepType.VERIFY, - content: [ - { - type: 'text', - text: t( - 'Verify that your instrumentation works by simply calling your LLM.' - ), - }, - ], - }, - ]; - }, - }; -} diff --git a/static/app/gettingStartedDocs/javascript/index.tsx b/static/app/gettingStartedDocs/javascript/index.tsx index 4f73ad8a0ee9..10abb7ad4723 100644 --- a/static/app/gettingStartedDocs/javascript/index.tsx +++ b/static/app/gettingStartedDocs/javascript/index.tsx @@ -4,7 +4,6 @@ import { replayOnboardingJsLoader, } from 'sentry/gettingStartedDocs/javascript/jsLoader'; -import {agentMonitoring} from './agentMonitoring'; import {crashReport} from './crashReport'; import {featureFlag} from './featureFlag'; import {feedback} from './feedback'; @@ -40,5 +39,4 @@ export const docs: Docs = { docsPlatform: 'javascript', packageName: '@sentry/browser', }), - agentMonitoringOnboarding: agentMonitoring(), }; diff --git a/static/app/gettingStartedDocs/javascript/logs.tsx b/static/app/gettingStartedDocs/javascript/logs.tsx index 212a0fd41364..a14f1a08b654 100644 --- a/static/app/gettingStartedDocs/javascript/logs.tsx +++ b/static/app/gettingStartedDocs/javascript/logs.tsx @@ -25,7 +25,7 @@ export const logs = , packageName: {packageName}, @@ -148,7 +148,7 @@ export const logsFullStack = < { type: 'text', text: tct( - 'To add logs make sure [packageName] is up-to-date. The minimum version of [packageName] that supports logs is [code:9.41.0].', + 'These instructions require [packageName] version [code:10.71.0] or later.', { code: , packageName: {packageName}, diff --git a/static/app/gettingStartedDocs/javascript/onboarding.spec.tsx b/static/app/gettingStartedDocs/javascript/onboarding.spec.tsx index 35b6f0eb1c0e..0d449b6a592c 100644 --- a/static/app/gettingStartedDocs/javascript/onboarding.spec.tsx +++ b/static/app/gettingStartedDocs/javascript/onboarding.spec.tsx @@ -89,7 +89,7 @@ describe('javascript onboarding docs', () => { ).toBeInTheDocument(); }); - it('enables profiling by setting profiling sample rates', () => { + it('enables automatic profiling and tracing when only profiling is selected', () => { renderWithOnboardingLayout(docs, { selectedProducts: [ProductSolution.ERROR_MONITORING, ProductSolution.PROFILING], selectedOptions: { @@ -103,6 +103,29 @@ describe('javascript onboarding docs', () => { expect( screen.getByText(textWithMarkupMatcher(/profileSessionSampleRate: 1\.0/)) ).toBeInTheDocument(); + expect( + screen.getByText(textWithMarkupMatcher(/profileLifecycle: "trace"/)) + ).toBeInTheDocument(); + expect( + screen.getByText(textWithMarkupMatcher(/Sentry.browserTracingIntegration\(\)/)) + ).toBeInTheDocument(); + expect( + screen.getByText(textWithMarkupMatcher(/tracesSampleRate: 1\.0/)) + ).toBeInTheDocument(); + }); + + it('enables automatic profiling in the standalone profiling guide', () => { + renderWithOnboardingLayout({ + ...docs, + onboarding: docs.profilingOnboarding!, + }); + + expect( + screen.getByText(textWithMarkupMatcher(/profileSessionSampleRate: 1\.0/)) + ).toBeInTheDocument(); + expect( + screen.getByText(textWithMarkupMatcher(/profileLifecycle: "trace"/)) + ).toBeInTheDocument(); }); it('renders Loader Script by default', () => { diff --git a/static/app/gettingStartedDocs/javascript/performance.tsx b/static/app/gettingStartedDocs/javascript/performance.tsx index 316f1df71554..236342dbe6a9 100644 --- a/static/app/gettingStartedDocs/javascript/performance.tsx +++ b/static/app/gettingStartedDocs/javascript/performance.tsx @@ -47,7 +47,7 @@ Sentry.init({ integrations: [Sentry.browserTracingIntegration()], // Set tracesSampleRate to 1.0 to capture 100% - // of transactions for performance monitoring. + // of traces for performance monitoring. // We recommend adjusting this value in production tracesSampleRate: 1.0, // Set \`tracePropagationTargets\` to control for which URLs distributed tracing should be enabled @@ -64,7 +64,7 @@ Sentry.init({ { type: 'text', text: tct( - 'We recommend adjusting the value of [code:tracesSampleRate] in production. Learn more about tracing [linkTracingOptions:options], how to use the [linkTracesSampler:traces_sampler] function, or how to do [linkSampleTransactions:sampling].', + 'We recommend adjusting the value of [code:tracesSampleRate] in production. Learn more about tracing [linkTracingOptions:options], how to use the [linkTracesSampler:tracesSampler] function, or how to do [linkSampleTransactions:sampling].', { code: , linkTracingOptions: ( diff --git a/static/app/gettingStartedDocs/javascript/profiling.tsx b/static/app/gettingStartedDocs/javascript/profiling.tsx index bba2aad2722f..e274a3e98f34 100644 --- a/static/app/gettingStartedDocs/javascript/profiling.tsx +++ b/static/app/gettingStartedDocs/javascript/profiling.tsx @@ -35,12 +35,13 @@ Sentry.init({ Sentry.browserProfilingIntegration() ], // Tracing - tracesSampleRate: 1.0, // Capture 100% of the transactions + tracesSampleRate: 1.0, // Capture 100% of traces // Set 'tracePropagationTargets' to control for which URLs distributed tracing should be enabled tracePropagationTargets: ["localhost", /^https:\\/\\/yourserver\\.io\\/api/], // Set profileSessionSampleRate to 1.0 to profile during every session. // The decision, whether to profile or not, is made once per session (when the SDK is initialized). - profileSessionSampleRate: 1.0 + profileSessionSampleRate: 1.0, + profileLifecycle: "trace" });`; const getDefaultProfilingHeaderContent = (): ContentBlock[] => [ @@ -241,7 +242,7 @@ export const profilingFullStack = < nodeProfilingIntegration(), ], // Tracing must be enabled for profiling to work - tracesSampleRate: 1.0, // Capture 100% of the transactions${ + tracesSampleRate: 1.0, // Capture 100% of traces${ params.profilingOptions?.defaultProfilingMode === 'continuous' ? ` // Set sampling rate for profiling - this is evaluated only once per SDK.init call diff --git a/static/app/gettingStartedDocs/javascript/utils.tsx b/static/app/gettingStartedDocs/javascript/utils.tsx index 7d5113c78387..e0929f163a3f 100644 --- a/static/app/gettingStartedDocs/javascript/utils.tsx +++ b/static/app/gettingStartedDocs/javascript/utils.tsx @@ -55,7 +55,7 @@ export const isAutoInstall = (params: Params) => const getIntegrations = (params: Params): string[] => { const integrations = []; - if (params.isPerformanceSelected) { + if (params.isPerformanceSelected || params.isProfilingSelected) { integrations.push('Sentry.browserTracingIntegration()'); } @@ -83,10 +83,10 @@ const getIntegrations = (params: Params): string[] => { const getDynamicParts = (params: Params): string[] => { const dynamicParts: string[] = []; - if (params.isPerformanceSelected) { + if (params.isPerformanceSelected || params.isProfilingSelected) { dynamicParts.push(` // Tracing - tracesSampleRate: 1.0, // Capture 100% of the transactions + tracesSampleRate: 1.0, // Capture 100% of traces // Set 'tracePropagationTargets' to control for which URLs distributed tracing should be enabled tracePropagationTargets: ["localhost", /^https:\\/\\/yourserver\\.io\\/api/]`); } @@ -102,7 +102,8 @@ const getDynamicParts = (params: Params): string[] => { dynamicParts.push(` // Set profileSessionSampleRate to 1.0 to profile during every session. // The decision, whether to profile or not, is made once per session (when the SDK is initialized). - profileSessionSampleRate: 1.0`); + profileSessionSampleRate: 1.0, + profileLifecycle: "trace"`); } return dynamicParts; @@ -292,7 +293,7 @@ export const loaderScriptOnboarding: OnboardingConfig = { params.isPerformanceSelected ? ` // Tracing - tracesSampleRate: 1.0, // Capture 100% of the transactions` + tracesSampleRate: 1.0, // Capture 100% of traces` : '' }${ params.isReplaySelected diff --git a/static/app/views/explore/conversations/onboarding.spec.tsx b/static/app/views/explore/conversations/onboarding.spec.tsx index 770210f9a32c..a9d08880bbe9 100644 --- a/static/app/views/explore/conversations/onboarding.spec.tsx +++ b/static/app/views/explore/conversations/onboarding.spec.tsx @@ -190,6 +190,59 @@ describe('ConversationOnboarding', () => { ).toBeGreaterThan(0); }); + it('shows the unsupported platform setup for a browser project', async () => { + const {organization} = setupProject('javascript'); + + render(, { + organization, + initialRouterConfig: { + location: { + pathname: '/', + query: {integration: 'openai', deploymentTarget: 'cloudflare'}, + }, + }, + }); + + expect( + await screen.findByText( + textWithMarkupMatcher( + /Auto instrumentation isn't available for Browser JavaScript,/ + ) + ) + ).toBeInTheDocument(); + expect(screen.getByRole('link', {name: /manually instrument/i})).toHaveAttribute( + 'href', + 'https://docs.sentry.io/platforms/javascript/tracing/instrumentation/ai-agents-module-browser/#manual-span-creation' + ); + expect( + screen.getByRole('button', {name: 'Copy Prompt for AI Agent'}) + ).toBeInTheDocument(); + }); + + it('prefers a supported project over a selected browser project', async () => { + const {organization, project} = setupProject('javascript-nextjs'); + const browserProject = ProjectFixture({ + id: '100', + slug: 'browser-project', + platform: 'javascript', + }); + ProjectsStore.loadInitialData([browserProject, project]); + PageFiltersStore.onInitializeUrlState( + PageFiltersFixture({projects: [Number(browserProject.id), Number(project.id)]}), + false + ); + + render(, { + organization, + }); + + expect( + await screen.findByText( + textWithMarkupMatcher(`Set up the Sentry SDK for ${project.slug}`) + ) + ).toBeInTheDocument(); + }); + it('pins Cloudflare projects to the Cloudflare runtime with no Node toggle', async () => { const {organization} = setupProject('node-cloudflare-workers'); diff --git a/static/app/views/explore/conversations/onboarding.tsx b/static/app/views/explore/conversations/onboarding.tsx index fad8417860e6..b28cd6e01d8a 100644 --- a/static/app/views/explore/conversations/onboarding.tsx +++ b/static/app/views/explore/conversations/onboarding.tsx @@ -817,7 +817,7 @@ function UnsupportedPlatformOnboarding({ {tct( - "Auto instrumentation isn't available for [platform] yet, but you can still get conversations working.", + "Auto instrumentation isn't available for [platform], but you can still get conversations working.", { platform: platformName, } @@ -827,7 +827,15 @@ function UnsupportedPlatformOnboarding({ {tct( '[link:Manually instrument] your agents using the Sentry SDK, or let an AI coding agent set it up for you.', { - link: , + link: ( + + ), } )} diff --git a/static/app/views/insights/pages/agents/onboarding.spec.tsx b/static/app/views/insights/pages/agents/onboarding.spec.tsx index b90e1286464c..22d8f4d003a3 100644 --- a/static/app/views/insights/pages/agents/onboarding.spec.tsx +++ b/static/app/views/insights/pages/agents/onboarding.spec.tsx @@ -107,6 +107,55 @@ describe('Onboarding deployment target', () => { ).toBeGreaterThan(0); }); + it('shows the unsupported platform setup for a browser project', async () => { + const {organization} = setupProject('javascript'); + + render(, { + organization, + initialRouterConfig: { + location: { + pathname: '/', + query: {integration: 'openai', deploymentTarget: 'cloudflare'}, + }, + }, + }); + + expect( + await screen.findByText( + textWithMarkupMatcher( + /Auto instrumentation of AI Agents is not available for your Browser JavaScript project/ + ) + ) + ).toBeInTheDocument(); + expect(screen.getByRole('link', {name: /manually instrument/i})).toHaveAttribute( + 'href', + 'https://docs.sentry.io/platforms/javascript/tracing/instrumentation/ai-agents-module-browser/#manual-span-creation' + ); + expect(screen.getByRole('button', {name: 'Copy instructions'})).toBeInTheDocument(); + }); + + it('prefers a supported project over a selected browser project', async () => { + const {organization, project} = setupProject('javascript-nextjs'); + const browserProject = ProjectFixture({ + id: '100', + slug: 'browser-project', + platform: 'javascript', + }); + ProjectsStore.loadInitialData([browserProject, project]); + PageFiltersStore.onInitializeUrlState( + PageFiltersFixture({projects: [Number(browserProject.id), Number(project.id)]}), + false + ); + + render(, {organization}); + + expect( + await screen.findByText( + textWithMarkupMatcher(`Set up the Sentry SDK for ${project.slug}`) + ) + ).toBeInTheDocument(); + }); + it('pins Cloudflare Workers projects to the Cloudflare runtime with no Node toggle', async () => { const {organization} = setupProject('node-cloudflare-workers'); diff --git a/static/app/views/insights/pages/agents/onboarding.tsx b/static/app/views/insights/pages/agents/onboarding.tsx index 5061e926781c..2615809f1644 100644 --- a/static/app/views/insights/pages/agents/onboarding.tsx +++ b/static/app/views/insights/pages/agents/onboarding.tsx @@ -502,7 +502,13 @@ export function UnsupportedPlatformOnboarding({ 'You can [link:manually instrument] your agents using the Sentry SDK tracing API, or click [bold:Copy instructions] to have an AI coding agent do it for you.', { link: ( - + ), bold: , } From e2d367fb21f79aed6bbcb57d68ebadcbbde2d00f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jan=20Peer=20St=C3=B6cklmair?= Date: Tue, 22 Sep 2026 16:18:17 +0300 Subject: [PATCH 03/21] fix(onboarding): Update Vue onboarding for SDK v11 (#123889) closes SDK-1445 Replace the bare verification error with a Vue component that sends an error and selected logs and metrics when its button is clicked. Fix root component imports, remove the unused Vue 3 router import, and use traces terminology. Add regression coverage for both Vue versions and the selected signals. I changed the `Verify` example, as only the function looked a little boring and is a little different to other examples (e.g. Svelte). The new test error will look like this: https://sentry-sdks.sentry.io/issues/7721323875/ Co-authored-by: Cursor --- .../javascript-vue/onboarding.spec.tsx | 75 +++++++++++++++++++ .../javascript-vue/onboarding.tsx | 41 ++++++++-- .../javascript-vue/utils.tsx | 11 ++- 3 files changed, 114 insertions(+), 13 deletions(-) diff --git a/static/app/gettingStartedDocs/javascript-vue/onboarding.spec.tsx b/static/app/gettingStartedDocs/javascript-vue/onboarding.spec.tsx index 902ea118ce14..c18255adbc9f 100644 --- a/static/app/gettingStartedDocs/javascript-vue/onboarding.spec.tsx +++ b/static/app/gettingStartedDocs/javascript-vue/onboarding.spec.tsx @@ -4,6 +4,7 @@ import {textWithMarkupMatcher} from 'sentry-test/utils'; import {ProductSolution} from 'sentry/components/onboarding/gettingStartedDoc/types'; +import {VueVersion} from './utils'; import {docs} from '.'; describe('javascript-vue onboarding docs', () => { @@ -24,6 +25,80 @@ describe('javascript-vue onboarding docs', () => { ).toBeInTheDocument(); }); + it('initializes Vue 3 with the root component and existing router', () => { + renderWithOnboardingLayout(docs); + + const setup = screen.getByText(textWithMarkupMatcher(/Sentry\.init\(/)); + expect(setup).toHaveTextContent('import App from "./App.vue"'); + expect(setup).toHaveTextContent('import router from "./router"'); + expect(setup).toHaveTextContent('const app = createApp(App)'); + expect(setup).toHaveTextContent('app.use(router)'); + expect(setup).not.toHaveTextContent('createRouter'); + }); + + it('keeps Vue 2 setup with its constructor and root component', () => { + renderWithOnboardingLayout(docs, { + selectedOptions: {siblingOption: VueVersion.VUE2}, + }); + + const setup = screen.getByText(textWithMarkupMatcher(/Sentry\.init\(/)); + expect(setup).toHaveTextContent('import Vue from "vue"'); + expect(setup).toHaveTextContent('import App from "./App.vue"'); + expect(setup).toHaveTextContent('Vue.use(Router)'); + expect(setup).toHaveTextContent(/Sentry\.init\(\{\s*Vue,/); + expect(setup).toHaveTextContent('render: (h) => h(App)'); + }); + + it.each([ + {products: [ProductSolution.LOGS]}, + {products: [ProductSolution.METRICS]}, + {products: [ProductSolution.LOGS, ProductSolution.METRICS]}, + ])('verifies selected signals: $products', ({products}) => { + renderWithOnboardingLayout(docs, { + selectedProducts: [ProductSolution.ERROR_MONITORING, ...products], + }); + + const verify = screen.getByText(textWithMarkupMatcher(/throw new Error/)); + expect(verify).toHaveTextContent('import * as Sentry from "@sentry/vue"'); + expect(verify.textContent?.includes('Sentry.logger.info')).toBe( + products.includes(ProductSolution.LOGS) + ); + expect(verify.textContent?.includes('Sentry.metrics.count')).toBe( + products.includes(ProductSolution.METRICS) + ); + + const setup = screen.getByText(textWithMarkupMatcher(/Sentry\.init\(/)); + expect(setup).toHaveTextContent('dataCollection:'); + expect(setup).not.toHaveTextContent(/sendDefaultPii|enableLogs|enableMetrics/); + }); + + it.each([VueVersion.VUE2, VueVersion.VUE3])( + 'shows a clickable verification component for %s', + siblingOption => { + renderWithOnboardingLayout(docs, { + selectedOptions: {siblingOption}, + selectedProducts: [ProductSolution.ERROR_MONITORING], + }); + + const verify = screen.getByText(textWithMarkupMatcher(/throw new Error/)); + expect(verify).toHaveTextContent(' + +`; }; export const onboarding: OnboardingConfig = { @@ -68,16 +93,18 @@ export const onboarding: OnboardingConfig = { content: [ { type: 'text', - text: t( - "This snippet contains an intentional error and can be used as a test to make sure that everything's working as expected." + text: tct( + 'Add this button to a Vue component, such as [code:App.vue], then click "Break the world" to send a test error to Sentry. If you selected Logs or Metrics, clicking the button sends those too.', + {code: } ), }, { type: 'code', tabs: [ { - label: 'JavaScript', - language: 'javascript', + label: 'Vue', + language: 'html', + filename: 'App.vue', code: getVerifySnippet(params), }, ], diff --git a/static/app/gettingStartedDocs/javascript-vue/utils.tsx b/static/app/gettingStartedDocs/javascript-vue/utils.tsx index 8713d25637f2..bcf185c8c8e4 100644 --- a/static/app/gettingStartedDocs/javascript-vue/utils.tsx +++ b/static/app/gettingStartedDocs/javascript-vue/utils.tsx @@ -67,7 +67,7 @@ const getDynamicParts = (params: Params): string[] => { if (params.isPerformanceSelected) { dynamicParts.push(` // Tracing - tracesSampleRate: 1.0, // Capture 100% of the transactions + tracesSampleRate: 1.0, // Capture 100% of the traces // Set 'tracePropagationTargets' to control for which URLs distributed tracing should be enabled tracePropagationTargets: ["localhost", /^https:\\/\\/yourserver\\.io\\/api/]`); } @@ -92,13 +92,14 @@ function getSiblingImportsSetupConfiguration(siblingOption: string): string { switch (siblingOption) { case VueVersion.VUE3: return `import {createApp} from "vue"; - import {createRouter} from "vue-router"; + import App from "./App.vue"; import router from "./router"; `; case VueVersion.VUE2: default: return `import Vue from "vue"; - import Router from "vue-router";`; + import Router from "vue-router"; + import App from "./App.vue";`; } } @@ -120,9 +121,7 @@ function getVueConstSetup(siblingOption: string): string { switch (siblingOption) { case VueVersion.VUE3: return ` - const app = createApp({ - // ... - }); + const app = createApp(App); `; case VueVersion.VUE2: return ` From cbd6ee46ce58bbfa74a7e34181b64d2b07fe0bf4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jan=20Peer=20St=C3=B6cklmair?= Date: Tue, 22 Sep 2026 16:19:39 +0300 Subject: [PATCH 04/21] fix(onboarding): Update Astro requirements for SDK v11 (#123880) closes SDK-1432 Raise the minimum supported Astro version to 4, matching the SDK v11 migration guide. Strengthen regression coverage to keep build-time options separate from client and server runtime configuration. Nothing else was in the onboarding guide. Co-authored-by: Cursor --- .../javascript-astro/onboarding.spec.tsx | 29 ++++++++++++------- .../javascript-astro/onboarding.tsx | 2 +- 2 files changed, 19 insertions(+), 12 deletions(-) diff --git a/static/app/gettingStartedDocs/javascript-astro/onboarding.spec.tsx b/static/app/gettingStartedDocs/javascript-astro/onboarding.spec.tsx index 6f2dbbb61ee0..82320b1e5147 100644 --- a/static/app/gettingStartedDocs/javascript-astro/onboarding.spec.tsx +++ b/static/app/gettingStartedDocs/javascript-astro/onboarding.spec.tsx @@ -16,7 +16,7 @@ describe('javascript-astro onboarding docs', () => { expect(screen.getByRole('heading', {name: 'Verify'})).toBeInTheDocument(); // Includes minimum required Astro version - expect(screen.getByText(textWithMarkupMatcher(/Astro 3.0.0/))).toBeInTheDocument(); + expect(screen.getByText(textWithMarkupMatcher(/Astro 4\.0\.0/))).toBeInTheDocument(); // Includes import statement in astro.config.mjs expect( @@ -173,19 +173,26 @@ describe('javascript-astro onboarding docs', () => { ], }); - // astro.config.mjs should only contain build-time options - expect( - screen.getByText(textWithMarkupMatcher(/process.env.SENTRY_AUTH_TOKEN/)) - ).toBeInTheDocument(); + const astroConfig = screen.getByText( + textWithMarkupMatcher(/import sentry from "@sentry\/astro"/) + ); - // Runtime config should NOT be in astro.config.mjs anymore - const astroConfigSections = screen.getAllByText( - textWithMarkupMatcher(/astro\.config\.mjs/) + expect(astroConfig).toHaveTextContent('project:'); + expect(astroConfig).toHaveTextContent('org:'); + expect(astroConfig).toHaveTextContent('authToken: process.env.SENTRY_AUTH_TOKEN'); + expect(astroConfig).not.toHaveTextContent('sourceMapsUploadOptions'); + expect(astroConfig).not.toHaveTextContent( + /dsn:|dataCollection:|tracesSampleRate:|replaysSessionSampleRate:|replaysOnErrorSampleRate:/ ); - // Check that DSN is not in astro.config.mjs section (it should be in client/server config) - // This is a bit complex to test precisely, but we can ensure the config is split correctly - expect(astroConfigSections.length).toBeGreaterThan(0); + // Runtime options belong in the client and server initialization files. + const runtimeConfigs = screen.getAllByText(textWithMarkupMatcher(/Sentry\.init\(/)); + expect(runtimeConfigs).toHaveLength(2); + for (const config of runtimeConfigs) { + expect(config).toHaveTextContent('dsn:'); + expect(config).toHaveTextContent('dataCollection:'); + expect(config).toHaveTextContent('tracesSampleRate: 1.0'); + } }); it('has metrics onboarding configuration', () => { diff --git a/static/app/gettingStartedDocs/javascript-astro/onboarding.tsx b/static/app/gettingStartedDocs/javascript-astro/onboarding.tsx index 25f7ec323908..6f0d9138b994 100644 --- a/static/app/gettingStartedDocs/javascript-astro/onboarding.tsx +++ b/static/app/gettingStartedDocs/javascript-astro/onboarding.tsx @@ -138,7 +138,7 @@ export const onboarding: OnboardingConfig = {

{tct( - "Sentry's integration with [astroLink:Astro] supports Astro 3.0.0 and above.", + "Sentry's integration with [astroLink:Astro] supports Astro 4.0.0 and above.", { astroLink: , } From ed5bfead26c86fdef98c9ec60055eba3295ca373 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jan=20Peer=20St=C3=B6cklmair?= Date: Tue, 22 Sep 2026 16:21:10 +0300 Subject: [PATCH 05/21] feat(onboarding): Offer one Cloudflare platform in the pickers (#123768) closes SDK-1449 Workers and Pages share one onboarding now, but both platforms were still offered when creating a project, under two entries that read the same. Add a hidden flag to PlatformIntegration for a platform that stays valid for existing projects but is no longer offered when creating one, and set it on `node-cloudflare-pages`. Unlike deprecated, it does not stop the docs from loading, so projects on the old key keep their onboarding. The SCM dropdown and the picker categories both drop it; project settings still lists it when it is the project's current platform, so the field shows a value. Both keys are named "Cloudflare (Node)", following the convention the other multi-language products use, such as AWS Lambda (Node), since Cloudflare Workers also runs Python, but we don't offer it yet. --------- Co-authored-by: Claude Opus 5 --- .../scm/scmPlatformHelpers.spec.tsx | 13 +- .../onboarding/scm/scmPlatformHelpers.tsx | 1 + static/app/data/platformPickerCategories.tsx | 2 - static/app/data/platforms.tsx | 6 +- .../node-cloudflare-pages/crashReport.tsx | 29 --- .../node-cloudflare-pages/index.spec.tsx | 26 ++ .../node-cloudflare-pages/index.tsx | 28 +-- .../node-cloudflare-pages/logs.tsx | 23 -- .../node-cloudflare-pages/mcp.tsx | 5 - .../node-cloudflare-pages/metrics.tsx | 6 - .../node-cloudflare-pages/onboarding.spec.tsx | 112 --------- .../node-cloudflare-pages/onboarding.tsx | 204 ---------------- .../node-cloudflare-workers/index.tsx | 4 +- .../node-cloudflare-workers/logs.tsx | 43 +++- .../onboarding.spec.tsx | 62 ++++- .../node-cloudflare-workers/onboarding.tsx | 229 +++++++++++++----- .../node-cloudflare-workers/utils.tsx | 43 ++++ static/app/types/project.tsx | 6 + .../settings/projectGeneralSettings/index.tsx | 4 +- 19 files changed, 364 insertions(+), 482 deletions(-) delete mode 100644 static/app/gettingStartedDocs/node-cloudflare-pages/crashReport.tsx create mode 100644 static/app/gettingStartedDocs/node-cloudflare-pages/index.spec.tsx delete mode 100644 static/app/gettingStartedDocs/node-cloudflare-pages/logs.tsx delete mode 100644 static/app/gettingStartedDocs/node-cloudflare-pages/mcp.tsx delete mode 100644 static/app/gettingStartedDocs/node-cloudflare-pages/metrics.tsx delete mode 100644 static/app/gettingStartedDocs/node-cloudflare-pages/onboarding.spec.tsx delete mode 100644 static/app/gettingStartedDocs/node-cloudflare-pages/onboarding.tsx create mode 100644 static/app/gettingStartedDocs/node-cloudflare-workers/utils.tsx diff --git a/static/app/components/onboarding/scm/scmPlatformHelpers.spec.tsx b/static/app/components/onboarding/scm/scmPlatformHelpers.spec.tsx index 92f8ff316d27..909ea279ede2 100644 --- a/static/app/components/onboarding/scm/scmPlatformHelpers.spec.tsx +++ b/static/app/components/onboarding/scm/scmPlatformHelpers.spec.tsx @@ -27,9 +27,18 @@ describe('platformOptionGroups', () => { ); }); - it('keeps every platform exactly once across sections', () => { + it('leaves hidden platforms out of the dropdown', () => { + expect( + otherGroup!.options.some(option => option.value === 'node-cloudflare-pages') + ).toBe(false); + expect( + platformOptions.filter(option => option.label === 'Cloudflare (Node)') + ).toHaveLength(1); + }); + + it('keeps every offered platform exactly once across sections', () => { const values = platformOptions.map(option => option.value); expect(new Set(values).size).toBe(values.length); - expect(values).toHaveLength(platforms.length); + expect(values).toHaveLength(platforms.filter(platform => !platform.hidden).length); }); }); diff --git a/static/app/components/onboarding/scm/scmPlatformHelpers.tsx b/static/app/components/onboarding/scm/scmPlatformHelpers.tsx index 72307d1cb0fa..76a08591787d 100644 --- a/static/app/components/onboarding/scm/scmPlatformHelpers.tsx +++ b/static/app/components/onboarding/scm/scmPlatformHelpers.tsx @@ -58,6 +58,7 @@ export const platformOptionGroups = [ { label: t('Other platforms'), options: platforms + .filter(platform => !platform.hidden) .filter(platform => !popularPlatformCategories.has(platform.id)) .toSorted((a, b) => comparePlatformNames(a.name, b.name)) .map(toPlatformOption), diff --git a/static/app/data/platformPickerCategories.tsx b/static/app/data/platformPickerCategories.tsx index beacbe91a8ba..04bb5468b27e 100644 --- a/static/app/data/platformPickerCategories.tsx +++ b/static/app/data/platformPickerCategories.tsx @@ -80,7 +80,6 @@ const server = new Set([ 'kotlin', 'native', 'node', - 'node-cloudflare-pages', 'node-cloudflare-workers', 'node-connect', 'node-express', @@ -158,7 +157,6 @@ const serverless = new Set([ 'node-awslambda', 'node-azurefunctions', 'node-gcpfunctions', - 'node-cloudflare-pages', 'node-cloudflare-workers', 'python-awslambda', 'python-gcpfunctions', diff --git a/static/app/data/platforms.tsx b/static/app/data/platforms.tsx index 887a04db2e28..9ce684f5da75 100644 --- a/static/app/data/platforms.tsx +++ b/static/app/data/platforms.tsx @@ -445,14 +445,16 @@ export const platforms: PlatformIntegration[] = [ }, { id: 'node-cloudflare-pages', - name: 'Cloudflare Pages', + name: 'Cloudflare (Node)', type: 'framework', language: 'node', link: 'https://docs.sentry.io/platforms/javascript/guides/cloudflare/', + // Merged into `node-cloudflare-workers`, which is the Cloudflare platform. + hidden: true, }, { id: 'node-cloudflare-workers', - name: 'Cloudflare Workers', + name: 'Cloudflare (Node)', type: 'framework', language: 'node', link: 'https://docs.sentry.io/platforms/javascript/guides/cloudflare/', diff --git a/static/app/gettingStartedDocs/node-cloudflare-pages/crashReport.tsx b/static/app/gettingStartedDocs/node-cloudflare-pages/crashReport.tsx deleted file mode 100644 index 6b7a91aa31ad..000000000000 --- a/static/app/gettingStartedDocs/node-cloudflare-pages/crashReport.tsx +++ /dev/null @@ -1,29 +0,0 @@ -import { - StepType, - type OnboardingConfig, -} from 'sentry/components/onboarding/gettingStartedDoc/types'; -import { - getCrashReportJavaScriptInstallSteps, - getCrashReportModalConfigDescription, - getCrashReportModalIntroduction, -} from 'sentry/components/onboarding/gettingStartedDoc/utils/feedbackOnboarding'; - -export const crashReport: OnboardingConfig = { - introduction: () => getCrashReportModalIntroduction(), - install: params => getCrashReportJavaScriptInstallSteps(params), - configure: () => [ - { - type: StepType.CONFIGURE, - content: [ - { - type: 'text', - text: getCrashReportModalConfigDescription({ - link: 'https://docs.sentry.io/platforms/javascript/guides/cloudflare/user-feedback/configuration/#crash-report-modal', - }), - }, - ], - }, - ], - verify: () => [], - nextSteps: () => [], -}; diff --git a/static/app/gettingStartedDocs/node-cloudflare-pages/index.spec.tsx b/static/app/gettingStartedDocs/node-cloudflare-pages/index.spec.tsx new file mode 100644 index 000000000000..f1151a229159 --- /dev/null +++ b/static/app/gettingStartedDocs/node-cloudflare-pages/index.spec.tsx @@ -0,0 +1,26 @@ +import {renderWithOnboardingLayout} from 'sentry-test/onboarding/renderWithOnboardingLayout'; +import {screen} from 'sentry-test/reactTestingLibrary'; +import {textWithMarkupMatcher} from 'sentry-test/utils'; + +import {CloudflareSetupType} from 'sentry/gettingStartedDocs/node-cloudflare-workers/utils'; + +import {docs} from '.'; + +describe('legacy cloudflare-pages platform', () => { + it('resolves to the merged Cloudflare docs', () => { + renderWithOnboardingLayout(docs); + + expect(screen.getByRole('heading', {name: 'Install'})).toBeInTheDocument(); + expect(screen.getByRole('heading', {name: 'Configure SDK'})).toBeInTheDocument(); + }); + + it('still offers the Pages setup', () => { + renderWithOnboardingLayout(docs, { + selectedOptions: {setupType: CloudflareSetupType.PAGES}, + }); + + expect( + screen.getByText(textWithMarkupMatcher(/Sentry\.sentryPagesPlugin\(/)) + ).toBeInTheDocument(); + }); +}); diff --git a/static/app/gettingStartedDocs/node-cloudflare-pages/index.tsx b/static/app/gettingStartedDocs/node-cloudflare-pages/index.tsx index 5125e91846ed..f988f36d3416 100644 --- a/static/app/gettingStartedDocs/node-cloudflare-pages/index.tsx +++ b/static/app/gettingStartedDocs/node-cloudflare-pages/index.tsx @@ -1,23 +1,5 @@ -import type {Docs} from 'sentry/components/onboarding/gettingStartedDoc/types'; -import {agentMonitoring} from 'sentry/gettingStartedDocs/node/agentMonitoring'; -import {featureFlag} from 'sentry/gettingStartedDocs/node/featureFlag'; - -import {crashReport} from './crashReport'; -import {logs} from './logs'; -import {mcp} from './mcp'; -import {metrics} from './metrics'; -import {onboarding} from './onboarding'; - -export const docs: Docs = { - onboarding, - crashReportOnboarding: crashReport, - featureFlagOnboarding: featureFlag({ - packageName: '@sentry/cloudflare', - }), - logsOnboarding: logs, - agentMonitoringOnboarding: agentMonitoring({ - packageName: '@sentry/cloudflare', - }), - mcpOnboarding: mcp, - metricsOnboarding: metrics, -}; +// `node-cloudflare-pages` is a legacy platform key: Workers and Pages were merged +// into a single "Cloudflare" platform, which lives under `node-cloudflare-workers`. +// Projects created before the merge keep this key, so they resolve to the same docs, +// where the "Setup Type" option covers Pages. +export {docs} from 'sentry/gettingStartedDocs/node-cloudflare-workers'; diff --git a/static/app/gettingStartedDocs/node-cloudflare-pages/logs.tsx b/static/app/gettingStartedDocs/node-cloudflare-pages/logs.tsx deleted file mode 100644 index 690e86aa5e1e..000000000000 --- a/static/app/gettingStartedDocs/node-cloudflare-pages/logs.tsx +++ /dev/null @@ -1,23 +0,0 @@ -import {getNodeLogsOnboarding} from 'sentry/gettingStartedDocs/node/utils'; - -export const logs = getNodeLogsOnboarding({ - docsPlatform: 'cloudflare', - packageName: '@sentry/cloudflare', - generateConfigureSnippet: (params, packageName) => ({ - type: 'code', - language: 'javascript', - code: `import * as Sentry from "${packageName}"; - -export const onRequest = [ - // Make sure Sentry is the first middleware - Sentry.sentryPagesPlugin((context) => ({ - dsn: "${params.dsn.public}", - integrations: [ - // send console.log, console.warn, and console.error calls as logs to Sentry - Sentry.consoleLoggingIntegration({ levels: ["log", "warn", "error"] }), - ], - })), - // Add more middlewares here -];`, - }), -}); diff --git a/static/app/gettingStartedDocs/node-cloudflare-pages/mcp.tsx b/static/app/gettingStartedDocs/node-cloudflare-pages/mcp.tsx deleted file mode 100644 index 532296cb0c92..000000000000 --- a/static/app/gettingStartedDocs/node-cloudflare-pages/mcp.tsx +++ /dev/null @@ -1,5 +0,0 @@ -import {getNodeMcpOnboarding} from 'sentry/gettingStartedDocs/node/utils'; - -export const mcp = getNodeMcpOnboarding({ - packageName: '@sentry/cloudflare', -}); diff --git a/static/app/gettingStartedDocs/node-cloudflare-pages/metrics.tsx b/static/app/gettingStartedDocs/node-cloudflare-pages/metrics.tsx deleted file mode 100644 index 842766552188..000000000000 --- a/static/app/gettingStartedDocs/node-cloudflare-pages/metrics.tsx +++ /dev/null @@ -1,6 +0,0 @@ -import {getNodeMetricsOnboarding} from 'sentry/gettingStartedDocs/node/metrics'; - -export const metrics = getNodeMetricsOnboarding({ - docsPlatform: 'cloudflare', - packageName: '@sentry/cloudflare', -}); diff --git a/static/app/gettingStartedDocs/node-cloudflare-pages/onboarding.spec.tsx b/static/app/gettingStartedDocs/node-cloudflare-pages/onboarding.spec.tsx deleted file mode 100644 index 4e193f4ceef5..000000000000 --- a/static/app/gettingStartedDocs/node-cloudflare-pages/onboarding.spec.tsx +++ /dev/null @@ -1,112 +0,0 @@ -import {renderWithOnboardingLayout} from 'sentry-test/onboarding/renderWithOnboardingLayout'; -import {screen} from 'sentry-test/reactTestingLibrary'; -import {textWithMarkupMatcher} from 'sentry-test/utils'; - -import {ProductSolution} from 'sentry/components/onboarding/gettingStartedDoc/types'; - -import {docs} from '.'; - -describe('cloudflare-pages onboarding docs', () => { - it('renders onboarding docs correctly', () => { - renderWithOnboardingLayout(docs); - - // Renders main headings - expect(screen.getByRole('heading', {name: 'Install'})).toBeInTheDocument(); - expect(screen.getByRole('heading', {name: 'Configure SDK'})).toBeInTheDocument(); - expect( - screen.getByRole('heading', {name: /Upload Source Maps/i}) - ).toBeInTheDocument(); - expect(screen.getByRole('heading', {name: 'Verify'})).toBeInTheDocument(); - - // Includes import statement - const allMatches = screen.getAllByText( - textWithMarkupMatcher(/import \* as Sentry from "@sentry\/cloudflare"/) - ); - allMatches.forEach(match => { - expect(match).toBeInTheDocument(); - }); - }); - - it('displays sample rates by default', () => { - renderWithOnboardingLayout(docs, { - selectedProducts: [ - ProductSolution.ERROR_MONITORING, - ProductSolution.PERFORMANCE_MONITORING, - ], - }); - - expect( - screen.getByText(textWithMarkupMatcher(/tracesSampleRate/)) - ).toBeInTheDocument(); - }); - - it('displays logs integration next step when logs are selected', () => { - renderWithOnboardingLayout(docs, { - selectedProducts: [ProductSolution.ERROR_MONITORING, ProductSolution.LOGS], - }); - - expect(screen.getByText('Logging Integrations')).toBeInTheDocument(); - }); - - it('does not display logs integration next step when logs are not selected', () => { - renderWithOnboardingLayout(docs, { - selectedProducts: [ProductSolution.ERROR_MONITORING], - }); - - expect(screen.queryByText('Logging Integrations')).not.toBeInTheDocument(); - }); - - it('displays logging code in verify section when logs are selected', () => { - renderWithOnboardingLayout(docs, { - selectedProducts: [ProductSolution.ERROR_MONITORING, ProductSolution.LOGS], - }); - - expect( - screen.getByText( - textWithMarkupMatcher(/Sentry\.logger\.info\('User triggered test error'/) - ) - ).toBeInTheDocument(); - }); - - it('does not display logging code in verify section when logs are not selected', () => { - renderWithOnboardingLayout(docs, { - selectedProducts: [ProductSolution.ERROR_MONITORING], - }); - - expect( - screen.queryByText( - textWithMarkupMatcher(/Sentry\.logger\.info\('User triggered test error'/) - ) - ).not.toBeInTheDocument(); - }); - - it('displays cloudflare features next step', () => { - renderWithOnboardingLayout(docs); - - expect(screen.getByText('Cloudflare Features')).toBeInTheDocument(); - }); - - it('displays metrics code in verify section when metrics are selected', () => { - renderWithOnboardingLayout(docs, { - selectedProducts: [ProductSolution.ERROR_MONITORING, ProductSolution.METRICS], - }); - - expect( - screen.getByText( - textWithMarkupMatcher(/Sentry\.metrics\.count\('test_counter', 1\)/) - ) - ).toBeInTheDocument(); - }); - - it('does not display metrics code in verify section when metrics are not selected', () => { - renderWithOnboardingLayout(docs, { - selectedProducts: [ProductSolution.ERROR_MONITORING], - }); - - expect( - screen.queryByText( - textWithMarkupMatcher(/Sentry\.metrics\.count\('test_counter', 1\)/) - ) - ).not.toBeInTheDocument(); - }); -}); diff --git a/static/app/gettingStartedDocs/node-cloudflare-pages/onboarding.tsx b/static/app/gettingStartedDocs/node-cloudflare-pages/onboarding.tsx deleted file mode 100644 index 234e999e953b..000000000000 --- a/static/app/gettingStartedDocs/node-cloudflare-pages/onboarding.tsx +++ /dev/null @@ -1,204 +0,0 @@ -import {ExternalLink} from '@sentry/scraps/link'; - -import type { - DocsParams, - OnboardingConfig, -} from 'sentry/components/onboarding/gettingStartedDoc/types'; -import {StepType} from 'sentry/components/onboarding/gettingStartedDoc/types'; -import {getUploadSourceMapsStep} from 'sentry/components/onboarding/gettingStartedDoc/utils'; -import {getInstallCodeBlock} from 'sentry/gettingStartedDocs/node/utils'; -import {t, tct} from 'sentry/locale'; - -const getSdkConfigureSnippetToml = () => ` -compatibility_flags = ["nodejs_compat"] -# compatibility_flags = ["nodejs_als"] -compatibility_date = "2024-09-23" -`; - -const getSdkConfigureSnippetJson = () => ` -{ - "compatibility_flags": [ - "nodejs_compat" - ], - "compatibility_date": "2024-09-23" -}`; - -const getSdkSetupSnippet = (params: DocsParams) => ` -import * as Sentry from "@sentry/cloudflare"; - -export const onRequest = [ - // Make sure Sentry is the first middleware - Sentry.sentryPagesPlugin((context) => ({ - dsn: "${params.dsn.public}",${ - params.isPerformanceSelected - ? ` - // Set tracesSampleRate to 1.0 to capture 100% of spans for tracing. - // Learn more at - // https://docs.sentry.io/platforms/javascript/configuration/options/#traces-sample-rate - tracesSampleRate: 1.0,` - : '' - } - - dataCollection: { - // To disable sending user data and HTTP bodies, uncomment the lines below. For more info visit: - // https://docs.sentry.io/platforms/javascript/guides/cloudflare-pages/configuration/options/#dataCollection - // userInfo: false, - // httpBodies: [], - }, - })), - // Add more middlewares here -];`; - -const getVerifySnippet = (params: DocsParams) => ` -export function onRequest(context) {${ - params.isLogsSelected - ? ` - // Send a log before throwing the error - Sentry.logger.info('User triggered test error', { - action: 'test_error_function', - }); -` - : '' -}${ - params.isMetricsSelected - ? ` -// Send a test metric before throwing the error -Sentry.metrics.count('test_counter', 1); -` - : '' -} -setTimeout(() => { - throw new Error(); -});}`; - -export const onboarding: OnboardingConfig = { - introduction: () => - tct( - "In this quick guide, you'll set up and configure the Sentry Cloudflare SDK for use in your Cloudflare Pages application. This will enable Sentry for the backend part of your application: the functions. If you'd like to monitor the frontend as well, refer to the instrumentation guide for [platformLink:the framework of your choice].", - { - platformLink: , - } - ), - install: params => [ - { - type: StepType.INSTALL, - content: [ - { - type: 'text', - text: t('Add the Sentry Cloudflare SDK as a dependency:'), - }, - getInstallCodeBlock(params, {packageName: '@sentry/cloudflare'}), - ], - }, - ], - configure: params => [ - { - type: StepType.CONFIGURE, - content: [ - { - type: 'text', - text: t( - "Configuration should happen as early as possible in your application's lifecycle." - ), - }, - { - type: 'text', - text: tct( - "To use the SDK, you'll need to set either the [code:nodejs_compat] or [code:nodejs_als] compatibility flags in your [code:wrangler.json]/[code:wrangler.toml]. This is because the SDK needs access to the [code:AsyncLocalStorage] API to work correctly.", - {code: } - ), - }, - { - type: 'code', - tabs: [ - { - label: 'JSON', - language: 'json', - filename: 'wrangler.json', - code: getSdkConfigureSnippetJson(), - }, - { - label: 'Toml', - language: 'toml', - filename: 'wrangler.toml', - code: getSdkConfigureSnippetToml(), - }, - ], - }, - { - type: 'text', - text: tct( - 'Add the [code:sentryPagesPlugin] as [guideLink:middleware to your Cloudflare Pages application]. We recommend adding a [code:functions/_middleware.js] for the middleware setup so that Sentry is initialized for your entire app.', - { - code: , - guideLink: ( - - ), - } - ), - }, - { - type: 'code', - tabs: [ - { - label: 'JavaScript', - language: 'javascript', - filename: 'functions/_middleware.js', - code: getSdkSetupSnippet(params), - }, - ], - }, - ], - }, - getUploadSourceMapsStep({ - guideLink: - 'https://docs.sentry.io/platforms/javascript/guides/cloudflare/sourcemaps/', - ...params, - }), - ], - verify: (params: DocsParams) => [ - { - type: StepType.VERIFY, - content: [ - { - type: 'text', - text: tct( - "This snippet contains an intentional error and can be used as a test to make sure that everything's working as expected. To trigger it, you need to access the [code:/customerror] path on your deployment.", - {code: } - ), - }, - { - type: 'code', - language: 'javascript', - filename: 'functions/customerror.js', - code: getVerifySnippet(params), - }, - ], - }, - ], - nextSteps: (params: DocsParams) => { - const steps = [ - { - id: 'cloudflare-features', - name: t('Cloudflare Features'), - description: t( - 'Learn about our first class integration with the Cloudflare Pages platform.' - ), - link: 'https://docs.sentry.io/platforms/javascript/guides/cloudflare/features/', - }, - ]; - - if (params.isLogsSelected) { - steps.push({ - id: 'logs', - name: t('Logging Integrations'), - description: t( - 'Add logging integrations to automatically capture logs from your application.' - ), - link: 'https://docs.sentry.io/platforms/javascript/guides/cloudflare/logs/#integrations', - }); - } - - return steps; - }, -}; diff --git a/static/app/gettingStartedDocs/node-cloudflare-workers/index.tsx b/static/app/gettingStartedDocs/node-cloudflare-workers/index.tsx index 5125e91846ed..e8d9aaba5c81 100644 --- a/static/app/gettingStartedDocs/node-cloudflare-workers/index.tsx +++ b/static/app/gettingStartedDocs/node-cloudflare-workers/index.tsx @@ -7,9 +7,11 @@ import {logs} from './logs'; import {mcp} from './mcp'; import {metrics} from './metrics'; import {onboarding} from './onboarding'; +import {platformOptions, type PlatformOptions} from './utils'; -export const docs: Docs = { +export const docs: Docs = { onboarding, + platformOptions, crashReportOnboarding: crashReport, featureFlagOnboarding: featureFlag({ packageName: '@sentry/cloudflare', diff --git a/static/app/gettingStartedDocs/node-cloudflare-workers/logs.tsx b/static/app/gettingStartedDocs/node-cloudflare-workers/logs.tsx index d56ca43b83cd..1b5ae996c6b2 100644 --- a/static/app/gettingStartedDocs/node-cloudflare-workers/logs.tsx +++ b/static/app/gettingStartedDocs/node-cloudflare-workers/logs.tsx @@ -1,16 +1,29 @@ import {getNodeLogsOnboarding} from 'sentry/gettingStartedDocs/node/utils'; -export const logs = getNodeLogsOnboarding({ - docsPlatform: 'cloudflare', - packageName: '@sentry/cloudflare', - generateConfigureSnippet: (params, packageName) => ({ - type: 'code', - language: 'javascript', - code: `import * as Sentry from "${packageName}"; +import type {PlatformOptions} from './utils'; +import {CloudflareSetupType} from './utils'; + +const getPagesConfigureSnippet = (dsn: string, packageName: string) => + `import * as Sentry from "${packageName}"; + +export const onRequest = [ + // Make sure Sentry is the first middleware + Sentry.sentryPagesPlugin((context) => ({ + dsn: "${dsn}", + integrations: [ + // send console.log, console.warn, and console.error calls as logs to Sentry + Sentry.consoleLoggingIntegration({ levels: ["log", "warn", "error"] }), + ], + })), + // Add more middlewares here +];`; + +const getWorkersConfigureSnippet = (dsn: string, packageName: string) => + `import * as Sentry from "${packageName}"; export default Sentry.withSentry( (env: Env) => ({ - dsn: "${params.dsn.public}", + dsn: "${dsn}", integrations: [ // send console.log, console.warn, and console.error calls as logs to Sentry Sentry.consoleLoggingIntegration({ levels: ["log", "warn", "error"] }), @@ -21,7 +34,17 @@ export default Sentry.withSentry( return new Response('Hello World!'); }, } satisfies ExportedHandler, -); - `, +);`; + +export const logs = getNodeLogsOnboarding({ + docsPlatform: 'cloudflare', + packageName: '@sentry/cloudflare', + generateConfigureSnippet: (params, packageName) => ({ + type: 'code', + language: 'javascript', + code: + params.platformOptions.setupType === CloudflareSetupType.PAGES + ? getPagesConfigureSnippet(params.dsn.public, packageName) + : getWorkersConfigureSnippet(params.dsn.public, packageName), }), }); diff --git a/static/app/gettingStartedDocs/node-cloudflare-workers/onboarding.spec.tsx b/static/app/gettingStartedDocs/node-cloudflare-workers/onboarding.spec.tsx index f73cfd7a569a..86bfd4a976c6 100644 --- a/static/app/gettingStartedDocs/node-cloudflare-workers/onboarding.spec.tsx +++ b/static/app/gettingStartedDocs/node-cloudflare-workers/onboarding.spec.tsx @@ -4,9 +4,10 @@ import {textWithMarkupMatcher} from 'sentry-test/utils'; import {ProductSolution} from 'sentry/components/onboarding/gettingStartedDoc/types'; +import {CloudflareSetupType} from './utils'; import {docs} from '.'; -describe('cloudflare-workers onboarding docs', () => { +describe('cloudflare onboarding docs', () => { it('renders onboarding docs correctly', () => { renderWithOnboardingLayout(docs); @@ -17,14 +18,61 @@ describe('cloudflare-workers onboarding docs', () => { screen.getByRole('heading', {name: /Upload Source Maps/i}) ).toBeInTheDocument(); expect(screen.getByRole('heading', {name: 'Verify'})).toBeInTheDocument(); + }); + + it('sets up the Vite plugin by default', () => { + renderWithOnboardingLayout(docs); + + expect( + screen.getByText( + textWithMarkupMatcher( + /import \{ sentryCloudflareVitePlugin \} from "@sentry\/cloudflare\/vite"/ + ) + ) + ).toBeInTheDocument(); + expect( + screen.getByText( + textWithMarkupMatcher(/export default defineCloudflareOptions\(\(env\) => \(\{/) + ) + ).toBeInTheDocument(); + expect( + screen.queryByText(textWithMarkupMatcher(/Sentry\.withSentry\(/)) + ).not.toBeInTheDocument(); + }); + + it('wraps the handler manually when the manual setup type is selected', () => { + renderWithOnboardingLayout(docs, { + selectedOptions: {setupType: CloudflareSetupType.MANUAL}, + }); - // Includes import statement - const allMatches = screen.getAllByText( - textWithMarkupMatcher(/import \* as Sentry from "@sentry\/cloudflare"/) - ); - allMatches.forEach(match => { - expect(match).toBeInTheDocument(); + expect( + screen.getByText(textWithMarkupMatcher(/Sentry\.withSentry\(/)) + ).toBeInTheDocument(); + expect( + screen.getByText( + textWithMarkupMatcher(/import \* as Sentry from "@sentry\/cloudflare"/) + ) + ).toBeInTheDocument(); + expect( + screen.queryByText(textWithMarkupMatcher(/sentryCloudflareVitePlugin/)) + ).not.toBeInTheDocument(); + }); + + it('sets up Pages middleware when the pages setup type is selected', () => { + renderWithOnboardingLayout(docs, { + selectedOptions: {setupType: CloudflareSetupType.PAGES}, }); + + expect( + screen.getByText(textWithMarkupMatcher(/Sentry\.sentryPagesPlugin\(/)) + ).toBeInTheDocument(); + expect( + screen.queryByText(textWithMarkupMatcher(/sentryCloudflareVitePlugin/)) + ).not.toBeInTheDocument(); + expect( + screen.queryByText(textWithMarkupMatcher(/Sentry\.withSentry\(/)) + ).not.toBeInTheDocument(); + expect(screen.getByRole('link', {name: 'migrate to Workers'})).toBeInTheDocument(); }); it('displays sample rates by default', () => { diff --git a/static/app/gettingStartedDocs/node-cloudflare-workers/onboarding.tsx b/static/app/gettingStartedDocs/node-cloudflare-workers/onboarding.tsx index 88a511532167..eaedc7def10a 100644 --- a/static/app/gettingStartedDocs/node-cloudflare-workers/onboarding.tsx +++ b/static/app/gettingStartedDocs/node-cloudflare-workers/onboarding.tsx @@ -1,17 +1,22 @@ import {ExternalLink} from '@sentry/scraps/link'; -import type { - DocsParams, - OnboardingConfig, -} from 'sentry/components/onboarding/gettingStartedDoc/types'; +import type {OnboardingConfig} from 'sentry/components/onboarding/gettingStartedDoc/types'; import {StepType} from 'sentry/components/onboarding/gettingStartedDoc/types'; import {getUploadSourceMapsStep} from 'sentry/components/onboarding/gettingStartedDoc/utils'; import {getInstallCodeBlock} from 'sentry/gettingStartedDocs/node/utils'; import {t, tct} from 'sentry/locale'; +import type {Params, PlatformOptions} from './utils'; +import {isPagesSetup, isViteSetup} from './utils'; + +// The date whose Cloudflare runtime defaults the snippets opt into. Today's date +// gives a new project the newest defaults, and any date from 2024-09-23 on gives +// `nodejs_compat` the full Node.js API surface the SDK needs. +const getCompatibilityDate = () => new Date().toISOString().slice(0, 10); + const getSdkConfigureSnippetToml = () => ` compatibility_flags = ["nodejs_compat"] -# compatibility_flags = ["nodejs_als"] +compatibility_date = "${getCompatibilityDate()}" `; const getSdkConfigureSnippetJson = () => ` @@ -19,30 +24,49 @@ const getSdkConfigureSnippetJson = () => ` "compatibility_flags": [ "nodejs_compat" ], - "compatibility_date": "2024-09-23" + "compatibility_date": "${getCompatibilityDate()}" }`; -const getSdkSetupSnippet = (params: DocsParams) => ` +const getSdkOptionsSnippet = (params: Params, indent: string) => + `${indent}dsn: "${params.dsn.public}",${ + params.isPerformanceSelected + ? ` +${indent}// Set tracesSampleRate to 1.0 to capture 100% of spans for tracing. +${indent}// Learn more at +${indent}// https://docs.sentry.io/platforms/javascript/configuration/options/#traces-sample-rate +${indent}tracesSampleRate: 1.0,` + : '' + } + +${indent}dataCollection: { +${indent} // To disable sending user data and HTTP bodies, uncomment the lines below. For more info visit: +${indent} // https://docs.sentry.io/platforms/javascript/guides/cloudflare/configuration/options/#dataCollection +${indent} // userInfo: false, +${indent} // httpBodies: [], +${indent}},`; + +const getViteConfigSnippet = () => ` +import { cloudflare } from "@cloudflare/vite-plugin"; +import { sentryCloudflareVitePlugin } from "@sentry/cloudflare/vite"; +import { defineConfig } from "vite"; + +export default defineConfig({ + plugins: [cloudflare(), sentryCloudflareVitePlugin()], +});`; + +const getInstrumentFileSnippet = (params: Params) => ` +import { defineCloudflareOptions } from "@sentry/cloudflare"; + +export default defineCloudflareOptions((env) => ({ +${getSdkOptionsSnippet(params, ' ')} +}));`; + +const getSdkSetupSnippet = (params: Params) => ` import * as Sentry from "@sentry/cloudflare"; export default Sentry.withSentry( (env: Env) => ({ - dsn: "${params.dsn.public}",${ - params.isPerformanceSelected - ? ` - // Set tracesSampleRate to 1.0 to capture 100% of spans for tracing. - // Learn more at - // https://docs.sentry.io/platforms/javascript/configuration/options/#traces-sample-rate - tracesSampleRate: 1.0,` - : '' - } - - dataCollection: { - // To disable sending user data and HTTP bodies, uncomment the lines below. For more info visit: - // https://docs.sentry.io/platforms/javascript/guides/cloudflare/configuration/options/#dataCollection - // userInfo: false, - // httpBodies: [], - }, +${getSdkOptionsSnippet(params, ' ')} }), { async fetch(request, env, ctx) { @@ -51,7 +75,18 @@ export default Sentry.withSentry( } satisfies ExportedHandler, );`; -const getVerifySnippet = (params: DocsParams) => `${ +const getPagesSetupSnippet = (params: Params) => ` +import * as Sentry from "@sentry/cloudflare"; + +export const onRequest = [ + // Make sure Sentry is the first middleware + Sentry.sentryPagesPlugin((context) => ({ +${getSdkOptionsSnippet(params, ' ')} + })), + // Add more middlewares here +];`; + +const getVerifySnippet = (params: Params) => `${ params.isLogsSelected ? ` // Send a log before throwing the error @@ -71,10 +106,115 @@ setTimeout(() => { throw new Error(); });`; -export const onboarding: OnboardingConfig = { +const getPagesVerifySnippet = (params: Params) => ` +export function onRequest(context) {${ + params.isLogsSelected + ? ` + // Send a log before throwing the error + Sentry.logger.info('User triggered test error', { + action: 'test_error_function', + });` + : '' +}${ + params.isMetricsSelected + ? ` + // Send a test metric before throwing the error + Sentry.metrics.count('test_counter', 1);` + : '' +} + setTimeout(() => { + throw new Error(); + }); +}`; + +const getSetupContent = (params: Params) => { + if (isPagesSetup(params)) { + return [ + { + type: 'alert', + alertType: 'info', + text: tct( + 'Cloudflare recommends Workers with static assets over Pages for new projects. If you can, [migrationLink:migrate to Workers] and use one of the Workers setups above.', + { + migrationLink: ( + + ), + } + ), + }, + { + type: 'text', + text: tct( + 'To use the SDK, add [code:sentryPagesPlugin] as middleware to your Pages application. We recommend a [code:functions/_middleware.js] file, so that Sentry is initialized for your entire app.', + {code: } + ), + }, + { + type: 'code', + language: 'javascript', + filename: 'functions/_middleware.js', + code: getPagesSetupSnippet(params), + }, + ] as const; + } + + if (isViteSetup(params)) { + return [ + { + type: 'text', + text: tct( + 'Add the Sentry plugin to your [code:vite.config.ts], after the [pluginLink:Cloudflare Vite plugin]. It wraps your worker entry, and every Durable Object, Workflow and WorkerEntrypoint class in your wrangler config, at build time.', + { + code: , + pluginLink: ( + + ), + } + ), + }, + { + type: 'code', + language: 'typescript', + filename: 'vite.config.ts', + code: getViteConfigSnippet(), + }, + { + type: 'text', + text: tct( + 'Put your Sentry options in an [code:instrument.server.ts] file next to your worker entry, and default-export them through [code:defineCloudflareOptions]. The plugin picks the file up and hands the options to [code:withSentry], so your worker entry itself stays unchanged.', + {code: } + ), + }, + { + type: 'code', + language: 'typescript', + filename: 'src/instrument.server.ts', + code: getInstrumentFileSnippet(params), + }, + ] as const; + } + + return [ + { + type: 'text', + text: tct( + 'In order to initialize the SDK, wrap your handler with the [code:withSentry] function. Note that you can turn off almost all side effects using the respective options.', + {code: } + ), + }, + { + type: 'code', + language: 'typescript', + filename: 'src/index.ts', + code: getSdkSetupSnippet(params), + }, + ] as const; +}; + +export const onboarding: OnboardingConfig = { introduction: () => t( - "In this quick guide you'll set up and configure the Sentry Cloudflare SDK for the use in your Cloudflare Workers application." + "In this quick guide you'll set up and configure the Sentry Cloudflare SDK for the use in your Cloudflare Workers or Cloudflare Pages application." ), install: params => [ { @@ -101,7 +241,7 @@ export const onboarding: OnboardingConfig = { { type: 'text', text: tct( - "To use the SDK, you'll need to set either the [code:nodejs_compat] or [code:nodejs_als] compatibility flags in your [code:wrangler.json]/[code:wrangler.toml]. This is because the SDK needs access to the [code:AsyncLocalStorage] API to work correctly.", + "To use the SDK, you'll need to set the [code:nodejs_compat] compatibility flag in your [code:wrangler.jsonc]/[code:wrangler.toml]. This is because the SDK needs access to the Node.js compatibility APIs to work correctly.", {code: } ), }, @@ -111,7 +251,7 @@ export const onboarding: OnboardingConfig = { { label: 'JSON', language: 'json', - filename: 'wrangler.json', + filename: 'wrangler.jsonc', code: getSdkConfigureSnippetJson(), }, { @@ -122,28 +262,7 @@ export const onboarding: OnboardingConfig = { }, ], }, - { - type: 'text', - text: tct( - 'In order to initialize the SDK, wrap your handler with the [code:withSentry] function. Note that you can turn off almost all side effects using the respective options.', - { - code: , - guideLink: ( - - ), - } - ), - }, - { - type: 'code', - tabs: [ - { - label: 'TypeScript', - language: 'typescript', - code: getSdkSetupSnippet(params), - }, - ], - }, + ...getSetupContent(params), ], }, getUploadSourceMapsStep({ @@ -152,7 +271,7 @@ export const onboarding: OnboardingConfig = { ...params, }), ], - verify: (params: DocsParams) => [ + verify: params => [ { type: StepType.VERIFY, content: [ @@ -165,18 +284,20 @@ export const onboarding: OnboardingConfig = { { type: 'code', language: 'javascript', - code: getVerifySnippet(params), + code: isPagesSetup(params) + ? getPagesVerifySnippet(params) + : getVerifySnippet(params), }, ], }, ], - nextSteps: (params: DocsParams) => { + nextSteps: params => { const steps = [ { id: 'cloudflare-features', name: t('Cloudflare Features'), description: t( - 'Learn about our first class integration with the Cloudflare Workers platform.' + 'Learn about our first class integration with the Cloudflare platform.' ), link: 'https://docs.sentry.io/platforms/javascript/guides/cloudflare/features/', }, diff --git a/static/app/gettingStartedDocs/node-cloudflare-workers/utils.tsx b/static/app/gettingStartedDocs/node-cloudflare-workers/utils.tsx new file mode 100644 index 000000000000..3983a5ffa032 --- /dev/null +++ b/static/app/gettingStartedDocs/node-cloudflare-workers/utils.tsx @@ -0,0 +1,43 @@ +import type { + BasePlatformOptions, + DocsParams, +} from 'sentry/components/onboarding/gettingStartedDoc/types'; +import {t} from 'sentry/locale'; + +export enum CloudflareSetupType { + VITE = 'vite', + MANUAL = 'manual', + PAGES = 'pages', +} + +export const platformOptions = { + setupType: { + label: t('Setup Type'), + defaultValue: CloudflareSetupType.VITE, + items: [ + { + label: t('Workers (Vite Plugin)'), + value: CloudflareSetupType.VITE, + }, + { + label: t('Workers (Manual)'), + value: CloudflareSetupType.MANUAL, + }, + { + label: t('Pages'), + value: CloudflareSetupType.PAGES, + }, + ], + }, +} satisfies BasePlatformOptions; + +export type PlatformOptions = typeof platformOptions; +export type Params = DocsParams; + +export function isViteSetup(params: Params) { + return params.platformOptions.setupType === CloudflareSetupType.VITE; +} + +export function isPagesSetup(params: Params) { + return params.platformOptions.setupType === CloudflareSetupType.PAGES; +} diff --git a/static/app/types/project.tsx b/static/app/types/project.tsx index 7f2f9b22767a..017b1710cebb 100644 --- a/static/app/types/project.tsx +++ b/static/app/types/project.tsx @@ -224,6 +224,12 @@ export type PlatformIntegration = { name: string; type: string; deprecated?: boolean; + /** + * True for a platform that is still valid for existing projects but is no + * longer offered when creating one, because it was merged into another + * platform. Its docs stay reachable, unlike `deprecated`. + */ + hidden?: boolean; iconConfig?: { withLanguageIcon: boolean; }; diff --git a/static/app/views/settings/projectGeneralSettings/index.tsx b/static/app/views/settings/projectGeneralSettings/index.tsx index d94c42bd71e8..ecbed34c5630 100644 --- a/static/app/views/settings/projectGeneralSettings/index.tsx +++ b/static/app/views/settings/projectGeneralSettings/index.tsx @@ -615,9 +615,9 @@ export function ProjectGeneralSettings({project, onChangeSlug}: Props) { () => platforms .filter( - ({id}) => + ({id, hidden}) => project.platform === id || - isPlatformAllowed({isSelfHosted, organization, platform: id}) + (!hidden && isPlatformAllowed({isSelfHosted, organization, platform: id})) ) .map(({id, name}) => ({ value: id, From 6f3807858b7545c980af6fcb92a66dd01748f93b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jan=20Peer=20St=C3=B6cklmair?= Date: Tue, 22 Sep 2026 16:22:34 +0300 Subject: [PATCH 06/21] feat(onboarding): Add Remix 2.x support | remove 1.x support (#123882) Nothing more than changing the version range. Remix v3 is not yet supported as of https://github.com/getsentry/sentry-javascript/issues/23901 and the rest of the onboarding is wizard driven. --- .../javascript-remix/onboarding.spec.tsx | 8 ++++++++ .../gettingStartedDocs/javascript-remix/onboarding.tsx | 9 +++------ 2 files changed, 11 insertions(+), 6 deletions(-) diff --git a/static/app/gettingStartedDocs/javascript-remix/onboarding.spec.tsx b/static/app/gettingStartedDocs/javascript-remix/onboarding.spec.tsx index 45569473e4d3..cd0d00efa57d 100644 --- a/static/app/gettingStartedDocs/javascript-remix/onboarding.spec.tsx +++ b/static/app/gettingStartedDocs/javascript-remix/onboarding.spec.tsx @@ -21,6 +21,14 @@ describe('javascript-remix onboarding docs', () => { ).toBeInTheDocument(); }); + it('documents the supported Remix version', () => { + renderWithOnboardingLayout(docs); + + expect( + screen.getByText(textWithMarkupMatcher(/supports Remix 2\.x/)) + ).toBeInTheDocument(); + }); + it('has metrics onboarding configuration', () => { expect(docs.metricsOnboarding).toBeDefined(); expect(docs.metricsOnboarding?.install).toBeDefined(); diff --git a/static/app/gettingStartedDocs/javascript-remix/onboarding.tsx b/static/app/gettingStartedDocs/javascript-remix/onboarding.tsx index bd142ecb5666..9248763c12f5 100644 --- a/static/app/gettingStartedDocs/javascript-remix/onboarding.tsx +++ b/static/app/gettingStartedDocs/javascript-remix/onboarding.tsx @@ -13,12 +13,9 @@ import {getInstallContent} from './utils'; export const onboarding: OnboardingConfig = { introduction: () => (

- {tct( - "Sentry's integration with [remixLink:Remix] supports Remix 1.0.0 and above.", - { - remixLink: , - } - )} + {tct("Sentry's integration with [remixLink:Remix] supports Remix 2.x.", { + remixLink: , + })}

), install: (params: DocsParams) => [ From a8bbc9f0bab63170859e2ecef159729e8706810e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jan=20Peer=20St=C3=B6cklmair?= Date: Tue, 22 Sep 2026 17:15:58 +0300 Subject: [PATCH 07/21] feat(onboarding): Update Node onboarding for SDK v11 (#123877) closes SDK-1446 The Node onboarding still described a v10 setup: the entry point snippet was mislabelled as `instrument.(js|mjs)`, the text carried an Express error handler note that does not belong in the generic Node guide, and ESM was left to a docs link. The configure step is now ESM only, in the shape the Express guide uses. `instrument.mjs` holds the `Sentry.init()` call, the app starts with `node --import ./instrument.mjs index.mjs`, and `index.mjs` is shown as the entry point. Widen `getSdkInitSnippet()` to take `esm-only`, so the snippet drops the CommonJS hint comment that an ESM only guide does not need. Pull the verify snippet into `getVerifySnippet()` so the tracing and the plain branch share one set of log and metric lines, and add a tracing next step when tracing is selected. This is the same as #123779 --- .../node/onboarding.spec.tsx | 14 +- .../gettingStartedDocs/node/onboarding.tsx | 150 ++++++++++-------- static/app/gettingStartedDocs/node/utils.tsx | 8 +- 3 files changed, 98 insertions(+), 74 deletions(-) diff --git a/static/app/gettingStartedDocs/node/onboarding.spec.tsx b/static/app/gettingStartedDocs/node/onboarding.spec.tsx index b59d5777dd3e..5724a745e60d 100644 --- a/static/app/gettingStartedDocs/node/onboarding.spec.tsx +++ b/static/app/gettingStartedDocs/node/onboarding.spec.tsx @@ -29,6 +29,16 @@ describe('node onboarding docs', () => { }); }); + it('starts the app with the --import flag', () => { + renderWithOnboardingLayout(docs); + + expect( + screen.getByText( + textWithMarkupMatcher(/node --import \.\/instrument\.mjs index\.mjs/) + ) + ).toBeInTheDocument(); + }); + it('displays sample rates by default', () => { renderWithOnboardingLayout(docs, { selectedProducts: [ @@ -67,7 +77,7 @@ describe('node onboarding docs', () => { expect( screen.getByText( textWithMarkupMatcher( - /const { nodeProfilingIntegration } = require\("@sentry\/profiling-node"\)/ + /import { nodeProfilingIntegration } from "@sentry\/profiling-node"/ ) ) ).toBeInTheDocument(); @@ -93,7 +103,7 @@ describe('node onboarding docs', () => { expect( screen.getByText( textWithMarkupMatcher( - /const { nodeProfilingIntegration } = require\("@sentry\/profiling-node"\)/ + /import { nodeProfilingIntegration } from "@sentry\/profiling-node"/ ) ) ).toBeInTheDocument(); diff --git a/static/app/gettingStartedDocs/node/onboarding.tsx b/static/app/gettingStartedDocs/node/onboarding.tsx index 5362d6116700..04a12af0b0be 100644 --- a/static/app/gettingStartedDocs/node/onboarding.tsx +++ b/static/app/gettingStartedDocs/node/onboarding.tsx @@ -11,24 +11,64 @@ import { } from 'sentry/components/onboarding/gettingStartedDoc/utils'; import {t, tct} from 'sentry/locale'; -import { - getImportInstrumentSnippet, - getInstallCodeBlock, - getSdkInitSnippet, -} from './utils'; - -const getSdkSetupSnippet = () => ` -${getImportInstrumentSnippet()} +import {getInstallCodeBlock, getSdkInitSnippet} from './utils'; -// All other imports below -const { createServer } = require("node:http"); +const getEntryPointSnippet = () => `import { createServer } from "node:http"; const server = createServer((req, res) => { // server code }); -server.listen(3000, "127.0.0.1"); -`; +server.listen(3000, "127.0.0.1");`; + +/** + * The log and metric calls the verify snippet makes before it throws. + * + * @param indent The whitespace put in front of every line. + */ +const getVerifySignalsSnippet = (params: DocsParams, indent: string) => + `${ + params.isLogsSelected + ? ` +${indent}// Send a log before throwing the error +${indent}Sentry.logger.info('User triggered test error', { +${indent} action: 'test_error', +${indent}});` + : '' + }${ + params.isMetricsSelected + ? ` +${indent}// Send a test metric before throwing the error +${indent}Sentry.metrics.count('test_counter', 1);` + : '' + }`; + +const getVerifySnippet = (params: DocsParams) => { + if (!params.isPerformanceSelected) { + return ` +import * as Sentry from "@sentry/node"; +${getVerifySignalsSnippet(params, '')} +try { + foo(); +} catch (e) { + Sentry.captureException(e); +}`; + } + + return ` +import * as Sentry from "@sentry/node"; + +Sentry.startSpan({ + op: "test", + name: "My First Test Span", +}, () => { + try {${getVerifySignalsSnippet(params, ' ')} + foo(); + } catch (e) { + Sentry.captureException(e); + } +});`; +}; export const onboarding: OnboardingConfig = { hideInstructionsCopy: true, @@ -61,7 +101,7 @@ export const onboarding: OnboardingConfig = { { type: 'text', text: tct( - 'To initialize the SDK before everything else, create an external file called [code:instrument.js/mjs].', + 'To initialize the SDK before everything else, create an external file called [code:instrument.mjs].', {code: } ), }, @@ -71,15 +111,15 @@ export const onboarding: OnboardingConfig = { { label: 'JavaScript', language: 'javascript', - filename: 'instrument.(js|mjs)', - code: getSdkInitSnippet(params, 'node'), + filename: 'instrument.mjs', + code: getSdkInitSnippet(params, 'node', 'esm-only'), }, ], }, { type: 'text', text: tct( - "Make sure to import [code:instrument.js/mjs] at the top of your file. Set up the error handler after all controllers and before any other error middleware. This setup is typically done in your application's entry point file, which is usually [code:index.(js|ts)]. If you're running your application in ESM mode, or looking for alternative ways to set up Sentry, read about [docs:installation methods in our docs].", + 'Start your application with the [code:--import] flag, so that [code:instrument.mjs] loads before any other module. For alternative ways to set up Sentry, read about [docs:installation methods in our docs].', { code: , docs: ( @@ -88,14 +128,26 @@ export const onboarding: OnboardingConfig = { } ), }, + { + type: 'code', + language: 'bash', + code: 'node --import ./instrument.mjs index.mjs', + }, + { + type: 'text', + text: tct( + 'This is what your application entry point, usually [code:index.mjs], looks like:', + {code: } + ), + }, { type: 'code', tabs: [ { label: 'JavaScript', language: 'javascript', - filename: 'instrument.(js|mjs)', - code: getSdkSetupSnippet(), + filename: 'index.mjs', + code: getEntryPointSnippet(), }, ], }, @@ -120,56 +172,7 @@ export const onboarding: OnboardingConfig = { { type: 'code', language: 'javascript', - code: params.isPerformanceSelected - ? ` -const Sentry = require("@sentry/node"); - -Sentry.startSpan({ - op: "test", - name: "My First Test Span", -}, () => { - try {${ - params.isLogsSelected - ? ` - // Send a log before throwing the error - Sentry.logger.info('User triggered test error', { - action: 'test_error_span', - });` - : '' - }${ - params.isMetricsSelected - ? ` - // Send a test metric before throwing the error - Sentry.metrics.count('test_counter', 1);` - : '' - } - foo(); - } catch (e) { - Sentry.captureException(e); - } -});` - : ` -const Sentry = require("@sentry/node"); -${ - params.isLogsSelected - ? ` -// Send a log before throwing the error -Sentry.logger.info('User triggered test error', { - action: 'test_error_basic', -});` - : '' -}${ - params.isMetricsSelected - ? ` -// Send a test metric before throwing the error -Sentry.metrics.count('test_counter', 1);` - : '' - } -try { - foo(); -} catch (e) { - Sentry.captureException(e); -}`, + code: getVerifySnippet(params), }, ], }, @@ -177,6 +180,17 @@ try { nextSteps: (params: DocsParams) => { const steps = []; + if (params.isPerformanceSelected) { + steps.push({ + id: 'tracing', + name: t('Tracing'), + description: t( + 'Learn which libraries the SDK instruments for you, and how to add your own spans.' + ), + link: 'https://docs.sentry.io/platforms/javascript/guides/node/tracing/', + }); + } + if (params.isLogsSelected) { steps.push({ id: 'logs', diff --git a/static/app/gettingStartedDocs/node/utils.tsx b/static/app/gettingStartedDocs/node/utils.tsx index b7f3d4f70b34..b361ca535349 100644 --- a/static/app/gettingStartedDocs/node/utils.tsx +++ b/static/app/gettingStartedDocs/node/utils.tsx @@ -102,8 +102,8 @@ export function getImport( ]; } -function getProfilingImport(defaultMode?: 'esm' | 'cjs'): string { - return defaultMode === 'esm' +function getProfilingImport(defaultMode?: 'esm' | 'cjs' | 'esm-only'): string { + return defaultMode === 'esm' || defaultMode === 'esm-only' ? 'import { nodeProfilingIntegration } from "@sentry/profiling-node";' : 'const { nodeProfilingIntegration } = require("@sentry/profiling-node");'; } @@ -144,7 +144,7 @@ function getDefaultNodeImports({ }: { params: DocsParams; sdkImport: 'node' | 'aws' | 'gpc' | 'nestjs' | null; - defaultMode?: 'esm' | 'cjs'; + defaultMode?: 'esm' | 'cjs' | 'esm-only'; }) { if (sdkImport === null || !libraryMap[sdkImport]) { return ''; @@ -579,7 +579,7 @@ Sentry.logger.info('User triggered test log', { action: 'test_log' })`, export const getSdkInitSnippet = ( params: DocsParams, sdkImport: 'node' | 'aws' | 'gpc' | 'nestjs' | null, - defaultMode?: 'esm' | 'cjs' + defaultMode?: 'esm' | 'cjs' | 'esm-only' ) => `${getDefaultNodeImports({params, sdkImport, defaultMode})} Sentry.init({ From cd1d4968fa0d09578e1848436a8b03047f435df0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jan=20Peer=20St=C3=B6cklmair?= Date: Tue, 22 Sep 2026 17:16:14 +0300 Subject: [PATCH 08/21] feat(onboarding): Update Bun onboarding for SDK v11 (#123797) closes SDK-1459 The Bun onboarding still described the v10 SDK: a single `Sentry.init()` call in an unnamed file, no `dataCollection` options, and no logs, metrics or MCP guides. The configure step now splits the setup into an `instrument.ts` file and a `bun --preload` command that runs it before every other module, which is what the `Bun.serve` and `node:http` instrumentation needs. When tracing is selected, it documents `sentryBunPlugin()`, because libraries such as `mysql` and `postgres` are instrumented at build time on Bun. Add logs, metrics and MCP onboarding. They share one install block and one `Sentry.init()` snippet from `utils.tsx`, so the snippets cannot drift apart. The install block uses `bun add` instead of the npm/yarn/pnpm tabs of the Node helpers. The metrics guide also documents `bunRuntimeMetricsIntegration` for CPU, memory and event loop metrics. --------- Co-authored-by: isaacs --- .../onboarding/productSelection.tsx | 6 +- static/app/data/platformCategories.tsx | 2 + static/app/gettingStartedDocs/bun/index.tsx | 17 +- static/app/gettingStartedDocs/bun/logs.tsx | 87 +++++++++ static/app/gettingStartedDocs/bun/mcp.tsx | 79 ++++++++ static/app/gettingStartedDocs/bun/metrics.tsx | 82 ++++++++ .../bun/onboarding.spec.tsx | 70 ++++++- .../app/gettingStartedDocs/bun/onboarding.tsx | 182 ++++++++++++++---- static/app/gettingStartedDocs/bun/utils.tsx | 68 +++++++ 9 files changed, 547 insertions(+), 46 deletions(-) create mode 100644 static/app/gettingStartedDocs/bun/logs.tsx create mode 100644 static/app/gettingStartedDocs/bun/mcp.tsx create mode 100644 static/app/gettingStartedDocs/bun/metrics.tsx create mode 100644 static/app/gettingStartedDocs/bun/utils.tsx diff --git a/static/app/components/onboarding/productSelection.tsx b/static/app/components/onboarding/productSelection.tsx index 796817714f7e..b8c38bd046ef 100644 --- a/static/app/components/onboarding/productSelection.tsx +++ b/static/app/components/onboarding/productSelection.tsx @@ -93,7 +93,11 @@ export function getDisabledProducts(organization: Organization): DisabledProduct // NOTE: Please keep the prefix in alphabetical order export const platformProductAvailability = { 'apple-macos': [ProductSolution.PERFORMANCE_MONITORING, ProductSolution.PROFILING], - bun: [ProductSolution.PERFORMANCE_MONITORING, ProductSolution.LOGS], + bun: [ + ProductSolution.PERFORMANCE_MONITORING, + ProductSolution.LOGS, + ProductSolution.METRICS, + ], capacitor: [ProductSolution.PERFORMANCE_MONITORING, ProductSolution.LOGS], dotnet: [ ProductSolution.PERFORMANCE_MONITORING, diff --git a/static/app/data/platformCategories.tsx b/static/app/data/platformCategories.tsx index 8275c27036e0..09cc91ac82b4 100644 --- a/static/app/data/platformCategories.tsx +++ b/static/app/data/platformCategories.tsx @@ -416,6 +416,7 @@ export const withMetricsOnboarding = new Set([ 'apple', 'apple-ios', 'apple-macos', + 'bun', 'dotnet', 'dotnet-aspnet', 'dotnet-aspnetcore', @@ -880,4 +881,5 @@ export const mcpMonitoringPlatforms: ReadonlySet = new Set([ ...javascriptMetaFrameworks, ...platformKeys.filter(id => id.startsWith('node')), ...platformKeys.filter(id => id.startsWith('python')), + 'bun', ]); diff --git a/static/app/gettingStartedDocs/bun/index.tsx b/static/app/gettingStartedDocs/bun/index.tsx index 88c563395030..3d345ec5ae0a 100644 --- a/static/app/gettingStartedDocs/bun/index.tsx +++ b/static/app/gettingStartedDocs/bun/index.tsx @@ -5,10 +5,13 @@ import { } from 'sentry/gettingStartedDocs/javascript/jsLoader'; import {agentMonitoring} from 'sentry/gettingStartedDocs/node/agentMonitoring'; import {featureFlag} from 'sentry/gettingStartedDocs/node/featureFlag'; -import {getNodeLogsOnboarding} from 'sentry/gettingStartedDocs/node/utils'; import {crashReport} from './crashReport'; +import {logs} from './logs'; +import {mcp} from './mcp'; +import {metrics} from './metrics'; import {onboarding} from './onboarding'; +import {PACKAGE_NAME, sentryImport} from './utils'; export const docs: Docs = { onboarding, @@ -16,13 +19,13 @@ export const docs: Docs = { crashReportOnboarding: crashReport, feedbackOnboardingJsLoader, featureFlagOnboarding: featureFlag({ - packageName: '@sentry/bun', - }), - logsOnboarding: getNodeLogsOnboarding({ - docsPlatform: 'bun', - packageName: '@sentry/bun', + packageName: PACKAGE_NAME, + sentryImport, }), agentMonitoringOnboarding: agentMonitoring({ - packageName: '@sentry/bun', + packageName: PACKAGE_NAME, }), + logsOnboarding: logs, + mcpOnboarding: mcp, + metricsOnboarding: metrics, }; diff --git a/static/app/gettingStartedDocs/bun/logs.tsx b/static/app/gettingStartedDocs/bun/logs.tsx new file mode 100644 index 000000000000..6f71de0b8947 --- /dev/null +++ b/static/app/gettingStartedDocs/bun/logs.tsx @@ -0,0 +1,87 @@ +import {ExternalLink} from '@sentry/scraps/link'; + +import type {OnboardingConfig} from 'sentry/components/onboarding/gettingStartedDoc/types'; +import {StepType} from 'sentry/components/onboarding/gettingStartedDoc/types'; +import {t, tct} from 'sentry/locale'; + +import { + getInstallContent, + getMigrationContent, + PACKAGE_NAME, + sentryImport, +} from './utils'; + +export const logs: OnboardingConfig = { + install: () => [ + { + type: StepType.INSTALL, + content: [ + ...getInstallContent( + tct( + 'Add the Sentry SDK as a dependency. The minimum version of [packageName] that supports logs is [code:9.41.0].', + { + code: , + packageName: {PACKAGE_NAME}, + } + ) + ), + getMigrationContent(), + ], + }, + ], + configure: params => [ + { + type: StepType.CONFIGURE, + content: [ + { + type: 'text', + text: tct( + 'Logs are enabled by default. To also capture your [code:console] logs, add the [code:consoleLoggingIntegration] to your [code:Sentry.init()] configuration.', + {code: } + ), + }, + { + type: 'code', + language: 'typescript', + code: `${sentryImport} + +Sentry.init({ + dsn: "${params.dsn.public}", + integrations: [ + // send console.log, console.warn, and console.error calls as logs to Sentry + Sentry.consoleLoggingIntegration({ levels: ["log", "warn", "error"] }), + ], +});`, + }, + { + type: 'text', + text: tct('For more detailed information, see the [link:logs documentation].', { + link: ( + + ), + }), + }, + ], + }, + ], + verify: () => [ + { + type: StepType.VERIFY, + content: [ + { + type: 'text', + text: t( + 'Send a test log from your app, then refresh this page to verify it arrived in Sentry.' + ), + }, + { + type: 'code', + language: 'typescript', + code: `${sentryImport} + +Sentry.logger.info('User triggered test log', { action: 'test_log' })`, + }, + ], + }, + ], +}; diff --git a/static/app/gettingStartedDocs/bun/mcp.tsx b/static/app/gettingStartedDocs/bun/mcp.tsx new file mode 100644 index 000000000000..efa4efefc08a --- /dev/null +++ b/static/app/gettingStartedDocs/bun/mcp.tsx @@ -0,0 +1,79 @@ +import type {OnboardingConfig} from 'sentry/components/onboarding/gettingStartedDoc/types'; +import {StepType} from 'sentry/components/onboarding/gettingStartedDoc/types'; +import {t, tct} from 'sentry/locale'; + +import {getInstallContent, sentryImport} from './utils'; + +export const mcp: OnboardingConfig = { + install: () => [ + { + type: StepType.INSTALL, + content: getInstallContent( + tct( + 'To enable MCP monitoring, you need to install the Sentry SDK with a minimum version of [code:9.44.0].', + {code: } + ) + ), + }, + ], + configure: params => [ + { + type: StepType.CONFIGURE, + content: [ + { + type: 'text', + text: tct('Initialize the Sentry SDK by calling [code:Sentry.init()]:', { + code: , + }), + }, + { + type: 'code', + language: 'typescript', + code: `${sentryImport} + +Sentry.init({ + dsn: "${params.dsn.public}", + // Tracing must be enabled for MCP monitoring to work + tracesSampleRate: 1.0, + dataCollection: { + // To disable sending user data and HTTP bodies, uncomment the lines below. For more info visit: + // https://docs.sentry.io/platforms/javascript/guides/bun/configuration/options/#dataCollection + // userInfo: false, + // httpBodies: [], + }, +});`, + }, + { + type: 'text', + text: tct( + 'Wrap your MCP server in a [code:Sentry.wrapMcpServerWithSentry()] call. This will automatically capture spans for all MCP server interactions.', + {code: } + ), + }, + { + type: 'code', + language: 'typescript', + code: `import { McpServer } from "@modelcontextprotocol/sdk"; + +const server = Sentry.wrapMcpServerWithSentry(new McpServer({ + name: "my-mcp-server", + version: "1.0.0", +}));`, + }, + ], + }, + ], + verify: () => [ + { + type: StepType.VERIFY, + content: [ + { + type: 'text', + text: t( + 'Verify that MCP monitoring is working correctly by triggering some MCP server interactions in your application.' + ), + }, + ], + }, + ], +}; diff --git a/static/app/gettingStartedDocs/bun/metrics.tsx b/static/app/gettingStartedDocs/bun/metrics.tsx new file mode 100644 index 000000000000..f1bab6c63977 --- /dev/null +++ b/static/app/gettingStartedDocs/bun/metrics.tsx @@ -0,0 +1,82 @@ +import {ExternalLink} from '@sentry/scraps/link'; + +import type {OnboardingConfig} from 'sentry/components/onboarding/gettingStartedDoc/types'; +import {StepType} from 'sentry/components/onboarding/gettingStartedDoc/types'; +import {tct} from 'sentry/locale'; + +import { + getInstallContent, + getMigrationContent, + getSdkInitSnippet, + PACKAGE_NAME, +} from './utils'; + +export const metrics: OnboardingConfig = { + install: () => [ + { + type: StepType.INSTALL, + content: [ + ...getInstallContent( + tct( + 'Add the Sentry SDK as a dependency. The minimum version of [packageName] that supports metrics is [code:10.25.0].', + { + code: , + packageName: {PACKAGE_NAME}, + } + ) + ), + getMigrationContent(), + ], + }, + ], + configure: () => [], + verify: params => [ + { + type: StepType.VERIFY, + content: [ + { + type: 'text', + text: tct( + 'Metrics are automatically enabled after Sentry is initialized. You can emit metrics using the [code:Sentry.metrics] API.', + {code: } + ), + }, + { + type: 'code', + language: 'typescript', + code: `${getSdkInitSnippet(params)} + +Sentry.metrics.count('button_click', 1); +Sentry.metrics.gauge('page_load_time', 150); +Sentry.metrics.distribution('response_time', 200);`, + }, + { + type: 'text', + text: tct( + 'To also collect CPU, memory and event loop metrics of the Bun process, add the [code:bunRuntimeMetricsIntegration] to your [code:Sentry.init()] configuration. It needs [minVersion] or newer.', + {code: , minVersion: 10.47.0} + ), + }, + { + type: 'code', + language: 'typescript', + code: `Sentry.init({ + dsn: "${params.dsn.public}", + integrations: [Sentry.bunRuntimeMetricsIntegration()], +});`, + }, + { + type: 'text', + text: tct( + 'For more detailed information, see the [link:metrics documentation].', + { + link: ( + + ), + } + ), + }, + ], + }, + ], +}; diff --git a/static/app/gettingStartedDocs/bun/onboarding.spec.tsx b/static/app/gettingStartedDocs/bun/onboarding.spec.tsx index 1cdbec860075..953e3e24a749 100644 --- a/static/app/gettingStartedDocs/bun/onboarding.spec.tsx +++ b/static/app/gettingStartedDocs/bun/onboarding.spec.tsx @@ -15,10 +15,42 @@ describe('bun onboarding docs', () => { expect(screen.getByRole('heading', {name: 'Configure SDK'})).toBeInTheDocument(); expect(screen.getByRole('heading', {name: 'Verify'})).toBeInTheDocument(); + // Renders the instrument file and the entry point that imports it + expect( + screen.getAllByText( + textWithMarkupMatcher(/import \* as Sentry from "@sentry\/bun";/) + ).length + ).toBeGreaterThan(0); + expect( + screen.getByText(textWithMarkupMatcher(/import "\.\/instrument\.ts";/)) + ).toBeInTheDocument(); + + // Renders the build step, which is what injects the library instrumentation + expect( + screen.getByText(textWithMarkupMatcher(/plugins: \[sentryBunPlugin\(\)\],/)) + ).toBeInTheDocument(); + + // Renders the filename header above each snippet + expect(screen.getByText('build.ts')).toBeInTheDocument(); + // Renders config options expect( screen.getByText(textWithMarkupMatcher(/tracesSampleRate: 1\.0,/)) ).toBeInTheDocument(); + expect( + screen.getByText(textWithMarkupMatcher(/dataCollection: \{/)) + ).toBeInTheDocument(); + }); + + it('renders the outgoing request note when tracing is selected', () => { + renderWithOnboardingLayout(docs, { + selectedProducts: [ProductSolution.PERFORMANCE_MONITORING], + }); + + expect( + screen.getByText(textWithMarkupMatcher(/are not traced on Bun yet/)) + ).toBeInTheDocument(); + expect(screen.getByText('Tracing')).toBeInTheDocument(); }); it('renders without tracing', () => { @@ -30,21 +62,53 @@ describe('bun onboarding docs', () => { expect( screen.queryByText(textWithMarkupMatcher(/tracesSampleRate: 1\.0,/)) ).not.toBeInTheDocument(); + + // Does not render the outgoing request note + expect( + screen.queryByText(textWithMarkupMatcher(/are not traced on Bun yet/)) + ).not.toBeInTheDocument(); }); - it('displays logs integration next step when logs are selected', () => { + it('displays logging code in verify section when logs are selected', () => { renderWithOnboardingLayout(docs, { selectedProducts: [ProductSolution.ERROR_MONITORING, ProductSolution.LOGS], }); + expect( + screen.getByText( + textWithMarkupMatcher(/Sentry\.logger\.info\('User triggered test error'/) + ) + ).toBeInTheDocument(); expect(screen.getByText('Logging Integrations')).toBeInTheDocument(); }); - it('does not display logs integration next step when logs are not selected', () => { + it('displays metrics code in verify section when metrics are selected', () => { + renderWithOnboardingLayout(docs, { + selectedProducts: [ProductSolution.ERROR_MONITORING, ProductSolution.METRICS], + }); + + expect( + screen.getByText( + textWithMarkupMatcher(/Sentry\.metrics\.count\('test_counter', 1\)/) + ) + ).toBeInTheDocument(); + expect(screen.getByText('Application Metrics')).toBeInTheDocument(); + }); + + it('does not display logs or metrics code when they are not selected', () => { renderWithOnboardingLayout(docs, { selectedProducts: [ProductSolution.ERROR_MONITORING], }); - expect(screen.queryByText('Logging Integrations')).not.toBeInTheDocument(); + expect( + screen.queryByText( + textWithMarkupMatcher(/Sentry\.logger\.info\('User triggered test error'/) + ) + ).not.toBeInTheDocument(); + expect( + screen.queryByText( + textWithMarkupMatcher(/Sentry\.metrics\.count\('test_counter', 1\)/) + ) + ).not.toBeInTheDocument(); }); }); diff --git a/static/app/gettingStartedDocs/bun/onboarding.tsx b/static/app/gettingStartedDocs/bun/onboarding.tsx index af908a1a25c2..ca3eb437cb52 100644 --- a/static/app/gettingStartedDocs/bun/onboarding.tsx +++ b/static/app/gettingStartedDocs/bun/onboarding.tsx @@ -3,68 +3,158 @@ import type { OnboardingConfig, } from 'sentry/components/onboarding/gettingStartedDoc/types'; import {StepType} from 'sentry/components/onboarding/gettingStartedDoc/types'; -import {t} from 'sentry/locale'; +import {t, tct} from 'sentry/locale'; -type Params = DocsParams; +import {getInstallContent, getSdkInitSnippet, PACKAGE_NAME, sentryImport} from './utils'; -const getConfigureSnippet = (params: Params) => ` -//... -import * as Sentry from "@sentry/bun"; +const INSTRUMENT_FILENAME = 'instrument.ts'; +const ENTRY_POINT_FILENAME = 'index.ts'; +const BUILD_FILENAME = 'build.ts'; -Sentry.init({ - dsn: "${params.dsn.public}",${ - params.isPerformanceSelected +const getEntryPointSnippet = () => `import "./${INSTRUMENT_FILENAME}"; + +// All other imports below +Bun.serve({ + fetch: () => new Response("Hello World!"), +});`; + +const getBuildSnippet = () => `import { sentryBunPlugin } from "${PACKAGE_NAME}/plugin"; + +await Bun.build({ + entrypoints: ["./${ENTRY_POINT_FILENAME}"], + target: "bun", + outdir: "./dist", + plugins: [sentryBunPlugin()], +});`; + +/** + * The log and metric calls the verify snippet makes before it throws. + * + * @param indent The whitespace put in front of every line. + */ +const getVerifySignalsSnippet = (params: DocsParams, indent: string) => + `${ + params.isLogsSelected ? ` - // Tracing - tracesSampleRate: 1.0, // Capture 100% of the transactions` +${indent}// Send a log before throwing the error +${indent}Sentry.logger.info('User triggered test error', { +${indent} action: 'test_error', +${indent}});` : '' - } + }${ + params.isMetricsSelected + ? ` +${indent}// Send a test metric before throwing the error +${indent}Sentry.metrics.count('test_counter', 1);` + : '' + }`; + +const getVerifySnippet = (params: DocsParams) => { + if (!params.isPerformanceSelected) { + return `${sentryImport} +${getVerifySignalsSnippet(params, '')} + +setTimeout(() => { + throw new Error(); });`; + } -const getVerifySnippet = () => `try { - throw new Error('Sentry Bun test'); -} catch (e) { - Sentry.captureException(e); -}`; + return `${sentryImport} + +Sentry.startSpan({ + op: "test", + name: "My First Test Span", +}, () => {${getVerifySignalsSnippet(params, ' ')} + throw new Error(); +});`; +}; export const onboarding: OnboardingConfig = { + introduction: () => + t( + "In this quick guide you'll set up and configure the Sentry Bun SDK for the use in your Bun application." + ), install: () => [ { type: StepType.INSTALL, + content: getInstallContent(t('Add the Sentry Bun SDK as a dependency:')), + }, + ], + configure: params => [ + { + type: StepType.CONFIGURE, content: [ { type: 'text', - text: t( - "Sentry captures data by using an SDK within your application's runtime." + text: tct( + 'Put the [code:Sentry.init()] call in its own [code:instrument.ts] file, so that it runs before the rest of your code.', + {code: } ), }, { type: 'code', - language: 'bash', - code: 'bun add @sentry/bun', + tabs: [ + { + label: 'TypeScript', + language: 'typescript', + filename: INSTRUMENT_FILENAME, + code: getSdkInitSnippet(params), + }, + ], }, - ], - }, - ], - configure: params => [ - { - type: StepType.CONFIGURE, - content: [ { type: 'text', - text: t( - "Initialize Sentry as early as possible in your application's lifecycle." + text: tct( + 'Import [code:instrument.ts] at the top of your entry point, before every other import. Incoming [code:Bun.serve] and [code:node:http] requests are then instrumented for you.', + {code: } ), }, { type: 'code', - language: 'javascript', - code: getConfigureSnippet(params), + tabs: [ + { + label: 'TypeScript', + language: 'typescript', + filename: ENTRY_POINT_FILENAME, + code: getEntryPointSnippet(), + }, + ], + }, + { + type: 'text', + text: tct( + 'Libraries such as [code:express], [code:mysql] and [code:postgres] are instrumented while your app is bundled. [code:bun run] cannot do this, so build your app with [code:sentryBunPlugin()] to get their spans and their errors.', + {code: } + ), + }, + { + type: 'code', + tabs: [ + { + label: 'TypeScript', + language: 'typescript', + filename: BUILD_FILENAME, + code: getBuildSnippet(), + }, + ], + }, + { + type: 'conditional', + condition: params.isPerformanceSelected, + content: [ + { + type: 'text', + text: tct( + 'Outgoing requests are traced when you send them with [code:fetch]. Clients that use [code:node:http], for example the [code:axios] HTTP adapter, are not traced on Bun yet.', + {code: } + ), + }, + ], }, ], }, ], - verify: () => [ + verify: params => [ { type: StepType.VERIFY, content: [ @@ -76,15 +166,26 @@ export const onboarding: OnboardingConfig = { }, { type: 'code', - language: 'javascript', - code: getVerifySnippet(), + language: 'typescript', + code: getVerifySnippet(params), }, ], }, ], - nextSteps: (params: Params) => { + nextSteps: params => { const steps = []; + if (params.isPerformanceSelected) { + steps.push({ + id: 'tracing', + name: t('Tracing'), + description: t( + 'Learn which libraries the SDK instruments for you, and how to add your own spans.' + ), + link: 'https://docs.sentry.io/platforms/javascript/guides/bun/tracing/', + }); + } + if (params.isLogsSelected) { steps.push({ id: 'logs', @@ -96,6 +197,17 @@ export const onboarding: OnboardingConfig = { }); } + if (params.isMetricsSelected) { + steps.push({ + id: 'metrics', + name: t('Application Metrics'), + description: t( + 'Learn how to track custom metrics to monitor your application performance and business KPIs.' + ), + link: 'https://docs.sentry.io/platforms/javascript/guides/bun/metrics/', + }); + } + return steps; }, }; diff --git a/static/app/gettingStartedDocs/bun/utils.tsx b/static/app/gettingStartedDocs/bun/utils.tsx new file mode 100644 index 000000000000..5b2a6a859168 --- /dev/null +++ b/static/app/gettingStartedDocs/bun/utils.tsx @@ -0,0 +1,68 @@ +import {ExternalLink} from '@sentry/scraps/link'; + +import type { + ContentBlock, + DocsParams, +} from 'sentry/components/onboarding/gettingStartedDoc/types'; +import {tct} from 'sentry/locale'; + +export const PACKAGE_NAME = '@sentry/bun'; + +export const sentryImport = `import * as Sentry from "${PACKAGE_NAME}";`; + +/** + * The install step content, shared by every onboarding on this platform. + * + * @param text The sentence above the code block. + */ +export function getInstallContent(text: React.ReactNode): ContentBlock[] { + return [ + {type: 'text', text}, + { + type: 'code', + language: 'bash', + code: `bun add ${PACKAGE_NAME}`, + }, + ]; +} + +export function getMigrationContent(): ContentBlock { + return { + type: 'text', + text: tct( + 'If you are on an older version of the SDK, follow our [link:migration guide] to upgrade.', + { + link: ( + + ), + } + ), + }; +} + +/** + * The `Sentry.init()` call, shared by every snippet on this platform so that + * they cannot drift apart. + */ +export function getSdkInitSnippet(params: DocsParams) { + return `${sentryImport} + +Sentry.init({ + dsn: "${params.dsn.public}",${ + params.isPerformanceSelected + ? ` + // Set tracesSampleRate to 1.0 to capture 100% of spans for tracing. + // Learn more at + // https://docs.sentry.io/platforms/javascript/configuration/options/#traces-sample-rate + tracesSampleRate: 1.0,` + : '' + } + + dataCollection: { + // To disable sending user data and HTTP bodies, uncomment the lines below. For more info visit: + // https://docs.sentry.io/platforms/javascript/guides/bun/configuration/options/#dataCollection + // userInfo: false, + // httpBodies: [], + }, +});`; +} From 301d4921bb39c97383835c01287b9fdded2596e8 Mon Sep 17 00:00:00 2001 From: Andrei <168741329+andreiborza@users.noreply.github.com> Date: Tue, 22 Sep 2026 23:49:15 +0900 Subject: [PATCH 09/21] ref(onboarding): Drop the Express error handler for v11 (#123779) ## What Switches the Express onboarding to ESM with a `node --import ./instrument.js` start command, and removes the `Sentry.setupExpressErrorHandler(app)` call together with the `res.sentry` fallthrough handler. ## Why v11 no longer supports `--require`, and `expressIntegration()` now captures route handler errors automatically. --- .../node-express/onboarding.spec.tsx | 18 +++++-- .../node-express/onboarding.tsx | 50 ++++++++++--------- 2 files changed, 41 insertions(+), 27 deletions(-) diff --git a/static/app/gettingStartedDocs/node-express/onboarding.spec.tsx b/static/app/gettingStartedDocs/node-express/onboarding.spec.tsx index aaa32bde2633..512adffaee9f 100644 --- a/static/app/gettingStartedDocs/node-express/onboarding.spec.tsx +++ b/static/app/gettingStartedDocs/node-express/onboarding.spec.tsx @@ -29,14 +29,24 @@ describe('express onboarding docs', () => { }); }); - it('includes error handler', () => { + it('starts the app with the --import flag', () => { renderWithOnboardingLayout(docs); expect( - screen.getByText(textWithMarkupMatcher(/Sentry\.setupExpressErrorHandler\(app\)/)) + screen.getByText( + textWithMarkupMatcher(/node --import \.\/instrument\.js index\.js/) + ) ).toBeInTheDocument(); }); + it('does not include the deprecated express error handler', () => { + renderWithOnboardingLayout(docs); + + expect( + screen.queryByText(textWithMarkupMatcher(/Sentry\.setupExpressErrorHandler/)) + ).not.toBeInTheDocument(); + }); + it('displays sample rates by default', () => { renderWithOnboardingLayout(docs, { selectedProducts: [ @@ -115,7 +125,7 @@ describe('express onboarding docs', () => { expect( screen.getByText( textWithMarkupMatcher( - /const { nodeProfilingIntegration } = require\("@sentry\/profiling-node"\)/ + /import { nodeProfilingIntegration } from "@sentry\/profiling-node"/ ) ) ).toBeInTheDocument(); @@ -140,7 +150,7 @@ describe('express onboarding docs', () => { expect( screen.getByText( textWithMarkupMatcher( - /const { nodeProfilingIntegration } = require\("@sentry\/profiling-node"\)/ + /import { nodeProfilingIntegration } from "@sentry\/profiling-node"/ ) ) ).toBeInTheDocument(); diff --git a/static/app/gettingStartedDocs/node-express/onboarding.tsx b/static/app/gettingStartedDocs/node-express/onboarding.tsx index b25697c335a9..99319b91ab88 100644 --- a/static/app/gettingStartedDocs/node-express/onboarding.tsx +++ b/static/app/gettingStartedDocs/node-express/onboarding.tsx @@ -7,19 +7,15 @@ import type { import {StepType} from 'sentry/components/onboarding/gettingStartedDoc/types'; import {getUploadSourceMapsStep} from 'sentry/components/onboarding/gettingStartedDoc/utils'; import { - getImportInstrumentSnippet, + getImport, getInstallCodeBlock, getSdkInitSnippet, - getSentryImportSnippet, } from 'sentry/gettingStartedDocs/node/utils'; import {t, tct} from 'sentry/locale'; const getSdkSetupSnippet = () => ` -${getImportInstrumentSnippet()} - -// All other imports below -${getSentryImportSnippet('@sentry/node')} -const express = require("express"); +${getImport('@sentry/node', 'esm-only').join('\n')} +import express from "express"; const app = express(); @@ -29,17 +25,6 @@ app.get("/", function rootHandler(req, res) { res.end("Hello world!"); }); -// The error handler must be registered before any other error middleware and after all controllers -Sentry.setupExpressErrorHandler(app); - -// Optional fallthrough error handler -app.use(function onError(err, req, res, next) { - // The error id is attached to \`res.sentry\` to be returned - // and optionally displayed to the user for support. - res.statusCode = 500; - res.end(res.sentry + "\\n"); -}); - app.listen(3000); `; @@ -76,7 +61,7 @@ export const onboarding: OnboardingConfig = { { type: 'text', text: tct( - 'To initialize the SDK before everything else, create an external file called [code:instrument.js/mjs].', + 'To initialize the SDK before everything else, create an external file called [code:instrument.js]. These snippets use ESM syntax, so your [code:package.json] needs [code:"type": "module"].', {code: } ), }, @@ -86,15 +71,15 @@ export const onboarding: OnboardingConfig = { { label: 'JavaScript', language: 'javascript', - filename: 'instrument.(js|mjs)', - code: getSdkInitSnippet(params, 'node'), + filename: 'instrument.js', + code: getSdkInitSnippet(params, 'node', 'esm-only'), }, ], }, { type: 'text', text: tct( - "Make sure to import [code:instrument.js/mjs] at the top of your file. Set up the error handler after all controllers and before any other error middleware. This setup is typically done in your application's entry point file, which is usually [code:index.(js|ts)]. If you're running your application in ESM mode, or looking for alternative ways to set up Sentry, read about [docs:installation methods in our docs].", + 'Start your application with the [code:--import] flag, so that [code:instrument.js] loads before any other module. For alternative ways to set up Sentry, read about [docs:installation methods in our docs].', { code: , docs: ( @@ -103,17 +88,36 @@ export const onboarding: OnboardingConfig = { } ), }, + { + type: 'code', + language: 'bash', + code: 'node --import ./instrument.js index.js', + }, + { + type: 'text', + text: tct( + 'This is what your application entry point, usually [code:index.js], looks like:', + {code: } + ), + }, { type: 'code', tabs: [ { label: 'JavaScript', language: 'javascript', - filename: 'index.(js|mjs)', + filename: 'index.js', code: getSdkSetupSnippet(), }, ], }, + { + type: 'text', + text: tct( + 'The default [code:expressIntegration] captures errors from your route handlers automatically. You do not have to add an error handler.', + {code: } + ), + }, ], }, getUploadSourceMapsStep({ From 45627ce31a4253a9493aeec3b8747f9a2c2d0ccb Mon Sep 17 00:00:00 2001 From: Andrei <168741329+andreiborza@users.noreply.github.com> Date: Tue, 22 Sep 2026 23:49:38 +0900 Subject: [PATCH 10/21] ref(onboarding): Fix the SolidStart server instrument file setup (#123852) ## What Moves the SolidStart server instrument file from `public/instrument.server.mjs` to `src/instrument.server.ts`, adds the missing `withSentry` wrapper in `app.config.ts`, and points the `--import` flag at the build output. ## Why The old placement served the instrument file to the internet, which the guide had to warn about. Wrapping the config with `withSentry` lets the build emit the file into `.output/server/` instead, matching our docs and e2e apps. --- .../javascript-solidstart/onboarding.spec.tsx | 2 +- .../javascript-solidstart/onboarding.tsx | 69 ++++++++++--------- 2 files changed, 37 insertions(+), 34 deletions(-) diff --git a/static/app/gettingStartedDocs/javascript-solidstart/onboarding.spec.tsx b/static/app/gettingStartedDocs/javascript-solidstart/onboarding.spec.tsx index 32804595b706..cab493137058 100644 --- a/static/app/gettingStartedDocs/javascript-solidstart/onboarding.spec.tsx +++ b/static/app/gettingStartedDocs/javascript-solidstart/onboarding.spec.tsx @@ -24,7 +24,7 @@ describe('javascript-solidstart onboarding docs', () => { screen.getAllByText(textWithMarkupMatcher(/src\/entry-client\.tsx/)).length ).toBeGreaterThanOrEqual(1); expect( - screen.getAllByText(textWithMarkupMatcher(/public\/instrument\.server\.mjs/)).length + screen.getAllByText(textWithMarkupMatcher(/src\/instrument\.server\.ts/)).length ).toBeGreaterThanOrEqual(1); }); diff --git a/static/app/gettingStartedDocs/javascript-solidstart/onboarding.tsx b/static/app/gettingStartedDocs/javascript-solidstart/onboarding.tsx index 8b1946537a45..e7b52da48763 100644 --- a/static/app/gettingStartedDocs/javascript-solidstart/onboarding.tsx +++ b/static/app/gettingStartedDocs/javascript-solidstart/onboarding.tsx @@ -55,14 +55,21 @@ export default createMiddleware({ }); `; -const getSdkMiddlewareLinkSetup = () => ` +const getAppConfigSetup = (params: DocsParams) => ` +import { withSentry } from "@sentry/solidstart"; import { defineConfig } from "@solidjs/start/config"; -export default defineConfig({ - middleware: "./src/middleware.ts" - // Other configuration options - // ... -}); +export default defineConfig( + withSentry({${ + params.isPerformanceSelected + ? ` + middleware: "./src/middleware.ts",` + : '' + } + // Other configuration options + // ... + }), +); `; const getSdkRouterWrappingSetup = () => ` @@ -84,7 +91,7 @@ export default function App() { const getSdkRun = () => ` { "scripts": { - "start": "NODE_OPTIONS='--import ./public/instrument.server.mjs' vinxi start" + "start": "NODE_OPTIONS='--import ./.output/server/instrument.server.mjs' vinxi start" } } `; @@ -173,7 +180,7 @@ export const onboarding: OnboardingConfig = { { type: 'text', text: tct( - 'For the server, create an instrument file [code:instrument.server.mjs], initialize the Sentry SDK and deploy it alongside your application. For example by placing it in the [code:public] folder.', + 'For the server, create an instrument file [code:src/instrument.server.ts] and initialize the Sentry SDK in it:', {code: } ), }, @@ -181,19 +188,13 @@ export const onboarding: OnboardingConfig = { type: 'code', tabs: [ { - label: 'JavaScript', + label: 'TypeScript', language: 'javascript', + filename: 'src/instrument.server.ts', code: getSdkServerSetupSnippet(params), }, ], }, - { - type: 'text', - text: tct( - 'Note: Placing [code:instrument.server.mjs] inside the [code:public] folder makes it accessible to the outside world. Consider blocking requests to this file or finding a more appropriate location which your backend can access.', - {code: } - ), - }, ...((params.isPerformanceSelected ? [ { @@ -218,22 +219,6 @@ export const onboarding: OnboardingConfig = { }, ], }, - { - type: 'text', - text: tct('And including it in the [code:app.config.ts] file', { - code: , - }), - }, - { - type: 'code', - tabs: [ - { - label: 'TypeScript', - language: 'javascript', - code: getSdkMiddlewareLinkSetup(), - }, - ], - }, { type: 'text', text: tct( @@ -261,7 +246,25 @@ export const onboarding: OnboardingConfig = { { type: 'text', text: tct( - 'Add an [code:--import] flag to the [code:NODE_OPTIONS] environment variable wherever you run your application to import [code:public/instrument.server.mjs]. For example, update your [code:scripts] entry in [code:package.json]', + 'Wrap your config in [code:app.config.ts] with [code:withSentry], so that the build includes your instrument file in the server output.', + {code: } + ), + }, + { + type: 'code', + tabs: [ + { + label: 'TypeScript', + language: 'javascript', + filename: 'app.config.ts', + code: getAppConfigSetup(params), + }, + ], + }, + { + type: 'text', + text: tct( + 'Build your application, then add an [code:--import] flag to the [code:NODE_OPTIONS] environment variable wherever you run it, pointing at the instrument file the build creates at [code:.output/server/instrument.server.mjs]. Your build preset can put the file elsewhere, so check the build log for the path [code:withSentry] reports. For example, update your [code:scripts] entry in [code:package.json]', { code: , } From 444e3b7efb01ec75f4a94cf76fea31fbf44c5873 Mon Sep 17 00:00:00 2001 From: Andrei <168741329+andreiborza@users.noreply.github.com> Date: Tue, 22 Sep 2026 23:49:52 +0900 Subject: [PATCH 11/21] ref(onboarding): Update the Hapi onboarding for v11 (#123836) ## What Switches the Hapi onboarding to ESM with a `node --import ./instrument.js` start command, and removes the `Sentry.setupHapiErrorHandler(server)` call. ## Why v11 no longer supports `--require`, and the Hapi error handler is registered automatically when the server starts. --- .../node-hapi/onboarding.spec.tsx | 18 +++++++-- .../node-hapi/onboarding.tsx | 40 +++++++++++++------ 2 files changed, 41 insertions(+), 17 deletions(-) diff --git a/static/app/gettingStartedDocs/node-hapi/onboarding.spec.tsx b/static/app/gettingStartedDocs/node-hapi/onboarding.spec.tsx index 42d8dd50d345..c7ae4cbc9ff5 100644 --- a/static/app/gettingStartedDocs/node-hapi/onboarding.spec.tsx +++ b/static/app/gettingStartedDocs/node-hapi/onboarding.spec.tsx @@ -29,14 +29,24 @@ describe('hapi onboarding docs', () => { }); }); - it('includes error handler', () => { + it('starts the app with the --import flag', () => { renderWithOnboardingLayout(docs); expect( - screen.getByText(textWithMarkupMatcher(/Sentry\.setupHapiErrorHandler\(server\)/)) + screen.getByText( + textWithMarkupMatcher(/node --import \.\/instrument\.js index\.js/) + ) ).toBeInTheDocument(); }); + it('does not include the deprecated hapi error handler', () => { + renderWithOnboardingLayout(docs); + + expect( + screen.queryByText(textWithMarkupMatcher(/Sentry\.setupHapiErrorHandler/)) + ).not.toBeInTheDocument(); + }); + it('displays sample rates by default', () => { renderWithOnboardingLayout(docs, { selectedProducts: [ @@ -115,7 +125,7 @@ describe('hapi onboarding docs', () => { expect( screen.getByText( textWithMarkupMatcher( - /const { nodeProfilingIntegration } = require\("@sentry\/profiling-node"\)/ + /import { nodeProfilingIntegration } from "@sentry\/profiling-node"/ ) ) ).toBeInTheDocument(); @@ -141,7 +151,7 @@ describe('hapi onboarding docs', () => { expect( screen.getByText( textWithMarkupMatcher( - /const { nodeProfilingIntegration } = require\("@sentry\/profiling-node"\)/ + /import { nodeProfilingIntegration } from "@sentry\/profiling-node"/ ) ) ).toBeInTheDocument(); diff --git a/static/app/gettingStartedDocs/node-hapi/onboarding.tsx b/static/app/gettingStartedDocs/node-hapi/onboarding.tsx index 1b229416b082..7defb6f775f8 100644 --- a/static/app/gettingStartedDocs/node-hapi/onboarding.tsx +++ b/static/app/gettingStartedDocs/node-hapi/onboarding.tsx @@ -7,19 +7,15 @@ import type { import {StepType} from 'sentry/components/onboarding/gettingStartedDoc/types'; import {getUploadSourceMapsStep} from 'sentry/components/onboarding/gettingStartedDoc/utils'; import { - getImportInstrumentSnippet, + getImport, getInstallCodeBlock, getSdkInitSnippet, - getSentryImportSnippet, } from 'sentry/gettingStartedDocs/node/utils'; import {t, tct} from 'sentry/locale'; const getSdkSetupSnippet = () => ` -${getImportInstrumentSnippet()} - -// All other imports below -${getSentryImportSnippet('@sentry/node')} -const Hapi = require('@hapi/hapi'); +${getImport('@sentry/node', 'esm-only').join('\n')} +import Hapi from "@hapi/hapi"; const init = async () => { const server = Hapi.server({ @@ -29,7 +25,6 @@ const init = async () => { // All your routes live here - await Sentry.setupHapiErrorHandler(server); await server.start(); }; @@ -90,7 +85,7 @@ export const onboarding: OnboardingConfig = { { type: 'text', text: tct( - 'To initialize the SDK before everything else, create an external file called [code:instrument.js/mjs].', + 'To initialize the SDK before everything else, create an external file called [code:instrument.js]. These snippets use ESM syntax, so your [code:package.json] needs [code:"type": "module"].', {code: } ), }, @@ -100,15 +95,15 @@ export const onboarding: OnboardingConfig = { { label: 'JavaScript', language: 'javascript', - filename: 'instrument.(js|mjs)', - code: getSdkInitSnippet(params, 'node'), + filename: 'instrument.js', + code: getSdkInitSnippet(params, 'node', 'esm-only'), }, ], }, { type: 'text', text: tct( - "Make sure to import [code:instrument.js/mjs] at the top of your file. Set up the error handler. This setup is typically done in your application's entry point file, which is usually [code:index.(js|ts)]. If you're running your application in ESM mode, or looking for alternative ways to set up Sentry, read about [docs:installation methods in our docs].", + 'Start your application with the [code:--import] flag, so that [code:instrument.js] loads before any other module. For alternative ways to set up Sentry, read about [docs:installation methods in our docs].', { code: , docs: ( @@ -117,17 +112,36 @@ export const onboarding: OnboardingConfig = { } ), }, + { + type: 'code', + language: 'bash', + code: 'node --import ./instrument.js index.js', + }, + { + type: 'text', + text: tct( + 'This is what your application entry point, usually [code:index.js], looks like:', + {code: } + ), + }, { type: 'code', tabs: [ { label: 'JavaScript', language: 'javascript', - filename: 'index.(js|mjs)', + filename: 'index.js', code: getSdkSetupSnippet(), }, ], }, + { + type: 'text', + text: tct( + 'The default [code:hapiIntegration] captures errors from your routes automatically. You do not have to add an error handler.', + {code: } + ), + }, ], }, getUploadSourceMapsStep({ From cf13725d29c0c3e5166d6705f190c86e4b25f98a Mon Sep 17 00:00:00 2001 From: Andrei <168741329+andreiborza@users.noreply.github.com> Date: Tue, 22 Sep 2026 23:50:04 +0900 Subject: [PATCH 12/21] ref(onboarding): Update the Koa onboarding for v11 (#123845) ## What Switches the Koa onboarding to ESM with a `node --import ./instrument.js` start command, and removes the `Sentry.setupKoaErrorHandler(app)` call. ## Why v11 no longer supports `--require`, and the Koa error handler is registered automatically when the app starts. --- .../node-koa/onboarding.spec.tsx | 18 ++++++-- .../node-koa/onboarding.tsx | 41 ++++++++++++------- 2 files changed, 41 insertions(+), 18 deletions(-) diff --git a/static/app/gettingStartedDocs/node-koa/onboarding.spec.tsx b/static/app/gettingStartedDocs/node-koa/onboarding.spec.tsx index fee5843a341d..9aaf1303e3ba 100644 --- a/static/app/gettingStartedDocs/node-koa/onboarding.spec.tsx +++ b/static/app/gettingStartedDocs/node-koa/onboarding.spec.tsx @@ -29,14 +29,24 @@ describe('koa onboarding docs', () => { }); }); - it('includes error handler', () => { + it('starts the app with the --import flag', () => { renderWithOnboardingLayout(docs); expect( - screen.getByText(textWithMarkupMatcher(/Sentry\.setupKoaErrorHandler\(app\)/)) + screen.getByText( + textWithMarkupMatcher(/node --import \.\/instrument\.js index\.js/) + ) ).toBeInTheDocument(); }); + it('does not include the deprecated koa error handler', () => { + renderWithOnboardingLayout(docs); + + expect( + screen.queryByText(textWithMarkupMatcher(/Sentry\.setupKoaErrorHandler/)) + ).not.toBeInTheDocument(); + }); + it('displays sample rates by default', () => { renderWithOnboardingLayout(docs, { selectedProducts: [ @@ -115,7 +125,7 @@ describe('koa onboarding docs', () => { expect( screen.getByText( textWithMarkupMatcher( - /const { nodeProfilingIntegration } = require\("@sentry\/profiling-node"\)/ + /import { nodeProfilingIntegration } from "@sentry\/profiling-node"/ ) ) ).toBeInTheDocument(); @@ -141,7 +151,7 @@ describe('koa onboarding docs', () => { expect( screen.getByText( textWithMarkupMatcher( - /const { nodeProfilingIntegration } = require\("@sentry\/profiling-node"\)/ + /import { nodeProfilingIntegration } from "@sentry\/profiling-node"/ ) ) ).toBeInTheDocument(); diff --git a/static/app/gettingStartedDocs/node-koa/onboarding.tsx b/static/app/gettingStartedDocs/node-koa/onboarding.tsx index 36e083ec788a..16b7cb76ce4c 100644 --- a/static/app/gettingStartedDocs/node-koa/onboarding.tsx +++ b/static/app/gettingStartedDocs/node-koa/onboarding.tsx @@ -7,24 +7,18 @@ import type { import {StepType} from 'sentry/components/onboarding/gettingStartedDoc/types'; import {getUploadSourceMapsStep} from 'sentry/components/onboarding/gettingStartedDoc/utils'; import { - getImportInstrumentSnippet, + getImport, getInstallCodeBlock, getSdkInitSnippet, - getSentryImportSnippet, } from 'sentry/gettingStartedDocs/node/utils'; import {t, tct} from 'sentry/locale'; const getSdkSetupSnippet = () => ` -${getImportInstrumentSnippet()} - -// All other imports below -${getSentryImportSnippet('@sentry/node')} -const Koa = require("koa"); +${getImport('@sentry/node', 'esm-only').join('\n')} +import Koa from "koa"; const app = new Koa(); -Sentry.setupKoaErrorHandler(app); - // All your controllers should live here app.listen(3000);`; @@ -79,7 +73,7 @@ export const onboarding: OnboardingConfig = { { type: 'text', text: tct( - 'To initialize the SDK before everything else, create an external file called [code:instrument.js/mjs].', + 'To initialize the SDK before everything else, create an external file called [code:instrument.js]. These snippets use ESM syntax, so your [code:package.json] needs [code:"type": "module"].', {code: } ), }, @@ -89,15 +83,15 @@ export const onboarding: OnboardingConfig = { { label: 'JavaScript', language: 'javascript', - filename: 'instrument.(js|mjs)', - code: getSdkInitSnippet(params, 'node'), + filename: 'instrument.js', + code: getSdkInitSnippet(params, 'node', 'esm-only'), }, ], }, { type: 'text', text: tct( - "Make sure to import [code:instrument.js/mjs] at the top of your file. Set up the error handler after all controllers and before any other error middleware. This setup is typically done in your application's entry point file, which is usually [code:index.(js|ts)]. If you're running your application in ESM mode, or looking for alternative ways to set up Sentry, read about [docs:installation methods in our docs].", + 'Start your application with the [code:--import] flag, so that [code:instrument.js] loads before any other module. For alternative ways to set up Sentry, read about [docs:installation methods in our docs].', { code: , docs: ( @@ -106,17 +100,36 @@ export const onboarding: OnboardingConfig = { } ), }, + { + type: 'code', + language: 'bash', + code: 'node --import ./instrument.js index.js', + }, + { + type: 'text', + text: tct( + 'This is what your application entry point, usually [code:index.js], looks like:', + {code: } + ), + }, { type: 'code', tabs: [ { label: 'JavaScript', language: 'javascript', - filename: 'index.(js|mjs)', + filename: 'index.js', code: getSdkSetupSnippet(), }, ], }, + { + type: 'text', + text: tct( + 'The default [code:koaIntegration] captures errors from your middleware automatically. You do not have to add an error handler.', + {code: } + ), + }, ], }, getUploadSourceMapsStep({ From 2c96432c3fc6e408036991832e866b92f17b1510 Mon Sep 17 00:00:00 2001 From: Andrei <168741329+andreiborza@users.noreply.github.com> Date: Tue, 22 Sep 2026 23:54:13 +0900 Subject: [PATCH 13/21] ref(onboarding): Update the Fastify onboarding for v11 (#123826) ## What Switches the Fastify onboarding to ESM with a `node --import ./instrument.js` start command, and removes the `Sentry.setupFastifyErrorHandler(app)` call. ## Why v11 no longer supports `--require`, and `fastifyIntegration` now captures route handler errors on its own. --- .../node-fastify/onboarding.spec.tsx | 18 +++++-- .../node-fastify/onboarding.tsx | 49 ++++++++++++------- 2 files changed, 45 insertions(+), 22 deletions(-) diff --git a/static/app/gettingStartedDocs/node-fastify/onboarding.spec.tsx b/static/app/gettingStartedDocs/node-fastify/onboarding.spec.tsx index 50244fe026f4..8b23ba056421 100644 --- a/static/app/gettingStartedDocs/node-fastify/onboarding.spec.tsx +++ b/static/app/gettingStartedDocs/node-fastify/onboarding.spec.tsx @@ -29,14 +29,24 @@ describe('fastify onboarding docs', () => { }); }); - it('includes error handler', () => { + it('starts the app with the --import flag', () => { renderWithOnboardingLayout(docs); expect( - screen.getByText(textWithMarkupMatcher(/Sentry\.setupFastifyErrorHandler\(app\)/)) + screen.getByText( + textWithMarkupMatcher(/node --import \.\/instrument\.js index\.js/) + ) ).toBeInTheDocument(); }); + it('does not include the deprecated fastify error handler', () => { + renderWithOnboardingLayout(docs); + + expect( + screen.queryByText(textWithMarkupMatcher(/Sentry\.setupFastifyErrorHandler/)) + ).not.toBeInTheDocument(); + }); + it('displays sample rates by default', () => { renderWithOnboardingLayout(docs, { selectedProducts: [ @@ -115,7 +125,7 @@ describe('fastify onboarding docs', () => { expect( screen.getByText( textWithMarkupMatcher( - /const { nodeProfilingIntegration } = require\("@sentry\/profiling-node"\)/ + /import { nodeProfilingIntegration } from "@sentry\/profiling-node"/ ) ) ).toBeInTheDocument(); @@ -141,7 +151,7 @@ describe('fastify onboarding docs', () => { expect( screen.getByText( textWithMarkupMatcher( - /const { nodeProfilingIntegration } = require\("@sentry\/profiling-node"\)/ + /import { nodeProfilingIntegration } from "@sentry\/profiling-node"/ ) ) ).toBeInTheDocument(); diff --git a/static/app/gettingStartedDocs/node-fastify/onboarding.tsx b/static/app/gettingStartedDocs/node-fastify/onboarding.tsx index 1d78a405f9b7..891f1a23b0e4 100644 --- a/static/app/gettingStartedDocs/node-fastify/onboarding.tsx +++ b/static/app/gettingStartedDocs/node-fastify/onboarding.tsx @@ -7,29 +7,23 @@ import type { import {StepType} from 'sentry/components/onboarding/gettingStartedDoc/types'; import {getUploadSourceMapsStep} from 'sentry/components/onboarding/gettingStartedDoc/utils'; import { - getImportInstrumentSnippet, + getImport, getInstallCodeBlock, getSdkInitSnippet, - getSentryImportSnippet, } from 'sentry/gettingStartedDocs/node/utils'; import {t, tct} from 'sentry/locale'; const getSdkSetupSnippet = () => ` -${getImportInstrumentSnippet()} +${getImport('@sentry/node', 'esm-only').join('\n')} +import Fastify from "fastify"; -// All other imports below -${getSentryImportSnippet('@sentry/node')} -const Fastify = require('fastify') +const fastify = Fastify(); -const app = Fastify(); - -Sentry.setupFastifyErrorHandler(app); - -app.get("/", function rootHandler(req, res) { +fastify.get("/", function rootHandler(req, res) { res.send("Hello world!"); }); -app.listen(3000); +fastify.listen({ port: 3000 }); `; export const onboarding: OnboardingConfig = { @@ -62,7 +56,7 @@ export const onboarding: OnboardingConfig = { { type: 'text', text: tct( - 'To initialize the SDK before everything else, create an external file called [code:instrument.js/mjs].', + 'To initialize the SDK before everything else, create an external file called [code:instrument.js]. These snippets use ESM syntax, so your [code:package.json] needs [code:"type": "module"].', {code: } ), }, @@ -72,15 +66,15 @@ export const onboarding: OnboardingConfig = { { label: 'JavaScript', language: 'javascript', - filename: 'instrument.(js|mjs)', - code: getSdkInitSnippet(params, 'node'), + filename: 'instrument.js', + code: getSdkInitSnippet(params, 'node', 'esm-only'), }, ], }, { type: 'text', text: tct( - "Make sure to import [code:instrument.js/mjs] at the top of your file. Set up the error handler. This setup is typically done in your application's entry point file, which is usually [code:index.(js|ts)]. If you're running your application in ESM mode, or looking for alternative ways to set up Sentry, read about [docs:installation methods in our docs].", + 'Start your application with the [code:--import] flag, so that [code:instrument.js] loads before any other module. For alternative ways to set up Sentry, read about [docs:installation methods in our docs].', { code: , docs: ( @@ -89,17 +83,36 @@ export const onboarding: OnboardingConfig = { } ), }, + { + type: 'code', + language: 'bash', + code: 'node --import ./instrument.js index.js', + }, + { + type: 'text', + text: tct( + 'This is what your application entry point, usually [code:index.js], looks like:', + {code: } + ), + }, { type: 'code', tabs: [ { label: 'JavaScript', language: 'javascript', - filename: 'index.(js|mjs)', + filename: 'index.js', code: getSdkSetupSnippet(), }, ], }, + { + type: 'text', + text: tct( + 'The default [code:fastifyIntegration] captures errors from your route handlers automatically. You do not have to add an error handler.', + {code: } + ), + }, ], }, getUploadSourceMapsStep({ @@ -121,7 +134,7 @@ export const onboarding: OnboardingConfig = { type: 'code', language: 'javascript', code: ` -app.get("/debug-sentry", function mainHandler(req, res) {${ +fastify.get("/debug-sentry", function mainHandler(req, res) {${ params.isLogsSelected ? ` // Send a log before throwing the error From edc48734a929a809c49fd5ea2b0be1d1d3698af8 Mon Sep 17 00:00:00 2001 From: Charly Gomez Date: Tue, 22 Sep 2026 17:32:19 +0200 Subject: [PATCH 14/21] feat(onboarding): Update Next.js onboarding for SDK v11 (#123911) Update the in-product Next.js onboarding for @sentry/nextjs v11. - State that the SDK requires Next.js 14 or later in the install step. - Import `withSentryConfig` from `@sentry/nextjs/config` in the profiling `next.config` snippet, and label the ESM tab `next.config.mjs`. The root export is removed in v11. The `./config` entry point already exists in 10.x, so the snippet also works for users who have not upgraded yet. - Drop the paragraph about SDK versions 7.57.x and below needing tracing enabled for distributed tracing. - Remove `browserTracingIntegration` from the distributed tracing snippet, since the Next.js SDK adds it by default. - Fix "React project" to "Next.js project" and "NextJS" to "Next.js" in the performance onboarding, and use a consistent `(js|ts)` suffix for the three config file names. Checked and unchanged: `reactComponentAnnotation`, `enableOpenTelemetrySetup` and `sendDefaultPii` do not appear in this onboarding, the agent monitoring step already targets `sentry.server.config` only, and all docs links resolve. closes [SDK-1435](https://linear.app/getsentry/issue/SDK-1435/update-the-javascript-nextjs-in-product-onboarding-for-v11) Co-authored-by: Claude Fable 5.1 --- .../javascript-nextjs/index.tsx | 56 ++++++++++--------- .../javascript-nextjs/onboarding.spec.tsx | 5 ++ .../javascript-nextjs/onboarding.tsx | 6 ++ .../javascript-nextjs/performance.tsx | 21 ++----- 4 files changed, 45 insertions(+), 43 deletions(-) diff --git a/static/app/gettingStartedDocs/javascript-nextjs/index.tsx b/static/app/gettingStartedDocs/javascript-nextjs/index.tsx index 338dca5773cf..b0ae31b1a7b2 100644 --- a/static/app/gettingStartedDocs/javascript-nextjs/index.tsx +++ b/static/app/gettingStartedDocs/javascript-nextjs/index.tsx @@ -30,7 +30,7 @@ export const docs: Docs = { { type: 'text', text: tct( - 'In Next.js you can configure document response headers via the headers option in [code:next.config.js]:', + 'In Next.js you can configure document response headers via the headers option in [code:next.config.(js|mjs|ts)]. Import [code:withSentryConfig] from [code:@sentry/nextjs/config]:', { code: , } @@ -42,38 +42,42 @@ export const docs: Docs = { { label: 'ESM', language: 'javascript', - filename: 'next.config.js', + filename: 'next.config.mjs', code: ` - export default withSentryConfig({ - async headers() { - return [{ - source: "/:path*", - headers: [{ - key: "Document-Policy", - value: "js-profiling", - }], - }]; - }, - // ... other Next.js config options - });`, +import { withSentryConfig } from "@sentry/nextjs/config"; + +export default withSentryConfig({ + async headers() { + return [{ + source: "/:path*", + headers: [{ + key: "Document-Policy", + value: "js-profiling", + }], + }]; + }, + // ... other Next.js config options +});`, }, { label: 'CJS', language: 'javascript', filename: 'next.config.js', code: ` - module.exports = withSentryConfig({ - async headers() { - return [{ - source: "/:path*", - headers: [{ - key: "Document-Policy", - value: "js-profiling", - }], - }]; - }, - // ... other Next.js config options - });`, +const { withSentryConfig } = require("@sentry/nextjs/config"); + +module.exports = withSentryConfig({ + async headers() { + return [{ + source: "/:path*", + headers: [{ + key: "Document-Policy", + value: "js-profiling", + }], + }]; + }, + // ... other Next.js config options +});`, }, ], }, diff --git a/static/app/gettingStartedDocs/javascript-nextjs/onboarding.spec.tsx b/static/app/gettingStartedDocs/javascript-nextjs/onboarding.spec.tsx index fe2447db93b0..e44b379f980d 100644 --- a/static/app/gettingStartedDocs/javascript-nextjs/onboarding.spec.tsx +++ b/static/app/gettingStartedDocs/javascript-nextjs/onboarding.spec.tsx @@ -19,6 +19,11 @@ describe('javascript-nextjs onboarding docs', () => { expect( screen.getByText(textWithMarkupMatcher(/npx @sentry\/wizard@latest -i nextjs/)) ).toBeInTheDocument(); + + // States the minimum supported Next.js version + expect( + screen.getByText(textWithMarkupMatcher(/requires Next\.js 14 or later/)) + ).toBeInTheDocument(); }); it('displays the verify instructions', () => { diff --git a/static/app/gettingStartedDocs/javascript-nextjs/onboarding.tsx b/static/app/gettingStartedDocs/javascript-nextjs/onboarding.tsx index 2fdc19140098..af8beee47e88 100644 --- a/static/app/gettingStartedDocs/javascript-nextjs/onboarding.tsx +++ b/static/app/gettingStartedDocs/javascript-nextjs/onboarding.tsx @@ -17,6 +17,12 @@ export const onboarding: OnboardingConfig = { { title: t('Automatic Configuration (Recommended)'), content: [ + { + type: 'text', + text: tct('The Sentry Next.js SDK requires Next.js [code:14] or later.', { + code: , + }), + }, { type: 'text', text: tct( diff --git a/static/app/gettingStartedDocs/javascript-nextjs/performance.tsx b/static/app/gettingStartedDocs/javascript-nextjs/performance.tsx index 0d0a95663e8a..a969d6630423 100644 --- a/static/app/gettingStartedDocs/javascript-nextjs/performance.tsx +++ b/static/app/gettingStartedDocs/javascript-nextjs/performance.tsx @@ -9,7 +9,7 @@ import {getInstallSnippet} from './utils'; export const performance: OnboardingConfig = { introduction: () => t( - "Adding Performance to your React project is simple. Make sure you've got these basics down." + "Adding Performance to your Next.js project is simple. Make sure you've got these basics down." ), install: params => [ { @@ -34,7 +34,7 @@ export const performance: OnboardingConfig = { { type: 'text', text: tct( - 'To configure, set [code:tracesSampleRate] in your config files, [code:sentry.server.config.js], [code:instrumentation-client.(js|ts)], and [code:sentry.edge.config.js]:', + 'To configure, set [code:tracesSampleRate] in your config files, [code:instrumentation-client.(js|ts)], [code:sentry.server.config.(js|ts)], and [code:sentry.edge.config.(js|ts)]:', {code: } ), }, @@ -93,27 +93,14 @@ Sentry.init({ { type: 'code', language: 'javascript', + filename: 'instrumentation-client.(js|ts)', code: ` -// instrumentation-client.(js|ts) Sentry.init({ dsn: "${params.dsn.public}", - integrations: [Sentry.browserTracingIntegration()], tracePropagationTargets: ["localhost", /^https:\\/\\/yourserver\\.io\\/api/] }); `, }, - { - type: 'text', - text: tct( - "If you're using version [code:7.57.x] or below, you'll need to have our [link:tracing feature enabled] in order for distributed tracing to work.", - { - code: , - link: ( - - ), - } - ), - }, ], }, ], @@ -124,7 +111,7 @@ Sentry.init({ { type: 'text', text: tct( - 'Verify that performance monitoring is working correctly with our [link:automatic instrumentation] by simply using your NextJS application.', + 'Verify that performance monitoring is working correctly with our [link:automatic instrumentation] by simply using your Next.js application.', { link: ( From eb18af61e23091072b66ae38de3b846fa9c2365d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jan=20Peer=20St=C3=B6cklmair?= Date: Tue, 22 Sep 2026 20:55:36 +0300 Subject: [PATCH 15/21] feat(onboarding): Update Deno onboarding for SDK v11 (#123772) closes SDK-1460 The Deno onboarding still described the v10 SDK: a single `Sentry.init()` call with no tracing, no separate instrument file, and no logs or metrics. v11 makes Deno a full server SDK, so the guide now matches the other server platforms. The configure step splits the setup into an `instrument.ts` file and a `main.ts` entry point that imports it first, which is what the OpenTelemetry tracer needs. When tracing is selected, it also documents the `--import` flag that turns on the channel-based instrumentations for libraries such as `mysql` and `postgres`. Add logs, metrics and MCP onboarding, which v11 exports on Deno. They share one install block and one `Sentry.init()` snippet from `utils.tsx`, so the snippets cannot drift apart. The install block uses `deno add` and the `npm:` specifier instead of the npm/yarn/pnpm tabs of the Node helpers. --- .../onboarding/productSelection.tsx | 5 + static/app/data/platformCategories.tsx | 3 + static/app/gettingStartedDocs/deno/index.tsx | 11 +- static/app/gettingStartedDocs/deno/logs.tsx | 74 +++++++ static/app/gettingStartedDocs/deno/mcp.tsx | 87 +++++++++ .../app/gettingStartedDocs/deno/metrics.tsx | 62 ++++++ .../deno/onboarding.spec.tsx | 76 ++++++++ .../gettingStartedDocs/deno/onboarding.tsx | 184 +++++++++++++----- static/app/gettingStartedDocs/deno/utils.tsx | 86 ++++++++ 9 files changed, 542 insertions(+), 46 deletions(-) create mode 100644 static/app/gettingStartedDocs/deno/logs.tsx create mode 100644 static/app/gettingStartedDocs/deno/mcp.tsx create mode 100644 static/app/gettingStartedDocs/deno/metrics.tsx create mode 100644 static/app/gettingStartedDocs/deno/utils.tsx diff --git a/static/app/components/onboarding/productSelection.tsx b/static/app/components/onboarding/productSelection.tsx index b8c38bd046ef..1bdfc6a6ae92 100644 --- a/static/app/components/onboarding/productSelection.tsx +++ b/static/app/components/onboarding/productSelection.tsx @@ -99,6 +99,11 @@ export const platformProductAvailability = { ProductSolution.METRICS, ], capacitor: [ProductSolution.PERFORMANCE_MONITORING, ProductSolution.LOGS], + deno: [ + ProductSolution.PERFORMANCE_MONITORING, + ProductSolution.LOGS, + ProductSolution.METRICS, + ], dotnet: [ ProductSolution.PERFORMANCE_MONITORING, ProductSolution.PROFILING, diff --git a/static/app/data/platformCategories.tsx b/static/app/data/platformCategories.tsx index 09cc91ac82b4..9056db39d68b 100644 --- a/static/app/data/platformCategories.tsx +++ b/static/app/data/platformCategories.tsx @@ -317,6 +317,7 @@ export const withLoggingOnboarding = new Set([ 'cocoa-objc', 'cocoa-swift', 'dart', + 'deno', 'dotnet', 'dotnet-aspnet', 'dotnet-aspnetcore', @@ -417,6 +418,7 @@ export const withMetricsOnboarding = new Set([ 'apple-ios', 'apple-macos', 'bun', + 'deno', 'dotnet', 'dotnet-aspnet', 'dotnet-aspnetcore', @@ -882,4 +884,5 @@ export const mcpMonitoringPlatforms: ReadonlySet = new Set([ ...platformKeys.filter(id => id.startsWith('node')), ...platformKeys.filter(id => id.startsWith('python')), 'bun', + 'deno', ]); diff --git a/static/app/gettingStartedDocs/deno/index.tsx b/static/app/gettingStartedDocs/deno/index.tsx index b60b7ce4e46a..eca496bc6e77 100644 --- a/static/app/gettingStartedDocs/deno/index.tsx +++ b/static/app/gettingStartedDocs/deno/index.tsx @@ -6,15 +6,22 @@ import { import {featureFlag} from 'sentry/gettingStartedDocs/node/featureFlag'; import {agentMonitoring} from './agentMonitoring'; +import {logs} from './logs'; +import {mcp} from './mcp'; +import {metrics} from './metrics'; import {onboarding} from './onboarding'; +import {PACKAGE_NAME, sentryImport} from './utils'; export const docs: Docs = { onboarding, replayOnboardingJsLoader, feedbackOnboardingJsLoader, featureFlagOnboarding: featureFlag({ - packageName: '@sentry/deno', - sentryImport: 'import * as Sentry from "npm:@sentry/deno";', + packageName: PACKAGE_NAME, + sentryImport, }), agentMonitoringOnboarding: agentMonitoring, + logsOnboarding: logs, + mcpOnboarding: mcp, + metricsOnboarding: metrics, }; diff --git a/static/app/gettingStartedDocs/deno/logs.tsx b/static/app/gettingStartedDocs/deno/logs.tsx new file mode 100644 index 000000000000..ede45f7c8df8 --- /dev/null +++ b/static/app/gettingStartedDocs/deno/logs.tsx @@ -0,0 +1,74 @@ +import {ExternalLink} from '@sentry/scraps/link'; + +import type {OnboardingConfig} from 'sentry/components/onboarding/gettingStartedDoc/types'; +import {StepType} from 'sentry/components/onboarding/gettingStartedDoc/types'; +import {t, tct} from 'sentry/locale'; + +import {getInstallContent, getMigrationContent, sentryImport} from './utils'; + +export const logs: OnboardingConfig = { + install: () => [ + { + type: StepType.INSTALL, + content: [ + ...getInstallContent(t('Add the Sentry Deno SDK as a dependency:')), + getMigrationContent(), + ], + }, + ], + configure: params => [ + { + type: StepType.CONFIGURE, + content: [ + { + type: 'text', + text: tct( + 'Logs are enabled by default. To also capture your [code:console] logs, add the [code:consoleLoggingIntegration] to your [code:Sentry.init()] configuration.', + {code: } + ), + }, + { + type: 'code', + language: 'typescript', + code: `${sentryImport} + +Sentry.init({ + dsn: "${params.dsn.public}", + integrations: [ + // send console.log, console.warn, and console.error calls as logs to Sentry + Sentry.consoleLoggingIntegration({ levels: ["log", "warn", "error"] }), + ], +});`, + }, + { + type: 'text', + text: tct('For more detailed information, see the [link:logs documentation].', { + link: ( + + ), + }), + }, + ], + }, + ], + verify: () => [ + { + type: StepType.VERIFY, + content: [ + { + type: 'text', + text: t( + 'Send a test log from your app, then refresh this page to verify it arrived in Sentry.' + ), + }, + { + type: 'code', + language: 'typescript', + code: `${sentryImport} + +Sentry.logger.info('User triggered test log', { action: 'test_log' })`, + }, + ], + }, + ], +}; diff --git a/static/app/gettingStartedDocs/deno/mcp.tsx b/static/app/gettingStartedDocs/deno/mcp.tsx new file mode 100644 index 000000000000..15b0bf53539f --- /dev/null +++ b/static/app/gettingStartedDocs/deno/mcp.tsx @@ -0,0 +1,87 @@ +import type {OnboardingConfig} from 'sentry/components/onboarding/gettingStartedDoc/types'; +import {StepType} from 'sentry/components/onboarding/gettingStartedDoc/types'; +import {t, tct} from 'sentry/locale'; + +import {getInstallContent, sentryImport} from './utils'; + +export const mcp: OnboardingConfig = { + install: () => [ + { + type: StepType.INSTALL, + content: getInstallContent(t('Add the Sentry Deno SDK as a dependency:')), + }, + ], + configure: params => [ + { + type: StepType.CONFIGURE, + content: [ + { + type: 'text', + text: tct('Initialize the Sentry SDK by calling [code:Sentry.init()]:', { + code: , + }), + }, + { + type: 'code', + language: 'typescript', + code: `${sentryImport} + +Sentry.init({ + dsn: "${params.dsn.public}", + // Tracing must be enabled for MCP monitoring to work + tracesSampleRate: 1.0, + dataCollection: { + // To disable sending user data and HTTP bodies, uncomment the lines below. For more info visit: + // https://docs.sentry.io/platforms/javascript/guides/deno/configuration/options/#dataCollection + // userInfo: false, + // httpBodies: [], + }, +});`, + }, + { + type: 'text', + text: tct( + 'Wrap your MCP server in a [code:Sentry.wrapMcpServerWithSentry()] call. This will automatically capture spans for all MCP server interactions.', + {code: } + ), + }, + { + type: 'code', + language: 'typescript', + code: `import { McpServer } from "npm:@modelcontextprotocol/sdk"; + +const server = Sentry.wrapMcpServerWithSentry(new McpServer({ + name: "my-mcp-server", + version: "1.0.0", +}));`, + }, + ], + }, + ], + verify: () => [ + { + type: StepType.VERIFY, + content: [ + { + type: 'text', + text: t( + 'Register a tool on the wrapped server and call it. The tool call creates a span, which you can find in Sentry.' + ), + }, + { + type: 'code', + language: 'typescript', + code: `import { z } from "npm:zod"; + +server.tool( + "roll_dice", + { sides: z.number() }, + ({ sides }) => ({ + content: [{ type: "text", text: String(1 + Math.floor(Math.random() * sides)) }], + }), +);`, + }, + ], + }, + ], +}; diff --git a/static/app/gettingStartedDocs/deno/metrics.tsx b/static/app/gettingStartedDocs/deno/metrics.tsx new file mode 100644 index 000000000000..f83cf8e5265f --- /dev/null +++ b/static/app/gettingStartedDocs/deno/metrics.tsx @@ -0,0 +1,62 @@ +import {ExternalLink} from '@sentry/scraps/link'; + +import type {OnboardingConfig} from 'sentry/components/onboarding/gettingStartedDoc/types'; +import {StepType} from 'sentry/components/onboarding/gettingStartedDoc/types'; +import {tct} from 'sentry/locale'; + +import {getInstallContent, getMigrationContent, getSdkInitSnippet} from './utils'; + +export const metrics: OnboardingConfig = { + install: () => [ + { + type: StepType.INSTALL, + content: [ + ...getInstallContent( + tct( + 'Add the Sentry SDK as a dependency. The minimum version of [packageName] that supports metrics is [code:10.25.0].', + { + code: , + packageName: @sentry/deno, + } + ) + ), + getMigrationContent(), + ], + }, + ], + configure: () => [], + verify: params => [ + { + type: StepType.VERIFY, + content: [ + { + type: 'text', + text: tct( + 'Metrics are automatically enabled after Sentry is initialized. You can emit metrics using the [code:Sentry.metrics] API.', + {code: } + ), + }, + { + type: 'code', + language: 'typescript', + code: `${getSdkInitSnippet(params)} + +Sentry.metrics.count('button_click', 1); +Sentry.metrics.gauge('page_load_time', 150); +Sentry.metrics.distribution('response_time', 200);`, + }, + { + type: 'text', + text: tct( + 'For more detailed information, see the [link:metrics documentation].', + { + link: ( + + ), + } + ), + }, + ], + }, + ], +}; diff --git a/static/app/gettingStartedDocs/deno/onboarding.spec.tsx b/static/app/gettingStartedDocs/deno/onboarding.spec.tsx index 0dd97bbbff6b..0b9bf146b54f 100644 --- a/static/app/gettingStartedDocs/deno/onboarding.spec.tsx +++ b/static/app/gettingStartedDocs/deno/onboarding.spec.tsx @@ -2,6 +2,8 @@ import {renderWithOnboardingLayout} from 'sentry-test/onboarding/renderWithOnboa import {screen} from 'sentry-test/reactTestingLibrary'; import {textWithMarkupMatcher} from 'sentry-test/utils'; +import {ProductSolution} from 'sentry/components/onboarding/gettingStartedDoc/types'; + import {docs} from '.'; describe('deno onboarding docs', () => { @@ -13,10 +15,36 @@ describe('deno onboarding docs', () => { expect(screen.getByRole('heading', {name: 'Configure SDK'})).toBeInTheDocument(); expect(screen.getByRole('heading', {name: 'Verify'})).toBeInTheDocument(); + // Renders the instrument file and the entry point that imports it + expect( + screen.getAllByText( + textWithMarkupMatcher(/import \* as Sentry from "npm:@sentry\/deno";/) + ).length + ).toBeGreaterThan(0); + expect( + screen.getByText(textWithMarkupMatcher(/import "\.\/instrument\.ts";/)) + ).toBeInTheDocument(); + // Renders config options expect( screen.getByText(textWithMarkupMatcher(/tracesSampleRate: 1\.0,/)) ).toBeInTheDocument(); + expect( + screen.getByText(textWithMarkupMatcher(/dataCollection: \{/)) + ).toBeInTheDocument(); + }); + + it('renders the auto-instrumentation step when tracing is selected', () => { + renderWithOnboardingLayout(docs, { + selectedProducts: [ProductSolution.PERFORMANCE_MONITORING], + }); + + expect( + screen.getByText( + textWithMarkupMatcher(/deno run --import=@sentry\/deno\/import main\.ts/) + ) + ).toBeInTheDocument(); + expect(screen.getByText('Tracing')).toBeInTheDocument(); }); it('renders without tracing', () => { @@ -28,5 +56,53 @@ describe('deno onboarding docs', () => { expect( screen.queryByText(textWithMarkupMatcher(/tracesSampleRate: 1\.0,/)) ).not.toBeInTheDocument(); + + // Does not render the auto-instrumentation step + expect( + screen.queryByText(textWithMarkupMatcher(/deno run --import=/)) + ).not.toBeInTheDocument(); + }); + + it('displays logging code in verify section when logs are selected', () => { + renderWithOnboardingLayout(docs, { + selectedProducts: [ProductSolution.ERROR_MONITORING, ProductSolution.LOGS], + }); + + expect( + screen.getByText( + textWithMarkupMatcher(/Sentry\.logger\.info\('User triggered test error'/) + ) + ).toBeInTheDocument(); + expect(screen.getByText('Logging Integrations')).toBeInTheDocument(); + }); + + it('displays metrics code in verify section when metrics are selected', () => { + renderWithOnboardingLayout(docs, { + selectedProducts: [ProductSolution.ERROR_MONITORING, ProductSolution.METRICS], + }); + + expect( + screen.getByText( + textWithMarkupMatcher(/Sentry\.metrics\.count\('test_counter', 1\)/) + ) + ).toBeInTheDocument(); + expect(screen.getByText('Application Metrics')).toBeInTheDocument(); + }); + + it('does not display logs or metrics code when they are not selected', () => { + renderWithOnboardingLayout(docs, { + selectedProducts: [ProductSolution.ERROR_MONITORING], + }); + + expect( + screen.queryByText( + textWithMarkupMatcher(/Sentry\.logger\.info\('User triggered test error'/) + ) + ).not.toBeInTheDocument(); + expect( + screen.queryByText( + textWithMarkupMatcher(/Sentry\.metrics\.count\('test_counter', 1\)/) + ) + ).not.toBeInTheDocument(); }); }); diff --git a/static/app/gettingStartedDocs/deno/onboarding.tsx b/static/app/gettingStartedDocs/deno/onboarding.tsx index a6e98a7826fb..da15f844a480 100644 --- a/static/app/gettingStartedDocs/deno/onboarding.tsx +++ b/static/app/gettingStartedDocs/deno/onboarding.tsx @@ -1,64 +1,130 @@ -import type {OnboardingConfig} from 'sentry/components/onboarding/gettingStartedDoc/types'; +import type { + DocsParams, + OnboardingConfig, +} from 'sentry/components/onboarding/gettingStartedDoc/types'; import {StepType} from 'sentry/components/onboarding/gettingStartedDoc/types'; -import {t} from 'sentry/locale'; +import {t, tct} from 'sentry/locale'; + +import {getInstallContent, getSdkInitSnippet, PACKAGE_NAME, sentryImport} from './utils'; + +const INSTRUMENT_FILENAME = 'instrument.ts'; +const ENTRY_POINT_FILENAME = 'main.ts'; + +const getEntryPointSnippet = () => ` +import "./${INSTRUMENT_FILENAME}"; + +// Every other import of your app goes below this line + +Deno.serve((_req) => new Response("Hello World!"));`; + +const getAutoInstrumentationSnippet = () => + `deno run --import=${PACKAGE_NAME}/import ${ENTRY_POINT_FILENAME}`; + +/** + * The log and metric calls the verify snippet makes before it throws. + * + * @param indent The whitespace put in front of every line. + */ +const getVerifySignalsSnippet = (params: DocsParams, indent: string) => + `${ + params.isLogsSelected + ? ` +${indent}// Send a log before throwing the error +${indent}Sentry.logger.info('User triggered test error', { +${indent} action: 'test_error', +${indent}});` + : '' + }${ + params.isMetricsSelected + ? ` +${indent}// Send a test metric before throwing the error +${indent}Sentry.metrics.count('test_counter', 1);` + : '' + }`; + +const getVerifySnippet = (params: DocsParams) => { + if (!params.isPerformanceSelected) { + return `${sentryImport} +${getVerifySignalsSnippet(params, '')} + +setTimeout(() => { + throw new Error(); +});`; + } + + return `${sentryImport} + +Sentry.startSpan({ + op: "test", + name: "My First Test Span", +}, () => {${getVerifySignalsSnippet(params, ' ')} + throw new Error(); +});`; +}; export const onboarding: OnboardingConfig = { + introduction: () => + t( + "In this quick guide you'll set up and configure the Sentry Deno SDK for the use in your Deno application." + ), install: () => [ { type: StepType.INSTALL, + content: getInstallContent(t('Add the Sentry Deno SDK as a dependency:')), + }, + ], + configure: params => [ + { + type: StepType.CONFIGURE, content: [ { type: 'text', - text: t( - "Sentry captures data by using an SDK within your application's runtime." + text: tct( + 'Put the [code:Sentry.init()] call in its own [code:instrument.ts] file, so that it runs before the rest of your code.', + {code: } ), }, { type: 'code', - tabs: [ - { - label: 'npm registry', - language: 'javascript', - code: 'import * as Sentry from "npm:@sentry/deno";', - }, - ], + language: 'typescript', + filename: INSTRUMENT_FILENAME, + code: getSdkInitSnippet(params), }, - ], - }, - ], - configure: params => [ - { - type: StepType.CONFIGURE, - content: [ { type: 'text', - text: t( - "Initialize Sentry as early as possible in your application's lifecycle." + text: tct( + 'Import [code:instrument.ts] at the top of your entry point, before every other import. Incoming [code:Deno.serve] and [code:node:http] requests are then instrumented for you.', + {code: } ), }, { type: 'code', - tabs: [ + language: 'typescript', + filename: ENTRY_POINT_FILENAME, + code: getEntryPointSnippet(), + }, + { + type: 'conditional', + condition: params.isPerformanceSelected, + content: [ { - language: 'javascript', - label: 'JavaScript', - code: ` -Sentry.init({ - dsn: "${params.dsn.public}",${ - params.isPerformanceSelected - ? ` - // enable performance - tracesSampleRate: 1.0,` - : '' - } -});`, + type: 'text', + text: tct( + 'Some libraries, for example [code:mysql] or [code:postgres], do not emit tracing signals on their own. To instrument them, start your app with the [code:--import] flag.', + {code: } + ), + }, + { + type: 'code', + language: 'bash', + code: getAutoInstrumentationSnippet(), }, ], }, ], }, ], - verify: () => [ + verify: params => [ { type: StepType.VERIFY, content: [ @@ -70,18 +136,48 @@ Sentry.init({ }, { type: 'code', - tabs: [ - { - label: 'JavaScript', - language: 'javascript', - code: `setTimeout(() => { - throw new Error(); - });`, - }, - ], + language: 'typescript', + code: getVerifySnippet(params), }, ], }, ], - nextSteps: () => [], + nextSteps: params => { + const steps = []; + + if (params.isPerformanceSelected) { + steps.push({ + id: 'tracing', + name: t('Tracing'), + description: t( + 'Learn which libraries the SDK instruments for you, and how to add your own spans.' + ), + link: 'https://docs.sentry.io/platforms/javascript/guides/deno/tracing/', + }); + } + + if (params.isLogsSelected) { + steps.push({ + id: 'logs', + name: t('Logging Integrations'), + description: t( + 'Add logging integrations to automatically capture logs from your application.' + ), + link: 'https://docs.sentry.io/platforms/javascript/guides/deno/logs/#integrations', + }); + } + + if (params.isMetricsSelected) { + steps.push({ + id: 'metrics', + name: t('Application Metrics'), + description: t( + 'Learn how to track custom metrics to monitor your application performance and business KPIs.' + ), + link: 'https://docs.sentry.io/platforms/javascript/guides/deno/metrics/', + }); + } + + return steps; + }, }; diff --git a/static/app/gettingStartedDocs/deno/utils.tsx b/static/app/gettingStartedDocs/deno/utils.tsx new file mode 100644 index 000000000000..27aa08255d4d --- /dev/null +++ b/static/app/gettingStartedDocs/deno/utils.tsx @@ -0,0 +1,86 @@ +import {ExternalLink} from '@sentry/scraps/link'; + +import type { + ContentBlock, + DocsParams, +} from 'sentry/components/onboarding/gettingStartedDoc/types'; +import {tct} from 'sentry/locale'; + +export const PACKAGE_NAME = '@sentry/deno'; + +export const MIN_DENO_VERSION = '2.8.3'; + +export const sentryImport = `import * as Sentry from "npm:${PACKAGE_NAME}";`; + +/** + * The install step content, shared by every onboarding on this platform. + * + * @param text The sentence above the code block, naming the minimum SDK version. + */ +export function getInstallContent(text: React.ReactNode): ContentBlock[] { + return [ + {type: 'text', text}, + { + type: 'code', + tabs: [ + { + label: 'deno add', + language: 'bash', + code: `deno add npm:${PACKAGE_NAME}`, + }, + { + label: 'npm specifier', + language: 'javascript', + code: sentryImport, + }, + ], + }, + { + type: 'text', + text: tct('The SDK needs Deno [minVersion] or newer.', { + minVersion: {MIN_DENO_VERSION}, + }), + }, + ]; +} + +export function getMigrationContent(): ContentBlock { + return { + type: 'text', + text: tct( + 'If you are on an older version of the SDK, follow our [link:migration guide] to upgrade.', + { + link: ( + + ), + } + ), + }; +} + +/** + * The `Sentry.init()` call, shared by every snippet on this platform so that + * they cannot drift apart. + */ +export function getSdkInitSnippet(params: DocsParams) { + return `${sentryImport} + +Sentry.init({ + dsn: "${params.dsn.public}",${ + params.isPerformanceSelected + ? ` + // Set tracesSampleRate to 1.0 to capture 100% of spans for tracing. + // Learn more at + // https://docs.sentry.io/platforms/javascript/configuration/options/#traces-sample-rate + tracesSampleRate: 1.0,` + : '' + } + + dataCollection: { + // To disable sending user data and HTTP bodies, uncomment the lines below. For more info visit: + // https://docs.sentry.io/platforms/javascript/guides/deno/configuration/options/#dataCollection + // userInfo: false, + // httpBodies: [], + }, +});`; +} From b9ce9a7472f95dc53072de236e5d12c41822bedc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jan=20Peer=20St=C3=B6cklmair?= Date: Wed, 23 Sep 2026 08:26:19 +0300 Subject: [PATCH 16/21] fix(onboarding): Update Ember onboarding for SDK v11 (#123896) closes SDK-1433 Add an explicit performance instance initializer when tracing is selected, as the v2 addon no longer registers instrumentation automatically. Keep loadInitializers in the application setup so the initializer runs. Replace timer-based verification with a clickable Ember component that sends selected logs and metrics before throwing a test error. Add regression coverage for setup, tracing selection, and verification. --------- Co-authored-by: Cursor Co-authored-by: Claude Co-authored-by: Andrei <168741329+andreiborza@users.noreply.github.com> Co-authored-by: Charly Gomez Co-authored-by: Andrei Borza Co-authored-by: Codex --- .../javascript-ember/onboarding.spec.tsx | 77 +++++++++++++++-- .../javascript-ember/onboarding.tsx | 85 ++++++++++++++++--- .../javascript-ember/utils.tsx | 4 +- .../explore/conversations/onboarding.spec.tsx | 48 ++++++----- 4 files changed, 176 insertions(+), 38 deletions(-) diff --git a/static/app/gettingStartedDocs/javascript-ember/onboarding.spec.tsx b/static/app/gettingStartedDocs/javascript-ember/onboarding.spec.tsx index 74a7036de76e..9f91faee9fed 100644 --- a/static/app/gettingStartedDocs/javascript-ember/onboarding.spec.tsx +++ b/static/app/gettingStartedDocs/javascript-ember/onboarding.spec.tsx @@ -18,12 +18,79 @@ describe('javascript-ember onboarding docs', () => { ).toBeInTheDocument(); expect(screen.getByRole('heading', {name: 'Verify'})).toBeInTheDocument(); - // Includes import statement in multiple places expect( - screen.getAllByText( - textWithMarkupMatcher(/import \* as Sentry from "@sentry\/ember"/) - ) - ).toHaveLength(2); // Appears in configure and verify steps + screen.getByText(textWithMarkupMatcher(/import \* as Sentry from "@sentry\/ember"/)) + ).toBeInTheDocument(); + }); + + it('initializes the SDK directly', () => { + renderWithOnboardingLayout(docs); + + const setup = screen.getByText(textWithMarkupMatcher(/Sentry\.init\(/)); + expect(setup).toHaveTextContent('import config from "./config/environment"'); + expect(setup).toHaveTextContent('loadInitializers(App, config.modulePrefix)'); + expect(setup).toHaveTextContent('dataCollection:'); + expect(setup).not.toHaveTextContent(/sendDefaultPii|enableLogs|enableMetrics/); + }); + + it('registers a performance instance initializer when tracing is selected', () => { + renderWithOnboardingLayout(docs, { + selectedProducts: [ProductSolution.PERFORMANCE_MONITORING], + }); + + const initializer = screen.getByText( + textWithMarkupMatcher(/export function initialize\(appInstance\)/) + ); + expect(initializer).toHaveTextContent( + 'import { instrumentAppInstancePerformance } from "@sentry/ember"' + ); + expect(initializer).toHaveTextContent( + 'instrumentAppInstancePerformance(appInstance)' + ); + expect(initializer).toHaveTextContent('export default { initialize }'); + }); + + it('omits performance instrumentation when tracing is not selected', () => { + renderWithOnboardingLayout(docs, { + selectedProducts: [ProductSolution.ERROR_MONITORING], + }); + + expect( + screen.queryByText(textWithMarkupMatcher(/instrumentAppInstancePerformance/)) + ).not.toBeInTheDocument(); + expect( + screen.queryByText(textWithMarkupMatcher(/tracesSampleRate/)) + ).not.toBeInTheDocument(); + }); + + it('verifies errors with a component action and button', () => { + renderWithOnboardingLayout(docs, { + selectedProducts: [ProductSolution.ERROR_MONITORING], + }); + + const component = screen.getByText(textWithMarkupMatcher(/throw new Error/)); + expect(component).toHaveTextContent('extends Component'); + expect(component).toHaveTextContent(/@action\s*triggerError\(\)/); + expect(component).not.toHaveTextContent(/setTimeout|@sentry\/ember/); + expect( + screen.getByText(textWithMarkupMatcher(/\{\{on "click" this\.triggerError\}\}/)) + ).toHaveTextContent('Break the world'); + }); + + it('sends both selected signals before the verification error', () => { + renderWithOnboardingLayout(docs, { + selectedProducts: [ + ProductSolution.ERROR_MONITORING, + ProductSolution.LOGS, + ProductSolution.METRICS, + ], + }); + + const component = screen.getByText(textWithMarkupMatcher(/throw new Error/)); + expect(component).toHaveTextContent('import * as Sentry from "@sentry/ember"'); + expect(component).toHaveTextContent( + /Sentry\.logger\.info.*Sentry\.metrics\.count.*throw new Error/ + ); }); it('displays sample rates by default', () => { diff --git a/static/app/gettingStartedDocs/javascript-ember/onboarding.tsx b/static/app/gettingStartedDocs/javascript-ember/onboarding.tsx index 27a29df8fe94..9b4ac9f2619e 100644 --- a/static/app/gettingStartedDocs/javascript-ember/onboarding.tsx +++ b/static/app/gettingStartedDocs/javascript-ember/onboarding.tsx @@ -1,4 +1,5 @@ import type { + ContentBlock, DocsParams, OnboardingConfig, } from 'sentry/components/onboarding/gettingStartedDoc/types'; @@ -10,30 +11,37 @@ import {getSdkSetupSnippet, installSnippetBlock} from './utils'; const getVerifyEmberSnippet = (params: DocsParams) => { const logsCode = params.isLogsSelected - ? `// Send a log before throwing the error - Sentry.logger.info(Sentry.logger.fmt\`User \${"sentry-test"} triggered test error button\`, { - action: "test_error_button_click", + ? ` // Send a log before throwing the error + Sentry.logger.info('User triggered test error', { + action: 'test_error_button_click', }); ` : ''; const metricsCode = params.isMetricsSelected - ? `// Send a test metric before throwing the error + ? ` // Send a test metric before throwing the error Sentry.metrics.count('test_counter', 1); ` : ''; - return ` -import * as Sentry from "@sentry/ember"; - -setTimeout(() => { - ${logsCode}${metricsCode}throw new Error("Sentry Test Error"); -});`; + return `import Component from "@glimmer/component"; +import { action } from "@ember/object"; +${ + params.isLogsSelected || params.isMetricsSelected + ? 'import * as Sentry from "@sentry/ember";\n' + : '' +} +export default class SentryTestComponent extends Component { + @action + triggerError() { +${logsCode}${metricsCode} throw new Error("Sentry Test Error"); + } +}`; }; export const onboarding: OnboardingConfig = { introduction: () => - tct("In this quick guide you'll use [strong:npm] or [strong:yarn] to set up:", { + tct("In this quick guide you'll use the [strong:Ember CLI] to set up:", { strong: , }), install: () => [ @@ -69,10 +77,39 @@ export const onboarding: OnboardingConfig = { { label: 'JavaScript', language: 'javascript', + filename: 'app/app.js', code: getSdkSetupSnippet(params), }, ], }, + ...((params.isPerformanceSelected + ? [ + { + type: 'text', + text: tct( + 'To enable tracing, create [code:app/instance-initializers/sentry-performance.js]. The v2 addon does not register performance instrumentation automatically:', + {code: } + ), + }, + { + type: 'code', + tabs: [ + { + label: 'JavaScript', + language: 'javascript', + filename: 'app/instance-initializers/sentry-performance.js', + code: `import { instrumentAppInstancePerformance } from "@sentry/ember"; + +export function initialize(appInstance) { + instrumentAppInstancePerformance(appInstance); +} + +export default { initialize };`, + }, + ], + }, + ] + : []) satisfies ContentBlock[]), ], }, getUploadSourceMapsStep({ @@ -86,8 +123,9 @@ export const onboarding: OnboardingConfig = { content: [ { type: 'text', - text: t( - "This snippet contains an intentional error and can be used as a test to make sure that everything's working as expected." + text: tct( + 'Create a [code:SentryTest] component with the following class and template:', + {code: } ), }, { @@ -96,10 +134,31 @@ export const onboarding: OnboardingConfig = { { label: 'JavaScript', language: 'javascript', + filename: 'app/components/sentry-test.js', code: getVerifyEmberSnippet(params), }, ], }, + { + type: 'code', + tabs: [ + { + label: 'Handlebars', + language: 'html', + filename: 'app/components/sentry-test.hbs', + code: ``, + }, + ], + }, + { + type: 'text', + text: tct( + 'Render [code:] in an application template, then click "Break the world" to send a test error to Sentry. If you selected Logs or Metrics, clicking the button sends those too.', + {code: } + ), + }, ], }, ], diff --git a/static/app/gettingStartedDocs/javascript-ember/utils.tsx b/static/app/gettingStartedDocs/javascript-ember/utils.tsx index 1d7f6c3cc2dd..e5978eda4d8b 100644 --- a/static/app/gettingStartedDocs/javascript-ember/utils.tsx +++ b/static/app/gettingStartedDocs/javascript-ember/utils.tsx @@ -43,7 +43,7 @@ const getDynamicParts = (params: DocsParams): string[] => { if (params.isPerformanceSelected) { dynamicParts.push(` // Tracing - tracesSampleRate: 1.0, // Capture 100% of the transactions + tracesSampleRate: 1.0, // Capture 100% of the traces // Set 'tracePropagationTargets' to control for which URLs distributed tracing should be enabled tracePropagationTargets: ["localhost", /^https:\\/\\/yourserver\\.io\\/api/]`); } @@ -91,6 +91,8 @@ export default class App extends Application { podModulePrefix = config.podModulePrefix; Resolver = Resolver; } + +loadInitializers(App, config.modulePrefix); `; } diff --git a/static/app/views/explore/conversations/onboarding.spec.tsx b/static/app/views/explore/conversations/onboarding.spec.tsx index a9d08880bbe9..1718d212d9fb 100644 --- a/static/app/views/explore/conversations/onboarding.spec.tsx +++ b/static/app/views/explore/conversations/onboarding.spec.tsx @@ -132,24 +132,27 @@ describe('ConversationOnboarding', () => { ); }); - it('uses the same agent setup for unsupported platforms', async () => { - const {organization, project} = setupProject('other'); - const prompt = getAgentSetupPrompt({ - organizationSlug: organization.slug, - project, - dsn: ProjectKeysFixture()[0].dsn.public, - }); + it.each(['other', 'javascript'] as const)( + 'uses the same agent setup for unsupported platform %s', + async platform => { + const {organization, project} = setupProject(platform); + const prompt = getAgentSetupPrompt({ + organizationSlug: organization.slug, + project, + dsn: ProjectKeysFixture()[0].dsn.public, + }); - render(, {organization}); + render(, {organization}); - await userEvent.click(await screen.findByRole('button', {name: 'Copy prompt'})); - expect(navigator.clipboard.writeText).toHaveBeenCalledWith(prompt); - expect(screen.getByText(prompt, {collapseWhitespace: false})).toBeInTheDocument(); - expect(screen.getByRole('tab', {name: 'For you'})).toHaveAttribute( - 'aria-disabled', - 'true' - ); - }); + await userEvent.click(await screen.findByRole('button', {name: 'Copy prompt'})); + expect(navigator.clipboard.writeText).toHaveBeenCalledWith(prompt); + expect(screen.getByText(prompt, {collapseWhitespace: false})).toBeInTheDocument(); + expect(screen.getByRole('tab', {name: 'For you'})).toHaveAttribute( + 'aria-disabled', + 'true' + ); + } + ); it.each([ {platform: 'node', linkName: 'documentation'}, @@ -190,8 +193,12 @@ describe('ConversationOnboarding', () => { ).toBeGreaterThan(0); }); - it('shows the unsupported platform setup for a browser project', async () => { - const {organization} = setupProject('javascript'); + it('shows manual instrumentation guidance for a browser project without a DSN', async () => { + const {organization, project} = setupProject('javascript'); + MockApiClient.addMockResponse({ + url: `/projects/${organization.slug}/${project.slug}/keys/`, + body: [], + }); render(, { organization, @@ -204,7 +211,10 @@ describe('ConversationOnboarding', () => { }); expect( - await screen.findByText( + await screen.findByRole('tab', {name: 'For you', selected: true}) + ).not.toHaveAttribute('aria-disabled', 'true'); + expect( + screen.getByText( textWithMarkupMatcher( /Auto instrumentation isn't available for Browser JavaScript,/ ) From 40c49337bb0c691428b60666167617092587f15f Mon Sep 17 00:00:00 2001 From: Andrei <168741329+andreiborza@users.noreply.github.com> Date: Wed, 23 Sep 2026 07:52:26 +0200 Subject: [PATCH 17/21] ref(onboarding): Remove Connect onboarding | keep existing Connect projects (#123834) ## What Removes the connect onboarding from the list in "New Projects" (default's to Node in case someone still comes to connect). Based on top of the Cloudflare stack, as they introduce the `hidden` flag, which is used here Screenshot 2026-09-11 at 08 52 16 Screenshot 2026-09-11 at 08 52 09 ## Why v11 removed the Connect instrumentation, so `connectIntegration` and `setupConnectErrorHandler` no longer exist and the guide would break on v11. --------- Co-authored-by: JPeer264 Co-authored-by: Claude Opus 5 Co-authored-by: Codex --- static/app/data/platformPickerCategories.tsx | 1 - static/app/data/platforms.tsx | 2 + .../node-connect/crashReport.tsx | 29 --- .../gettingStartedDocs/node-connect/index.tsx | 25 +-- .../gettingStartedDocs/node-connect/logs.tsx | 6 - .../gettingStartedDocs/node-connect/mcp.tsx | 3 - .../node-connect/metrics.tsx | 6 - .../node-connect/onboarding.spec.tsx | 200 ------------------ .../node-connect/onboarding.tsx | 173 --------------- .../node-connect/profiling.tsx | 3 - static/app/types/project.tsx | 4 +- 11 files changed, 8 insertions(+), 444 deletions(-) delete mode 100644 static/app/gettingStartedDocs/node-connect/crashReport.tsx delete mode 100644 static/app/gettingStartedDocs/node-connect/logs.tsx delete mode 100644 static/app/gettingStartedDocs/node-connect/mcp.tsx delete mode 100644 static/app/gettingStartedDocs/node-connect/metrics.tsx delete mode 100644 static/app/gettingStartedDocs/node-connect/onboarding.spec.tsx delete mode 100644 static/app/gettingStartedDocs/node-connect/onboarding.tsx delete mode 100644 static/app/gettingStartedDocs/node-connect/profiling.tsx diff --git a/static/app/data/platformPickerCategories.tsx b/static/app/data/platformPickerCategories.tsx index 04bb5468b27e..77cc2e676ff6 100644 --- a/static/app/data/platformPickerCategories.tsx +++ b/static/app/data/platformPickerCategories.tsx @@ -81,7 +81,6 @@ const server = new Set([ 'native', 'node', 'node-cloudflare-workers', - 'node-connect', 'node-express', 'node-fastify', 'node-hapi', diff --git a/static/app/data/platforms.tsx b/static/app/data/platforms.tsx index 9ce684f5da75..68d46becc1a6 100644 --- a/static/app/data/platforms.tsx +++ b/static/app/data/platforms.tsx @@ -465,6 +465,8 @@ export const platforms: PlatformIntegration[] = [ type: 'framework', language: 'node', link: 'https://docs.sentry.io/platforms/javascript/guides/connect/', + // Version 11 of the SDK removed the Connect instrumentation. + hidden: true, }, { id: 'node-express', diff --git a/static/app/gettingStartedDocs/node-connect/crashReport.tsx b/static/app/gettingStartedDocs/node-connect/crashReport.tsx deleted file mode 100644 index fd846465bf9d..000000000000 --- a/static/app/gettingStartedDocs/node-connect/crashReport.tsx +++ /dev/null @@ -1,29 +0,0 @@ -import { - StepType, - type OnboardingConfig, -} from 'sentry/components/onboarding/gettingStartedDoc/types'; -import { - getCrashReportJavaScriptInstallSteps, - getCrashReportModalConfigDescription, - getCrashReportModalIntroduction, -} from 'sentry/components/onboarding/gettingStartedDoc/utils/feedbackOnboarding'; - -export const crashReport: OnboardingConfig = { - introduction: () => getCrashReportModalIntroduction(), - install: params => getCrashReportJavaScriptInstallSteps(params), - configure: () => [ - { - type: StepType.CONFIGURE, - content: [ - { - type: 'text', - text: getCrashReportModalConfigDescription({ - link: 'https://docs.sentry.io/platforms/javascript/guides/connect/user-feedback/configuration/#crash-report-modal', - }), - }, - ], - }, - ], - verify: () => [], - nextSteps: () => [], -}; diff --git a/static/app/gettingStartedDocs/node-connect/index.tsx b/static/app/gettingStartedDocs/node-connect/index.tsx index eea878568681..ebcae29fb84a 100644 --- a/static/app/gettingStartedDocs/node-connect/index.tsx +++ b/static/app/gettingStartedDocs/node-connect/index.tsx @@ -1,21 +1,4 @@ -import type {Docs} from 'sentry/components/onboarding/gettingStartedDoc/types'; -import {agentMonitoring} from 'sentry/gettingStartedDocs/node/agentMonitoring'; -import {featureFlag} from 'sentry/gettingStartedDocs/node/featureFlag'; - -import {crashReport} from './crashReport'; -import {logs} from './logs'; -import {mcp} from './mcp'; -import {metrics} from './metrics'; -import {onboarding} from './onboarding'; -import {profiling} from './profiling'; - -export const docs: Docs = { - onboarding, - crashReportOnboarding: crashReport, - profilingOnboarding: profiling, - featureFlagOnboarding: featureFlag(), - logsOnboarding: logs, - metricsOnboarding: metrics, - agentMonitoringOnboarding: agentMonitoring(), - mcpOnboarding: mcp, -}; +// `node-connect` is a legacy platform key: version 11 of the SDK removed the Connect +// instrumentation, so Connect is no longer offered when creating a project. Projects +// created before that keep this key, so they resolve to the plain Node docs. +export {docs} from 'sentry/gettingStartedDocs/node'; diff --git a/static/app/gettingStartedDocs/node-connect/logs.tsx b/static/app/gettingStartedDocs/node-connect/logs.tsx deleted file mode 100644 index d689bd26c7d6..000000000000 --- a/static/app/gettingStartedDocs/node-connect/logs.tsx +++ /dev/null @@ -1,6 +0,0 @@ -import {getNodeLogsOnboarding} from 'sentry/gettingStartedDocs/node/utils'; - -export const logs = getNodeLogsOnboarding({ - docsPlatform: 'connect', - packageName: '@sentry/node', -}); diff --git a/static/app/gettingStartedDocs/node-connect/mcp.tsx b/static/app/gettingStartedDocs/node-connect/mcp.tsx deleted file mode 100644 index de9927939869..000000000000 --- a/static/app/gettingStartedDocs/node-connect/mcp.tsx +++ /dev/null @@ -1,3 +0,0 @@ -import {getNodeMcpOnboarding} from 'sentry/gettingStartedDocs/node/utils'; - -export const mcp = getNodeMcpOnboarding(); diff --git a/static/app/gettingStartedDocs/node-connect/metrics.tsx b/static/app/gettingStartedDocs/node-connect/metrics.tsx deleted file mode 100644 index 38f3943cdd85..000000000000 --- a/static/app/gettingStartedDocs/node-connect/metrics.tsx +++ /dev/null @@ -1,6 +0,0 @@ -import {getNodeMetricsOnboarding} from 'sentry/gettingStartedDocs/node/metrics'; - -export const metrics = getNodeMetricsOnboarding({ - docsPlatform: 'connect', - packageName: '@sentry/node', -}); diff --git a/static/app/gettingStartedDocs/node-connect/onboarding.spec.tsx b/static/app/gettingStartedDocs/node-connect/onboarding.spec.tsx deleted file mode 100644 index f43b12f086b3..000000000000 --- a/static/app/gettingStartedDocs/node-connect/onboarding.spec.tsx +++ /dev/null @@ -1,200 +0,0 @@ -import {OrganizationFixture} from 'sentry-fixture/organization'; - -import {renderWithOnboardingLayout} from 'sentry-test/onboarding/renderWithOnboardingLayout'; -import {screen} from 'sentry-test/reactTestingLibrary'; -import {textWithMarkupMatcher} from 'sentry-test/utils'; - -import {ProductSolution} from 'sentry/components/onboarding/gettingStartedDoc/types'; - -import {docs} from '.'; - -describe('connect onboarding docs', () => { - it('renders onboarding docs correctly', () => { - renderWithOnboardingLayout(docs); - - // Renders main headings - expect(screen.getByRole('heading', {name: 'Install'})).toBeInTheDocument(); - expect(screen.getByRole('heading', {name: 'Configure SDK'})).toBeInTheDocument(); - expect( - screen.getByRole('heading', {name: /Upload Source Maps/i}) - ).toBeInTheDocument(); - - // Includes import statement - const allMatches = screen.getAllByText( - textWithMarkupMatcher(/import \* as Sentry from "@sentry\/node"/) - ); - allMatches.forEach(match => { - expect(match).toBeInTheDocument(); - }); - }); - - it('includes error handler', () => { - renderWithOnboardingLayout(docs); - - expect( - screen.getByText(textWithMarkupMatcher(/Sentry\.setupConnectErrorHandler\(app\)/)) - ).toBeInTheDocument(); - }); - - it('displays sample rates by default', () => { - renderWithOnboardingLayout(docs, { - selectedProducts: [ - ProductSolution.ERROR_MONITORING, - ProductSolution.PERFORMANCE_MONITORING, - ProductSolution.PROFILING, - ], - }); - - expect( - screen.getByText(textWithMarkupMatcher(/tracesSampleRate/)) - ).toBeInTheDocument(); - expect( - screen.getByText(textWithMarkupMatcher(/profilesSampleRate: 1\.0/)) - ).toBeInTheDocument(); - }); - - it('displays logs integration next step when logs are selected', () => { - renderWithOnboardingLayout(docs, { - selectedProducts: [ProductSolution.ERROR_MONITORING, ProductSolution.LOGS], - }); - - expect(screen.getByText('Logging Integrations')).toBeInTheDocument(); - }); - - it('does not display logs integration next step when logs are not selected', () => { - renderWithOnboardingLayout(docs, { - selectedProducts: [ProductSolution.ERROR_MONITORING], - }); - - expect(screen.queryByText('Logging Integrations')).not.toBeInTheDocument(); - }); - - it('displays logging code in verify section when logs are selected', () => { - renderWithOnboardingLayout(docs, { - selectedProducts: [ProductSolution.ERROR_MONITORING, ProductSolution.LOGS], - }); - - expect( - screen.getByText( - textWithMarkupMatcher(/Sentry\.logger\.info\('User triggered test error'/) - ) - ).toBeInTheDocument(); - }); - - it('does not display logging code in verify section when logs are not selected', () => { - renderWithOnboardingLayout(docs, { - selectedProducts: [ProductSolution.ERROR_MONITORING], - }); - - expect( - screen.queryByText( - textWithMarkupMatcher(/Sentry\.logger\.info\('User triggered test error'/) - ) - ).not.toBeInTheDocument(); - }); - - it('enables performance setting the tracesSampleRate to 1', () => { - renderWithOnboardingLayout(docs, { - selectedProducts: [ - ProductSolution.ERROR_MONITORING, - ProductSolution.PERFORMANCE_MONITORING, - ], - }); - - expect( - screen.getByText(textWithMarkupMatcher(/tracesSampleRate: 1\.0/)) - ).toBeInTheDocument(); - }); - - it('enables profiling by setting profiling samplerates', () => { - renderWithOnboardingLayout(docs, { - selectedProducts: [ProductSolution.ERROR_MONITORING, ProductSolution.PROFILING], - }); - - expect( - screen.getByText( - textWithMarkupMatcher( - /const { nodeProfilingIntegration } = require\("@sentry\/profiling-node"\)/ - ) - ) - ).toBeInTheDocument(); - - expect( - screen.getByText(textWithMarkupMatcher(/profilesSampleRate: 1\.0/)) - ).toBeInTheDocument(); - }); - - it('continuous profiling', () => { - const organization = OrganizationFixture({ - features: ['continuous-profiling'], - }); - - renderWithOnboardingLayout( - docs, - {}, - { - organization, - } - ); - - expect( - screen.getByText( - textWithMarkupMatcher( - /const { nodeProfilingIntegration } = require\("@sentry\/profiling-node"\)/ - ) - ) - ).toBeInTheDocument(); - - expect( - screen.getByText(textWithMarkupMatcher(/profileLifecycle: 'trace'/)) - ).toBeInTheDocument(); - expect( - screen.getByText(textWithMarkupMatcher(/profileSessionSampleRate: 1\.0/)) - ).toBeInTheDocument(); - - // Profiles sample rate should not be set for continuous profiling - expect( - screen.queryByText(textWithMarkupMatcher(/profilesSampleRate: 1\.0/)) - ).not.toBeInTheDocument(); - }); - - it('displays metrics code in verify section when metrics are selected', () => { - renderWithOnboardingLayout(docs, { - selectedProducts: [ProductSolution.ERROR_MONITORING, ProductSolution.METRICS], - }); - - expect( - screen.getByText( - textWithMarkupMatcher(/Sentry\.metrics\.count\('test_counter', 1\)/) - ) - ).toBeInTheDocument(); - }); - - it('does not display metrics code in verify section when metrics are not selected', () => { - renderWithOnboardingLayout(docs, { - selectedProducts: [ProductSolution.ERROR_MONITORING], - }); - - expect( - screen.queryByText( - textWithMarkupMatcher(/Sentry\.metrics\.count\('test_counter', 1\)/) - ) - ).not.toBeInTheDocument(); - }); - - it('displays Metrics in next steps when metrics are selected', () => { - renderWithOnboardingLayout(docs, { - selectedProducts: [ProductSolution.ERROR_MONITORING, ProductSolution.METRICS], - }); - - expect(screen.getByText('Application Metrics')).toBeInTheDocument(); - }); - - it('does not display Metrics in next steps when metrics are not selected', () => { - renderWithOnboardingLayout(docs, { - selectedProducts: [ProductSolution.ERROR_MONITORING], - }); - - expect(screen.queryByText('Application Metrics')).not.toBeInTheDocument(); - }); -}); diff --git a/static/app/gettingStartedDocs/node-connect/onboarding.tsx b/static/app/gettingStartedDocs/node-connect/onboarding.tsx deleted file mode 100644 index f9e087dffd82..000000000000 --- a/static/app/gettingStartedDocs/node-connect/onboarding.tsx +++ /dev/null @@ -1,173 +0,0 @@ -import {ExternalLink} from '@sentry/scraps/link'; - -import type { - DocsParams, - OnboardingConfig, -} from 'sentry/components/onboarding/gettingStartedDoc/types'; -import {StepType} from 'sentry/components/onboarding/gettingStartedDoc/types'; -import {getUploadSourceMapsStep} from 'sentry/components/onboarding/gettingStartedDoc/utils'; -import { - getImportInstrumentSnippet, - getInstallCodeBlock, - getSdkInitSnippet, - getSentryImportSnippet, -} from 'sentry/gettingStartedDocs/node/utils'; -import {t, tct} from 'sentry/locale'; - -const getSdkSetupSnippet = () => ` -${getImportInstrumentSnippet()} - -// All other imports below -${getSentryImportSnippet('@sentry/node')} -const connect = require("connect"); - -const app = connect(); - -Sentry.setupConnectErrorHandler(app); - -// All your controllers should live here - -app.listen(3000); -`; - -export const onboarding: OnboardingConfig = { - introduction: () => - tct("In this quick guide you'll use [strong:npm] or [strong:yarn] to set up:", { - strong: , - }), - install: params => [ - { - type: StepType.INSTALL, - content: [ - { - type: 'text', - text: t('Add the Sentry Node SDK as a dependency:'), - }, - getInstallCodeBlock(params), - ], - }, - ], - configure: params => [ - { - type: StepType.CONFIGURE, - content: [ - { - type: 'text', - text: t( - "Initialize Sentry as early as possible in your application's lifecycle." - ), - }, - { - type: 'text', - text: tct( - 'To initialize the SDK before everything else, create an external file called [code:instrument.js/mjs].', - {code: } - ), - }, - { - type: 'code', - tabs: [ - { - label: 'JavaScript', - language: 'javascript', - filename: 'instrument.(js|mjs)', - code: getSdkInitSnippet(params, 'node'), - }, - ], - }, - { - type: 'text', - text: tct( - "Make sure to import [code:instrument.js/mjs] at the top of your file. Set up the error handler after all controllers and before any other error middleware. This setup is typically done in your application's entry point file, which is usually [code:index.(js|ts)]. If you're running your application in ESM mode, or looking for alternative ways to set up Sentry, read about [docs:installation methods in our docs].", - { - code: , - docs: ( - - ), - } - ), - }, - { - type: 'code', - tabs: [ - { - label: 'JavaScript', - language: 'javascript', - filename: 'index.(js|mjs)', - code: getSdkSetupSnippet(), - }, - ], - }, - ], - }, - getUploadSourceMapsStep({ - guideLink: 'https://docs.sentry.io/platforms/javascript/guides/connect/sourcemaps/', - ...params, - }), - ], - verify: (params: DocsParams) => [ - { - type: StepType.VERIFY, - content: [ - { - type: 'text', - text: t( - "This snippet contains an intentional error and can be used as a test to make sure that everything's working as expected." - ), - }, - { - type: 'code', - language: 'javascript', - code: getVerifySnippet(params), - }, - ], - }, - ], - nextSteps: (params: DocsParams) => { - const steps = []; - - if (params.isLogsSelected) { - steps.push({ - id: 'logs', - name: t('Logging Integrations'), - description: t( - 'Add logging integrations to automatically capture logs from your application.' - ), - link: 'https://docs.sentry.io/platforms/javascript/guides/connect/logs/#integrations', - }); - } - - if (params.isMetricsSelected) { - steps.push({ - id: 'metrics', - name: t('Application Metrics'), - description: t( - 'Learn how to track custom metrics to monitor your application performance and business KPIs.' - ), - link: 'https://docs.sentry.io/platforms/javascript/guides/connect/metrics/', - }); - } - - return steps; - }, -}; - -const getVerifySnippet = (params: DocsParams) => ` -app.use(async function () {${ - params.isLogsSelected - ? ` - // Send a log before throwing the error - Sentry.logger.info('User triggered test error', { - action: 'test_error_middleware', - });` - : '' -}${ - params.isMetricsSelected - ? ` - // Send a test metric before throwing the error - Sentry.metrics.count('test_counter', 1);` - : '' -} - throw new Error("My first Sentry error!"); -}); -`; diff --git a/static/app/gettingStartedDocs/node-connect/profiling.tsx b/static/app/gettingStartedDocs/node-connect/profiling.tsx deleted file mode 100644 index 8999b8c332fe..000000000000 --- a/static/app/gettingStartedDocs/node-connect/profiling.tsx +++ /dev/null @@ -1,3 +0,0 @@ -import {getNodeProfilingOnboarding} from 'sentry/gettingStartedDocs/node/utils'; - -export const profiling = getNodeProfilingOnboarding(); diff --git a/static/app/types/project.tsx b/static/app/types/project.tsx index 017b1710cebb..44ffe55a957d 100644 --- a/static/app/types/project.tsx +++ b/static/app/types/project.tsx @@ -226,8 +226,8 @@ export type PlatformIntegration = { deprecated?: boolean; /** * True for a platform that is still valid for existing projects but is no - * longer offered when creating one, because it was merged into another - * platform. Its docs stay reachable, unlike `deprecated`. + * longer offered when creating one. Its docs stay reachable, unlike + * `deprecated`. */ hidden?: boolean; iconConfig?: { From ee6aaab3604ded06183ed4d0c322f3510fc0957f Mon Sep 17 00:00:00 2001 From: Andrei Borza Date: Wed, 23 Sep 2026 13:05:37 +0200 Subject: [PATCH 18/21] Use instrument.server.ts for SolidStart agent monitoring --- static/app/gettingStartedDocs/javascript-solidstart/index.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/static/app/gettingStartedDocs/javascript-solidstart/index.tsx b/static/app/gettingStartedDocs/javascript-solidstart/index.tsx index b0447b4ca47b..503253a547e3 100644 --- a/static/app/gettingStartedDocs/javascript-solidstart/index.tsx +++ b/static/app/gettingStartedDocs/javascript-solidstart/index.tsx @@ -25,7 +25,7 @@ export const docs: Docs = { }), agentMonitoringOnboarding: agentMonitoring({ packageName: '@sentry/solidstart', - configFileName: 'instrument.server.mjs', + configFileName: 'instrument.server.ts', }), logsOnboarding: logsFullStack({ docsPlatform: 'solidstart', From 23df131e3724a12eaadbfb06d4694fb59b4146db Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jan=20Peer=20St=C3=B6cklmair?= Date: Wed, 23 Sep 2026 14:49:12 +0300 Subject: [PATCH 19/21] fix(onboarding): Show Vite setup in Cloudflare logs onboarding (#125298) The Logs page passes no setup type, so the logs onboarding always showed the manual withSentry snippet, which conflicts with the default Vite plugin setup. Show the Vite, Manual and Pages snippets as tabs when no setup type is set, and only the matching snippet when one is. Co-authored-by: Claude Opus 5.5 --- .../node-cloudflare-workers/logs.spec.tsx | 70 +++++++++++++++++++ .../node-cloudflare-workers/logs.tsx | 54 +++++++++++--- 2 files changed, 116 insertions(+), 8 deletions(-) create mode 100644 static/app/gettingStartedDocs/node-cloudflare-workers/logs.spec.tsx diff --git a/static/app/gettingStartedDocs/node-cloudflare-workers/logs.spec.tsx b/static/app/gettingStartedDocs/node-cloudflare-workers/logs.spec.tsx new file mode 100644 index 000000000000..beaa87b7de6f --- /dev/null +++ b/static/app/gettingStartedDocs/node-cloudflare-workers/logs.spec.tsx @@ -0,0 +1,70 @@ +import {OrganizationFixture} from 'sentry-fixture/organization'; +import {ProjectFixture} from 'sentry-fixture/project'; +import {ProjectKeysFixture} from 'sentry-fixture/projectKeys'; + +import {render, screen, userEvent} from 'sentry-test/reactTestingLibrary'; + +import type {Organization} from 'sentry/types/organization'; +import type {Project} from 'sentry/types/project'; +import {LogsTabOnboarding} from 'sentry/views/explore/logs/logsOnboarding'; + +function renderMockRequests({ + organization, + project, +}: { + organization: Organization; + project: Project; +}) { + MockApiClient.addMockResponse({ + url: `/projects/${organization.slug}/${project.slug}/keys/`, + method: 'GET', + body: [ProjectKeysFixture()[0]], + }); + MockApiClient.addMockResponse({ + url: `/projects/${organization.slug}/${project.slug}/`, + method: 'GET', + body: project, + }); + MockApiClient.addMockResponse({ + url: `/customers/${organization.slug}/`, + method: 'GET', + body: {}, + }); + MockApiClient.addMockResponse({ + url: `/organizations/${organization.slug}/sdks/`, + method: 'GET', + body: [], + }); + MockApiClient.addMockResponse({ + url: `/organizations/${organization.slug}/stats_v2/`, + method: 'GET', + body: {}, + }); +} + +describe('cloudflare logs onboarding', () => { + it('shows a tab for each setup type on the Logs page', async () => { + const organization = OrganizationFixture(); + const project = ProjectFixture({platform: 'node-cloudflare-workers'}); + renderMockRequests({organization, project}); + + render( + + ); + + expect(await screen.findByRole('heading', {name: /install/i})).toBeInTheDocument(); + await userEvent.click(screen.getByRole('button', {name: 'Next'})); + + expect(await screen.findByRole('button', {name: 'Vite Plugin'})).toBeInTheDocument(); + expect(screen.getByRole('button', {name: 'Manual'})).toBeInTheDocument(); + expect(screen.getByRole('button', {name: 'Pages'})).toBeInTheDocument(); + expect(screen.getByText(/Sentry\.defineCloudflareOptions/)).toBeInTheDocument(); + + await userEvent.click(screen.getByRole('button', {name: 'Manual'})); + expect(await screen.findByText(/Sentry\.withSentry\(/)).toBeInTheDocument(); + }); +}); diff --git a/static/app/gettingStartedDocs/node-cloudflare-workers/logs.tsx b/static/app/gettingStartedDocs/node-cloudflare-workers/logs.tsx index 1b5ae996c6b2..5539cb5f5392 100644 --- a/static/app/gettingStartedDocs/node-cloudflare-workers/logs.tsx +++ b/static/app/gettingStartedDocs/node-cloudflare-workers/logs.tsx @@ -18,6 +18,17 @@ export const onRequest = [ // Add more middlewares here ];`; +const getViteConfigureSnippet = (dsn: string, packageName: string) => + `import * as Sentry from "${packageName}"; + +export default Sentry.defineCloudflareOptions((env) => ({ + dsn: "${dsn}", + integrations: [ + // send console.log, console.warn, and console.error calls as logs to Sentry + Sentry.consoleLoggingIntegration({ levels: ["log", "warn", "error"] }), + ], +}));`; + const getWorkersConfigureSnippet = (dsn: string, packageName: string) => `import * as Sentry from "${packageName}"; @@ -36,15 +47,42 @@ export default Sentry.withSentry( } satisfies ExportedHandler, );`; +const getConfigureTabs = (dsn: string, packageName: string) => ({ + [CloudflareSetupType.VITE]: { + label: 'Vite Plugin', + language: 'typescript', + filename: 'src/instrument.server.ts', + code: getViteConfigureSnippet(dsn, packageName), + }, + [CloudflareSetupType.MANUAL]: { + label: 'Manual', + language: 'typescript', + filename: 'src/index.ts', + code: getWorkersConfigureSnippet(dsn, packageName), + }, + [CloudflareSetupType.PAGES]: { + label: 'Pages', + language: 'javascript', + filename: 'functions/_middleware.js', + code: getPagesConfigureSnippet(dsn, packageName), + }, +}); + export const logs = getNodeLogsOnboarding({ docsPlatform: 'cloudflare', packageName: '@sentry/cloudflare', - generateConfigureSnippet: (params, packageName) => ({ - type: 'code', - language: 'javascript', - code: - params.platformOptions.setupType === CloudflareSetupType.PAGES - ? getPagesConfigureSnippet(params.dsn.public, packageName) - : getWorkersConfigureSnippet(params.dsn.public, packageName), - }), + generateConfigureSnippet: (params, packageName) => { + const tabs = getConfigureTabs(params.dsn.public, packageName); + const setupType = Object.values(CloudflareSetupType).find( + type => type === params.platformOptions?.setupType + ); + + // The Logs page passes no setup type, so it shows every setup as a tab. + if (!setupType) { + return {type: 'code', tabs: Object.values(tabs)}; + } + + const {label: _label, ...snippet} = tabs[setupType]; + return {type: 'code', ...snippet}; + }, }); From 65daf41ea1697f25de1d3f0a32d173033299f99d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jan=20Peer=20St=C3=B6cklmair?= Date: Wed, 23 Sep 2026 15:03:12 +0300 Subject: [PATCH 20/21] fix(onboarding): Show where Cloudflare Pages users put the verify snippet (#125300) The merged Cloudflare onboarding dropped the functions/customerror.js file name and the /customerror path from the Pages verify step, so users could not tell how to trigger the test error. Add both back to the text, and show the file name in the snippet header. The content block type allows a filename on a single code block, but the renderer only passed it to tabbed snippets, so single snippets never showed their file name. Pass it through for single snippets as well. Co-authored-by: Claude Opus 5.5 --- .../contentBlocks/defaultRenderers.tsx | 2 +- .../onboarding.spec.tsx | 14 ++++++ .../node-cloudflare-workers/onboarding.tsx | 44 ++++++++++++------- 3 files changed, 44 insertions(+), 16 deletions(-) diff --git a/static/app/components/onboarding/gettingStartedDoc/contentBlocks/defaultRenderers.tsx b/static/app/components/onboarding/gettingStartedDoc/contentBlocks/defaultRenderers.tsx index 6669335e58a5..ba39a214537a 100644 --- a/static/app/components/onboarding/gettingStartedDoc/contentBlocks/defaultRenderers.tsx +++ b/static/app/components/onboarding/gettingStartedDoc/contentBlocks/defaultRenderers.tsx @@ -60,7 +60,7 @@ function CodeBlock(block: Extract) { if ('code' in block) { return (
- + {block.code}
diff --git a/static/app/gettingStartedDocs/node-cloudflare-workers/onboarding.spec.tsx b/static/app/gettingStartedDocs/node-cloudflare-workers/onboarding.spec.tsx index 86bfd4a976c6..59bf4fea9ab9 100644 --- a/static/app/gettingStartedDocs/node-cloudflare-workers/onboarding.spec.tsx +++ b/static/app/gettingStartedDocs/node-cloudflare-workers/onboarding.spec.tsx @@ -75,6 +75,20 @@ describe('cloudflare onboarding docs', () => { expect(screen.getByRole('link', {name: 'migrate to Workers'})).toBeInTheDocument(); }); + it('tells Pages users where to put and how to trigger the verify snippet', () => { + renderWithOnboardingLayout(docs, { + selectedOptions: {setupType: CloudflareSetupType.PAGES}, + }); + + expect( + screen.getByText( + textWithMarkupMatcher( + /Add it to a functions\/customerror\.js file, then access the \/customerror path/ + ) + ) + ).toBeInTheDocument(); + }); + it('displays sample rates by default', () => { renderWithOnboardingLayout(docs, { selectedProducts: [ diff --git a/static/app/gettingStartedDocs/node-cloudflare-workers/onboarding.tsx b/static/app/gettingStartedDocs/node-cloudflare-workers/onboarding.tsx index eaedc7def10a..38925114fd73 100644 --- a/static/app/gettingStartedDocs/node-cloudflare-workers/onboarding.tsx +++ b/static/app/gettingStartedDocs/node-cloudflare-workers/onboarding.tsx @@ -274,21 +274,35 @@ export const onboarding: OnboardingConfig = { verify: params => [ { type: StepType.VERIFY, - content: [ - { - type: 'text', - text: t( - "This snippet contains an intentional error and can be used as a test to make sure that everything's working as expected." - ), - }, - { - type: 'code', - language: 'javascript', - code: isPagesSetup(params) - ? getPagesVerifySnippet(params) - : getVerifySnippet(params), - }, - ], + content: isPagesSetup(params) + ? [ + { + type: 'text', + text: tct( + "This snippet contains an intentional error and can be used as a test to make sure that everything's working as expected. Add it to a [code:functions/customerror.js] file, then access the [code:/customerror] path on your deployment to trigger it.", + {code: } + ), + }, + { + type: 'code', + language: 'javascript', + filename: 'functions/customerror.js', + code: getPagesVerifySnippet(params), + }, + ] + : [ + { + type: 'text', + text: t( + "This snippet contains an intentional error and can be used as a test to make sure that everything's working as expected." + ), + }, + { + type: 'code', + language: 'javascript', + code: getVerifySnippet(params), + }, + ], }, ], nextSteps: params => { From c7a9ce201b35155a583e80e4f874d64f9f53d7b6 Mon Sep 17 00:00:00 2001 From: Sigrid <32902192+s1gr1d@users.noreply.github.com> Date: Wed, 23 Sep 2026 14:20:37 +0200 Subject: [PATCH 21/21] ref(onboarding/agents): Make dataCollection an explicit step for JS platforms (#123733) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Init snippets carried a commented-out `dataCollection` block. Commented code is easy to skim past, so the spec asks for a real step instead (getsentry/sentry-javascript#23694). This drops those blocks across JS platforms and adds a "Control the Data You Send to Sentry (Optional)" step, linking each guide's `#dataCollection` options. Agent monitoring gets a variant that leads with genAI inputs and outputs. It cannot be collapsible there, since those flows filter collapsible steps out. Eve gets none, since it never calls `Sentry.init`. The AI snippets also drop `dataCollection: {}`, unneeded in v11. Closes https://github.com/getsentry/sentry-javascript/issues/23698 ## AI Agent Setup image image --- In Onboarding image --------- Co-authored-by: Jan Peer Stöcklmair Co-authored-by: Claude Opus 5 Co-authored-by: Andrei <168741329+andreiborza@users.noreply.github.com> --- static/app/components/clippedBox.tsx | 1 + .../gettingStartedDoc/utils/index.spec.tsx | 75 +++++ .../gettingStartedDoc/utils/index.tsx | 141 ++++++++++ static/app/gettingStartedDocs/bun/mcp.tsx | 20 +- .../bun/onboarding.spec.tsx | 4 +- .../app/gettingStartedDocs/bun/onboarding.tsx | 5 + static/app/gettingStartedDocs/bun/utils.tsx | 7 - .../capacitor/onboarding.tsx | 9 +- .../gettingStartedDocs/cordova/onboarding.tsx | 5 + .../deno/agentMonitoring.tsx | 260 +++++++++--------- static/app/gettingStartedDocs/deno/mcp.tsx | 20 +- .../deno/onboarding.spec.tsx | 4 +- .../gettingStartedDocs/deno/onboarding.tsx | 5 + static/app/gettingStartedDocs/deno/utils.tsx | 7 - .../electron/onboarding.tsx | 9 +- .../javascript-angular/onboarding.tsx | 9 +- .../javascript-angular/utils.tsx | 10 +- .../javascript-astro/onboarding.spec.tsx | 1 - .../javascript-astro/onboarding.tsx | 21 +- .../javascript-ember/onboarding.spec.tsx | 6 +- .../javascript-ember/onboarding.tsx | 9 +- .../javascript-ember/utils.tsx | 10 +- .../javascript-gatsby/onboarding.spec.tsx | 11 +- .../javascript-gatsby/onboarding.tsx | 9 +- .../javascript-gatsby/utils.tsx | 10 +- .../javascript-nextjs/onboarding.tsx | 9 +- .../javascript-nuxt/onboarding.tsx | 5 + .../javascript-react-router/onboarding.tsx | 5 + .../javascript-react-router/utils.tsx | 8 +- .../javascript-react/onboarding.tsx | 5 + .../javascript-react/utils.tsx | 10 +- .../javascript-remix/onboarding.tsx | 5 + .../javascript-solid/onboarding.tsx | 9 +- .../javascript-solid/utils.tsx | 10 +- .../javascript-solidstart/onboarding.tsx | 15 +- .../javascript-solidstart/utils.tsx | 10 +- .../javascript-svelte/onboarding.tsx | 9 +- .../javascript-svelte/utils.tsx | 10 +- .../javascript-sveltekit/onboarding.tsx | 5 + .../onboarding.tsx | 53 ++-- .../javascript-vue/onboarding.spec.tsx | 6 +- .../javascript-vue/onboarding.tsx | 9 +- .../javascript-vue/utils.tsx | 6 - .../javascript/performance.tsx | 12 - .../gettingStartedDocs/javascript/utils.tsx | 19 +- .../node-awslambda/onboarding.tsx | 9 +- .../node-azurefunctions/onboarding.tsx | 9 +- .../node-cloudflare-workers/onboarding.tsx | 18 +- .../node-express/onboarding.tsx | 9 +- .../node-fastify/onboarding.tsx | 9 +- .../node-gcpfunctions/onboarding.tsx | 9 +- .../node-hapi/onboarding.tsx | 9 +- .../node-hono/onboarding.tsx | 39 +-- .../node-koa/onboarding.tsx | 9 +- .../node-nestjs/onboarding.tsx | 9 +- .../node/agentMonitoring.spec.tsx | 34 +++ .../node/agentMonitoring.tsx | 120 +++++--- .../app/gettingStartedDocs/node/mcp.spec.tsx | 45 +++ .../gettingStartedDocs/node/onboarding.tsx | 5 + static/app/gettingStartedDocs/node/utils.tsx | 42 +-- .../agents/llmOnboardingInstructions.spec.tsx | 36 +++ .../agents/llmOnboardingInstructions.tsx | 14 +- 62 files changed, 894 insertions(+), 419 deletions(-) create mode 100644 static/app/components/onboarding/gettingStartedDoc/utils/index.spec.tsx create mode 100644 static/app/gettingStartedDocs/node/mcp.spec.tsx diff --git a/static/app/components/clippedBox.tsx b/static/app/components/clippedBox.tsx index b040416fc2cb..e257bd381903 100644 --- a/static/app/components/clippedBox.tsx +++ b/static/app/components/clippedBox.tsx @@ -370,5 +370,6 @@ const ClipFade = styled('div')` const CollapseButton = styled('div')` text-align: center; + margin-top: ${p => p.theme.space.lg}; margin-bottom: ${p => p.theme.space.lg}; `; diff --git a/static/app/components/onboarding/gettingStartedDoc/utils/index.spec.tsx b/static/app/components/onboarding/gettingStartedDoc/utils/index.spec.tsx new file mode 100644 index 000000000000..7446ec4e213d --- /dev/null +++ b/static/app/components/onboarding/gettingStartedDoc/utils/index.spec.tsx @@ -0,0 +1,75 @@ +import { + getJsDataCollectionDocsLink, + isJavaScriptPlatform, +} from 'sentry/components/onboarding/gettingStartedDoc/utils'; + +describe('isJavaScriptPlatform', () => { + it.each([ + 'javascript', + 'javascript-nextjs', + 'node', + 'node-express', + 'bun', + 'deno', + 'electron', + 'capacitor', + 'ionic', + ])('accepts %s', platform => { + expect(isJavaScriptPlatform(platform)).toBe(true); + }); + + it.each([ + 'python', + 'python-fastapi', + 'php-laravel', + 'other', + 'go', + // React Native is JavaScript-based, but its docs do not cover `dataCollection`. + 'react-native', + undefined, + ])('rejects %s', platform => { + expect(isJavaScriptPlatform(platform)).toBe(false); + }); +}); + +describe('getJsDataCollectionDocsLink', () => { + const BASE = 'https://docs.sentry.io/platforms/javascript'; + + it.each([ + ['javascript-nextjs', `${BASE}/guides/nextjs/configuration/options/#dataCollection`], + ['node', `${BASE}/guides/node/configuration/options/#dataCollection`], + ['node-express', `${BASE}/guides/express/configuration/options/#dataCollection`], + ['bun', `${BASE}/guides/bun/configuration/options/#dataCollection`], + // Keys whose guide slug differs from the platform key + ['node-awslambda', `${BASE}/guides/aws-lambda/configuration/options/#dataCollection`], + [ + 'node-azurefunctions', + `${BASE}/guides/azure-functions/configuration/options/#dataCollection`, + ], + [ + 'node-gcpfunctions', + `${BASE}/guides/gcp-functions/configuration/options/#dataCollection`, + ], + [ + 'node-cloudflare-workers', + `${BASE}/guides/cloudflare/configuration/options/#dataCollection`, + ], + // Legacy key of the merged Cloudflare platform + [ + 'node-cloudflare-pages', + `${BASE}/guides/cloudflare/configuration/options/#dataCollection`, + ], + ['ionic', `${BASE}/guides/capacitor/configuration/options/#dataCollection`], + ])('maps %s to its guide', (platform, expected) => { + expect(getJsDataCollectionDocsLink(platform)).toBe(expected); + }); + + it.each(['javascript', 'other', undefined])( + 'falls back to the canonical page for %s', + platform => { + expect(getJsDataCollectionDocsLink(platform)).toBe( + `${BASE}/configuration/options/#dataCollection` + ); + } + ); +}); diff --git a/static/app/components/onboarding/gettingStartedDoc/utils/index.tsx b/static/app/components/onboarding/gettingStartedDoc/utils/index.tsx index 301b89b01d9f..d93132a0f772 100644 --- a/static/app/components/onboarding/gettingStartedDoc/utils/index.tsx +++ b/static/app/components/onboarding/gettingStartedDoc/utils/index.tsx @@ -3,6 +3,7 @@ import {Fragment} from 'react'; import {Button} from '@sentry/scraps/button'; import {ExternalLink} from '@sentry/scraps/link'; +import type {ContentBlock} from 'sentry/components/onboarding/gettingStartedDoc/contentBlocks/types'; import { docsFlowVariantParams, resolveDocsFlowEvent, @@ -84,6 +85,146 @@ export function getUploadSourceMapsStep({ }; } +// Platform key families using the JavaScript SDKs. React Native is excluded +// because its docs do not cover `dataCollection`. +const JS_PLATFORM_PREFIXES = [ + 'javascript', + 'node', + 'bun', + 'deno', + 'electron', + 'capacitor', + 'cordova', + 'ionic', +]; + +/** + * Whether the platform uses a JavaScript SDK that exposes `dataCollection`. + * A positive list, so a future non-JavaScript platform is excluded by default. + */ +export function isJavaScriptPlatform(platformKey: string | null | undefined): boolean { + return ( + !!platformKey && + JS_PLATFORM_PREFIXES.some( + prefix => platformKey === prefix || platformKey.startsWith(`${prefix}-`) + ) + ); +} + +// Platform keys whose docs guide slug is not simply the key minus its family prefix. +const DOCS_GUIDE_SLUG_OVERRIDES: Record = { + 'node-awslambda': 'aws-lambda', + 'node-azurefunctions': 'azure-functions', + 'node-gcpfunctions': 'gcp-functions', + // Workers and Pages share the `cloudflare` guide. + 'node-cloudflare-workers': 'cloudflare', + 'node-cloudflare-pages': 'cloudflare', + ionic: 'capacitor', +}; + +/** + * The `dataCollection` docs for the platform's own guide, for configs shared + * across JavaScript platforms. Unknown platforms get the canonical page. + */ +export function getJsDataCollectionDocsLink( + platformKey: string | null | undefined +): string { + if ( + !platformKey || + !isJavaScriptPlatform(platformKey) || + platformKey === 'javascript' + ) { + return 'https://docs.sentry.io/platforms/javascript/configuration/options/#dataCollection'; + } + const slug = + DOCS_GUIDE_SLUG_OVERRIDES[platformKey] ?? + platformKey.replace(/^(javascript|node)-/, ''); + return `https://docs.sentry.io/platforms/javascript/guides/${slug}/configuration/options/#dataCollection`; +} + +/** + * Shown without an init wrapper because agent and MCP monitoring span several + * init shapes (`Sentry.init`, `Sentry.withSentry`, `instrumentAgentWithSentry`). + */ +export const GEN_AI_DATA_COLLECTION_SNIPPET = `dataCollection: { + genAI: { inputs: false, outputs: false }, +},`; + +const DEFAULT_DATA_COLLECTION_SNIPPET = `Sentry.init({ + // ... + dataCollection: { + userInfo: false, + // other options + }, +});`; + +/** + * Presents `dataCollection` as its own setup step instead of a commented-out + * override in the init snippet, as the SDK data collection spec requires. Pass + * `collapsible: false` for `GuidedSteps` surfaces, which drop collapsible steps. + */ +export function getDataCollectionStep({ + docsLink, + code, + description, + collapsible = true, +}: { + docsLink: string; + code?: string; + collapsible?: boolean; + description?: React.ReactNode; +}): OnboardingStep { + const summary: ContentBlock[] = description + ? [{type: 'text', text: description}] + : [ + { + type: 'text', + text: t( + 'By default, the SDK sends user identity data (IP address, ID, and similar) and other data like HTTP bodies and URL query parameters. This gives you rich debugging context.' + ), + }, + { + type: 'text', + text: tct( + 'The SDK always filters sensitive values whose keys match a built-in denylist, such as [authCode:auth] or [passwordCode:password], and sends [filtered] instead.', + { + authCode: , + passwordCode: , + filtered: [Filtered], + } + ), + }, + ]; + + return { + collapsible, + title: t('Control the Data You Send to Sentry (Optional)'), + content: [ + ...summary, + { + type: 'text', + text: tct( + "To send less data, turn off the categories you don't need in the [code:dataCollection] option. For the full list of categories and their defaults, see [link:the dataCollection options].", + { + code: , + link: , + } + ), + }, + { + type: 'code', + tabs: [ + { + label: 'JavaScript', + language: 'javascript', + code: code ?? DEFAULT_DATA_COLLECTION_SNIPPET, + }, + ], + }, + ], + }; +} + const SENTRY_INSTRUMENT_SKILL_URL = 'https://skills.sentry.dev/instrument'; function CopyPromptButton({prompt}: {prompt: string}) { diff --git a/static/app/gettingStartedDocs/bun/mcp.tsx b/static/app/gettingStartedDocs/bun/mcp.tsx index efa4efefc08a..78624da8990d 100644 --- a/static/app/gettingStartedDocs/bun/mcp.tsx +++ b/static/app/gettingStartedDocs/bun/mcp.tsx @@ -1,5 +1,9 @@ import type {OnboardingConfig} from 'sentry/components/onboarding/gettingStartedDoc/types'; import {StepType} from 'sentry/components/onboarding/gettingStartedDoc/types'; +import { + GEN_AI_DATA_COLLECTION_SNIPPET, + getDataCollectionStep, +} from 'sentry/components/onboarding/gettingStartedDoc/utils'; import {t, tct} from 'sentry/locale'; import {getInstallContent, sentryImport} from './utils'; @@ -35,12 +39,6 @@ Sentry.init({ dsn: "${params.dsn.public}", // Tracing must be enabled for MCP monitoring to work tracesSampleRate: 1.0, - dataCollection: { - // To disable sending user data and HTTP bodies, uncomment the lines below. For more info visit: - // https://docs.sentry.io/platforms/javascript/guides/bun/configuration/options/#dataCollection - // userInfo: false, - // httpBodies: [], - }, });`, }, { @@ -62,6 +60,16 @@ const server = Sentry.wrapMcpServerWithSentry(new McpServer({ }, ], }, + // Not collapsible: the MCP onboarding's GuidedSteps drops collapsible steps. + getDataCollectionStep({ + collapsible: false, + docsLink: + 'https://docs.sentry.io/platforms/javascript/guides/bun/configuration/options/#dataCollection', + description: t( + 'By default, the SDK sends the inputs and outputs of your MCP tool calls, prompt retrievals, and resource reads. This gives you rich debugging context.' + ), + code: GEN_AI_DATA_COLLECTION_SNIPPET, + }), ], verify: () => [ { diff --git a/static/app/gettingStartedDocs/bun/onboarding.spec.tsx b/static/app/gettingStartedDocs/bun/onboarding.spec.tsx index 953e3e24a749..64e1e03d01cd 100644 --- a/static/app/gettingStartedDocs/bun/onboarding.spec.tsx +++ b/static/app/gettingStartedDocs/bun/onboarding.spec.tsx @@ -37,8 +37,10 @@ describe('bun onboarding docs', () => { expect( screen.getByText(textWithMarkupMatcher(/tracesSampleRate: 1\.0,/)) ).toBeInTheDocument(); + + // `dataCollection` is presented as its own step rather than in the snippet. expect( - screen.getByText(textWithMarkupMatcher(/dataCollection: \{/)) + screen.getByText('Control the Data You Send to Sentry (Optional)') ).toBeInTheDocument(); }); diff --git a/static/app/gettingStartedDocs/bun/onboarding.tsx b/static/app/gettingStartedDocs/bun/onboarding.tsx index ca3eb437cb52..475dc7c4f8b7 100644 --- a/static/app/gettingStartedDocs/bun/onboarding.tsx +++ b/static/app/gettingStartedDocs/bun/onboarding.tsx @@ -3,6 +3,7 @@ import type { OnboardingConfig, } from 'sentry/components/onboarding/gettingStartedDoc/types'; import {StepType} from 'sentry/components/onboarding/gettingStartedDoc/types'; +import {getDataCollectionStep} from 'sentry/components/onboarding/gettingStartedDoc/utils'; import {t, tct} from 'sentry/locale'; import {getInstallContent, getSdkInitSnippet, PACKAGE_NAME, sentryImport} from './utils'; @@ -153,6 +154,10 @@ export const onboarding: OnboardingConfig = { }, ], }, + getDataCollectionStep({ + docsLink: + 'https://docs.sentry.io/platforms/javascript/guides/bun/configuration/options/#dataCollection', + }), ], verify: params => [ { diff --git a/static/app/gettingStartedDocs/bun/utils.tsx b/static/app/gettingStartedDocs/bun/utils.tsx index 5b2a6a859168..6ae39e3d6fe4 100644 --- a/static/app/gettingStartedDocs/bun/utils.tsx +++ b/static/app/gettingStartedDocs/bun/utils.tsx @@ -57,12 +57,5 @@ Sentry.init({ tracesSampleRate: 1.0,` : '' } - - dataCollection: { - // To disable sending user data and HTTP bodies, uncomment the lines below. For more info visit: - // https://docs.sentry.io/platforms/javascript/guides/bun/configuration/options/#dataCollection - // userInfo: false, - // httpBodies: [], - }, });`; } diff --git a/static/app/gettingStartedDocs/capacitor/onboarding.tsx b/static/app/gettingStartedDocs/capacitor/onboarding.tsx index 95b388dcdb20..92aebabe0296 100644 --- a/static/app/gettingStartedDocs/capacitor/onboarding.tsx +++ b/static/app/gettingStartedDocs/capacitor/onboarding.tsx @@ -1,6 +1,9 @@ import type {OnboardingConfig} from 'sentry/components/onboarding/gettingStartedDoc/types'; import {StepType} from 'sentry/components/onboarding/gettingStartedDoc/types'; -import {getUploadSourceMapsStep} from 'sentry/components/onboarding/gettingStartedDoc/utils'; +import { + getDataCollectionStep, + getUploadSourceMapsStep, +} from 'sentry/components/onboarding/gettingStartedDoc/utils'; import {t, tct} from 'sentry/locale'; import { @@ -70,6 +73,10 @@ export const onboarding: OnboardingConfig = { 'https://docs.sentry.io/platforms/javascript/guides/capacitor/sourcemaps/', ...params, }), + getDataCollectionStep({ + docsLink: + 'https://docs.sentry.io/platforms/javascript/guides/capacitor/configuration/options/#dataCollection', + }), ], verify: _ => [ { diff --git a/static/app/gettingStartedDocs/cordova/onboarding.tsx b/static/app/gettingStartedDocs/cordova/onboarding.tsx index 9d82a1b07092..9d5183d8b085 100644 --- a/static/app/gettingStartedDocs/cordova/onboarding.tsx +++ b/static/app/gettingStartedDocs/cordova/onboarding.tsx @@ -5,6 +5,7 @@ import type { OnboardingConfig, } from 'sentry/components/onboarding/gettingStartedDoc/types'; import {StepType} from 'sentry/components/onboarding/gettingStartedDoc/types'; +import {getDataCollectionStep} from 'sentry/components/onboarding/gettingStartedDoc/utils'; import {t, tct} from 'sentry/locale'; const getConfigureSnippet = (params: DocsParams) => ` @@ -53,6 +54,10 @@ export const onboarding: OnboardingConfig = { }, ], }, + getDataCollectionStep({ + docsLink: + 'https://docs.sentry.io/platforms/javascript/guides/cordova/configuration/options/#dataCollection', + }), ], verify: () => [ { diff --git a/static/app/gettingStartedDocs/deno/agentMonitoring.tsx b/static/app/gettingStartedDocs/deno/agentMonitoring.tsx index 56085926b509..66ef02f842d3 100644 --- a/static/app/gettingStartedDocs/deno/agentMonitoring.tsx +++ b/static/app/gettingStartedDocs/deno/agentMonitoring.tsx @@ -1,8 +1,13 @@ import {ExternalLink} from '@sentry/scraps/link'; -import type {OnboardingConfig} from 'sentry/components/onboarding/gettingStartedDoc/types'; +import type { + DocsParams, + OnboardingConfig, + OnboardingStep, +} from 'sentry/components/onboarding/gettingStartedDoc/types'; import {StepType} from 'sentry/components/onboarding/gettingStartedDoc/types'; import { + getAgentDataCollectionStep, getAgentIntegration, getManualConfigureStep, } from 'sentry/gettingStartedDocs/node/agentMonitoring'; @@ -16,6 +21,131 @@ const MIN_VERSION = '10.61.0'; const sentryImport = `import * as Sentry from "npm:${PACKAGE_NAME}";`; +// The data collection step is appended once around these branches, so none can +// miss or repeat it. +function configureSteps(params: DocsParams): OnboardingStep[] { + const selected = getAgentIntegration(params); + + if (selected === AgentIntegration.MANUAL) { + return getManualConfigureStep(params, { + sentryImport, + docUrl: + 'https://docs.sentry.io/platforms/javascript/guides/deno/ai-agent-monitoring/#manual-instrumentation', + }); + } + + return [ + { + title: t('Configure'), + content: [ + { + type: 'text', + text: tct( + 'Import and initialize the Sentry SDK. The [integration] integration is enabled by default:', + { + integration: 'Vercel AI SDK', + } + ), + }, + { + type: 'code', + tabs: [ + { + label: 'JavaScript', + language: 'javascript', + code: `${sentryImport} + +Sentry.init({ +dsn: "${params.dsn.public}", +// Tracing must be enabled for agent monitoring to work +tracesSampleRate: 1.0, +});`, + }, + ], + }, + { + type: 'text', + text: tct( + 'When using [code:generateText], [code:generateObject], or [code:streamText], pass the [code:experimental_telemetry] object to correctly capture spans. For the [code:ToolLoopAgent] class, telemetry is configured via the constructor. For more details, see the [telemetryLink:AI SDK Telemetry Metadata docs] and the [agentLink:ToolLoopAgent docs].', + { + code: , + telemetryLink: ( + + ), + agentLink: ( + + ), + } + ), + }, + { + type: 'code', + tabs: [ + { + label: 'generateText', + language: 'javascript', + code: `import { generateText } from "npm:ai"; +import { openai } from "npm:@ai-sdk/openai"; + +const result = await generateText({ +model: openai("gpt-4o"), +prompt: "Tell me a joke", +experimental_telemetry: { + isEnabled: true, + recordInputs: true, + recordOutputs: true, +}, +});`, + }, + { + label: 'ToolLoopAgent', + language: 'javascript', + code: `import { ToolLoopAgent, tool } from "npm:ai"; +import { z } from "npm:zod"; + +const agent = new ToolLoopAgent({ +model: "openai/gpt-5.4", +tools: { + weather: tool({ + description: "Get the weather in a location", + inputSchema: z.object({ + location: z.string().describe("The location to get the weather for"), + }), + execute: async ({ location }) => ({ + location, + temperature: 72 + Math.floor(Math.random() * 21) - 10, + }), + }), +}, +telemetry: { + isEnabled: true, + functionId: "weather_agent", + recordInputs: true, + recordOutputs: true, +}, +}); + +const result = await agent.generate({ +prompt: "What is the weather in San Francisco?", +});`, + }, + ], + }, + { + type: 'custom', + content: ( + + } + /> + ), + }, + ], + }, + ]; +} + export const agentMonitoring: OnboardingConfig = { introduction: params => ( { - const selected = getAgentIntegration(params); - - if (selected === AgentIntegration.MANUAL) { - return getManualConfigureStep(params, { - sentryImport, - docUrl: - 'https://docs.sentry.io/platforms/javascript/guides/deno/ai-agent-monitoring/#manual-instrumentation', - }); - } - - return [ - { - title: t('Configure'), - content: [ - { - type: 'text', - text: tct( - 'Import and initialize the Sentry SDK. The [integration] integration is enabled by default:', - { - integration: 'Vercel AI SDK', - } - ), - }, - { - type: 'code', - tabs: [ - { - label: 'JavaScript', - language: 'javascript', - code: `${sentryImport} - -Sentry.init({ - dsn: "${params.dsn.public}", - // Tracing must be enabled for agent monitoring to work - tracesSampleRate: 1.0, - dataCollection: { - // Control data collection of LLMs and tools. - // For more info visit: https://docs.sentry.io/platforms/javascript/data-management/data-collected/ - // genAI: { inputs: false, outputs: false }, - }, -});`, - }, - ], - }, - { - type: 'text', - text: tct( - 'When using [code:generateText], [code:generateObject], or [code:streamText], pass the [code:experimental_telemetry] object to correctly capture spans. For the [code:ToolLoopAgent] class, telemetry is configured via the constructor. For more details, see the [telemetryLink:AI SDK Telemetry Metadata docs] and the [agentLink:ToolLoopAgent docs].', - { - code: , - telemetryLink: ( - - ), - agentLink: ( - - ), - } - ), - }, - { - type: 'code', - tabs: [ - { - label: 'generateText', - language: 'javascript', - code: `import { generateText } from "npm:ai"; -import { openai } from "npm:@ai-sdk/openai"; - -const result = await generateText({ - model: openai("gpt-4o"), - prompt: "Tell me a joke", - experimental_telemetry: { - isEnabled: true, - recordInputs: true, - recordOutputs: true, - }, -});`, - }, - { - label: 'ToolLoopAgent', - language: 'javascript', - code: `import { ToolLoopAgent, tool } from "npm:ai"; -import { z } from "npm:zod"; - -const agent = new ToolLoopAgent({ - model: "openai/gpt-5.4", - tools: { - weather: tool({ - description: "Get the weather in a location", - inputSchema: z.object({ - location: z.string().describe("The location to get the weather for"), - }), - execute: async ({ location }) => ({ - location, - temperature: 72 + Math.floor(Math.random() * 21) - 10, - }), - }), - }, - telemetry: { - isEnabled: true, - functionId: "weather_agent", - recordInputs: true, - recordOutputs: true, - }, -}); - -const result = await agent.generate({ - prompt: "What is the weather in San Francisco?", -});`, - }, - ], - }, - { - type: 'custom', - content: ( - - } - /> - ), - }, - ], - }, - ]; - }, + configure: params => [...configureSteps(params), ...getAgentDataCollectionStep(params)], verify: () => [ { type: StepType.VERIFY, diff --git a/static/app/gettingStartedDocs/deno/mcp.tsx b/static/app/gettingStartedDocs/deno/mcp.tsx index 15b0bf53539f..435966780386 100644 --- a/static/app/gettingStartedDocs/deno/mcp.tsx +++ b/static/app/gettingStartedDocs/deno/mcp.tsx @@ -1,5 +1,9 @@ import type {OnboardingConfig} from 'sentry/components/onboarding/gettingStartedDoc/types'; import {StepType} from 'sentry/components/onboarding/gettingStartedDoc/types'; +import { + GEN_AI_DATA_COLLECTION_SNIPPET, + getDataCollectionStep, +} from 'sentry/components/onboarding/gettingStartedDoc/utils'; import {t, tct} from 'sentry/locale'; import {getInstallContent, sentryImport} from './utils'; @@ -30,12 +34,6 @@ Sentry.init({ dsn: "${params.dsn.public}", // Tracing must be enabled for MCP monitoring to work tracesSampleRate: 1.0, - dataCollection: { - // To disable sending user data and HTTP bodies, uncomment the lines below. For more info visit: - // https://docs.sentry.io/platforms/javascript/guides/deno/configuration/options/#dataCollection - // userInfo: false, - // httpBodies: [], - }, });`, }, { @@ -57,6 +55,16 @@ const server = Sentry.wrapMcpServerWithSentry(new McpServer({ }, ], }, + // Not collapsible: the MCP onboarding's GuidedSteps drops collapsible steps. + getDataCollectionStep({ + collapsible: false, + docsLink: + 'https://docs.sentry.io/platforms/javascript/guides/deno/configuration/options/#dataCollection', + description: t( + 'By default, the SDK sends the inputs and outputs of your MCP tool calls, prompt retrievals, and resource reads. This gives you rich debugging context.' + ), + code: GEN_AI_DATA_COLLECTION_SNIPPET, + }), ], verify: () => [ { diff --git a/static/app/gettingStartedDocs/deno/onboarding.spec.tsx b/static/app/gettingStartedDocs/deno/onboarding.spec.tsx index 0b9bf146b54f..ad6a6807b50b 100644 --- a/static/app/gettingStartedDocs/deno/onboarding.spec.tsx +++ b/static/app/gettingStartedDocs/deno/onboarding.spec.tsx @@ -29,8 +29,10 @@ describe('deno onboarding docs', () => { expect( screen.getByText(textWithMarkupMatcher(/tracesSampleRate: 1\.0,/)) ).toBeInTheDocument(); + + // `dataCollection` is presented as its own step rather than in the snippet. expect( - screen.getByText(textWithMarkupMatcher(/dataCollection: \{/)) + screen.getByText('Control the Data You Send to Sentry (Optional)') ).toBeInTheDocument(); }); diff --git a/static/app/gettingStartedDocs/deno/onboarding.tsx b/static/app/gettingStartedDocs/deno/onboarding.tsx index da15f844a480..b4cc34bef4b4 100644 --- a/static/app/gettingStartedDocs/deno/onboarding.tsx +++ b/static/app/gettingStartedDocs/deno/onboarding.tsx @@ -3,6 +3,7 @@ import type { OnboardingConfig, } from 'sentry/components/onboarding/gettingStartedDoc/types'; import {StepType} from 'sentry/components/onboarding/gettingStartedDoc/types'; +import {getDataCollectionStep} from 'sentry/components/onboarding/gettingStartedDoc/utils'; import {t, tct} from 'sentry/locale'; import {getInstallContent, getSdkInitSnippet, PACKAGE_NAME, sentryImport} from './utils'; @@ -123,6 +124,10 @@ export const onboarding: OnboardingConfig = { }, ], }, + getDataCollectionStep({ + docsLink: + 'https://docs.sentry.io/platforms/javascript/guides/deno/configuration/options/#dataCollection', + }), ], verify: params => [ { diff --git a/static/app/gettingStartedDocs/deno/utils.tsx b/static/app/gettingStartedDocs/deno/utils.tsx index 27aa08255d4d..510a544b899c 100644 --- a/static/app/gettingStartedDocs/deno/utils.tsx +++ b/static/app/gettingStartedDocs/deno/utils.tsx @@ -75,12 +75,5 @@ Sentry.init({ tracesSampleRate: 1.0,` : '' } - - dataCollection: { - // To disable sending user data and HTTP bodies, uncomment the lines below. For more info visit: - // https://docs.sentry.io/platforms/javascript/guides/deno/configuration/options/#dataCollection - // userInfo: false, - // httpBodies: [], - }, });`; } diff --git a/static/app/gettingStartedDocs/electron/onboarding.tsx b/static/app/gettingStartedDocs/electron/onboarding.tsx index 28f0d07fe74a..e0b7b35f9085 100644 --- a/static/app/gettingStartedDocs/electron/onboarding.tsx +++ b/static/app/gettingStartedDocs/electron/onboarding.tsx @@ -5,7 +5,10 @@ import type { OnboardingConfig, } from 'sentry/components/onboarding/gettingStartedDoc/types'; import {StepType} from 'sentry/components/onboarding/gettingStartedDoc/types'; -import {getUploadSourceMapsStep} from 'sentry/components/onboarding/gettingStartedDoc/utils'; +import { + getDataCollectionStep, + getUploadSourceMapsStep, +} from 'sentry/components/onboarding/gettingStartedDoc/utils'; import {t, tct} from 'sentry/locale'; import {installCodeBlock} from './utils'; @@ -58,6 +61,10 @@ export const onboarding: OnboardingConfig = { 'https://docs.sentry.io/platforms/javascript/guides/electron/sourcemaps/', ...params, }), + getDataCollectionStep({ + docsLink: + 'https://docs.sentry.io/platforms/javascript/guides/electron/configuration/options/#dataCollection', + }), ], verify: () => [ { diff --git a/static/app/gettingStartedDocs/javascript-angular/onboarding.tsx b/static/app/gettingStartedDocs/javascript-angular/onboarding.tsx index ef47917291fc..a695b99fb4bf 100644 --- a/static/app/gettingStartedDocs/javascript-angular/onboarding.tsx +++ b/static/app/gettingStartedDocs/javascript-angular/onboarding.tsx @@ -1,6 +1,9 @@ import {StepType} from 'sentry/components/onboarding/gettingStartedDoc/types'; import type {OnboardingConfig} from 'sentry/components/onboarding/gettingStartedDoc/types'; -import {getUploadSourceMapsStep} from 'sentry/components/onboarding/gettingStartedDoc/utils'; +import { + getDataCollectionStep, + getUploadSourceMapsStep, +} from 'sentry/components/onboarding/gettingStartedDoc/utils'; import {t, tct} from 'sentry/locale'; import { @@ -169,6 +172,10 @@ export const onboarding: OnboardingConfig = { guideLink: 'https://docs.sentry.io/platforms/javascript/guides/angular/sourcemaps/', ...params, }), + getDataCollectionStep({ + docsLink: + 'https://docs.sentry.io/platforms/javascript/guides/angular/configuration/options/#dataCollection', + }), ], verify: (params: Params) => [ { diff --git a/static/app/gettingStartedDocs/javascript-angular/utils.tsx b/static/app/gettingStartedDocs/javascript-angular/utils.tsx index 867fb3638c1c..313a8d4db60c 100644 --- a/static/app/gettingStartedDocs/javascript-angular/utils.tsx +++ b/static/app/gettingStartedDocs/javascript-angular/utils.tsx @@ -119,15 +119,7 @@ bootstrapApplication(AppComponent, appConfig) const config = buildSdkConfig({ params, - staticParts: [ - `dsn: "${params.dsn.public}"`, - `dataCollection: { - // To disable sending user data and HTTP bodies, uncomment the lines below. For more info visit: - // https://docs.sentry.io/platforms/javascript/guides/angular/configuration/options/#dataCollection - // userInfo: false, - // httpBodies: [] - }`, - ], + staticParts: [`dsn: "${params.dsn.public}"`], getIntegrations, getDynamicParts, }); diff --git a/static/app/gettingStartedDocs/javascript-astro/onboarding.spec.tsx b/static/app/gettingStartedDocs/javascript-astro/onboarding.spec.tsx index 82320b1e5147..c25dd9d0f60f 100644 --- a/static/app/gettingStartedDocs/javascript-astro/onboarding.spec.tsx +++ b/static/app/gettingStartedDocs/javascript-astro/onboarding.spec.tsx @@ -190,7 +190,6 @@ describe('javascript-astro onboarding docs', () => { expect(runtimeConfigs).toHaveLength(2); for (const config of runtimeConfigs) { expect(config).toHaveTextContent('dsn:'); - expect(config).toHaveTextContent('dataCollection:'); expect(config).toHaveTextContent('tracesSampleRate: 1.0'); } }); diff --git a/static/app/gettingStartedDocs/javascript-astro/onboarding.tsx b/static/app/gettingStartedDocs/javascript-astro/onboarding.tsx index 6f0d9138b994..38dd4192a90f 100644 --- a/static/app/gettingStartedDocs/javascript-astro/onboarding.tsx +++ b/static/app/gettingStartedDocs/javascript-astro/onboarding.tsx @@ -7,6 +7,7 @@ import type { OnboardingConfig, } from 'sentry/components/onboarding/gettingStartedDoc/types'; import {StepType} from 'sentry/components/onboarding/gettingStartedDoc/types'; +import {getDataCollectionStep} from 'sentry/components/onboarding/gettingStartedDoc/utils'; import {t, tct} from 'sentry/locale'; import {installSnippetBlock} from './utils'; @@ -23,13 +24,7 @@ function getServerConfigSnippet(params: DocsParams) { import * as Sentry from "@sentry/astro"; Sentry.init({ - dsn: "${params.dsn.public}", - dataCollection: { - // To disable sending user data and HTTP bodies, uncomment the lines below. For more info visit: - // https://docs.sentry.io/platforms/javascript/guides/astro/configuration/options/#dataCollection - // userInfo: false, - // httpBodies: [], - },${performanceConfig} + dsn: "${params.dsn.public}",${performanceConfig} }); `; } @@ -71,13 +66,7 @@ ${integrations.join('\n')} import * as Sentry from "@sentry/astro"; Sentry.init({ - dsn: "${params.dsn.public}", - dataCollection: { - // To disable sending user data and HTTP bodies, uncomment the lines below. For more info visit: - // https://docs.sentry.io/platforms/javascript/guides/astro/configuration/options/#dataCollection - // userInfo: false, - // httpBodies: [], - },${integrationsConfig}${performanceConfig}${replaySampleRates} + dsn: "${params.dsn.public}",${integrationsConfig}${performanceConfig}${replaySampleRates} }); `; } @@ -253,6 +242,10 @@ export const onboarding: OnboardingConfig = { }, ], }, + getDataCollectionStep({ + docsLink: + 'https://docs.sentry.io/platforms/javascript/guides/astro/configuration/options/#dataCollection', + }), ], verify: params => [ { diff --git a/static/app/gettingStartedDocs/javascript-ember/onboarding.spec.tsx b/static/app/gettingStartedDocs/javascript-ember/onboarding.spec.tsx index 9f91faee9fed..f35cb6e540f1 100644 --- a/static/app/gettingStartedDocs/javascript-ember/onboarding.spec.tsx +++ b/static/app/gettingStartedDocs/javascript-ember/onboarding.spec.tsx @@ -29,8 +29,12 @@ describe('javascript-ember onboarding docs', () => { const setup = screen.getByText(textWithMarkupMatcher(/Sentry\.init\(/)); expect(setup).toHaveTextContent('import config from "./config/environment"'); expect(setup).toHaveTextContent('loadInitializers(App, config.modulePrefix)'); - expect(setup).toHaveTextContent('dataCollection:'); expect(setup).not.toHaveTextContent(/sendDefaultPii|enableLogs|enableMetrics/); + + // `dataCollection` is presented as its own step rather than in the snippet. + expect( + screen.getByText('Control the Data You Send to Sentry (Optional)') + ).toBeInTheDocument(); }); it('registers a performance instance initializer when tracing is selected', () => { diff --git a/static/app/gettingStartedDocs/javascript-ember/onboarding.tsx b/static/app/gettingStartedDocs/javascript-ember/onboarding.tsx index 9b4ac9f2619e..dda8325a7679 100644 --- a/static/app/gettingStartedDocs/javascript-ember/onboarding.tsx +++ b/static/app/gettingStartedDocs/javascript-ember/onboarding.tsx @@ -4,7 +4,10 @@ import type { OnboardingConfig, } from 'sentry/components/onboarding/gettingStartedDoc/types'; import {StepType} from 'sentry/components/onboarding/gettingStartedDoc/types'; -import {getUploadSourceMapsStep} from 'sentry/components/onboarding/gettingStartedDoc/utils'; +import { + getDataCollectionStep, + getUploadSourceMapsStep, +} from 'sentry/components/onboarding/gettingStartedDoc/utils'; import {t, tct} from 'sentry/locale'; import {getSdkSetupSnippet, installSnippetBlock} from './utils'; @@ -116,6 +119,10 @@ export default { initialize };`, guideLink: 'https://docs.sentry.io/platforms/javascript/guides/ember/sourcemaps/', ...params, }), + getDataCollectionStep({ + docsLink: + 'https://docs.sentry.io/platforms/javascript/guides/ember/configuration/options/#dataCollection', + }), ], verify: (params: DocsParams) => [ { diff --git a/static/app/gettingStartedDocs/javascript-ember/utils.tsx b/static/app/gettingStartedDocs/javascript-ember/utils.tsx index e5978eda4d8b..a4cbf05b8d4f 100644 --- a/static/app/gettingStartedDocs/javascript-ember/utils.tsx +++ b/static/app/gettingStartedDocs/javascript-ember/utils.tsx @@ -61,15 +61,7 @@ const getDynamicParts = (params: DocsParams): string[] => { export function getSdkSetupSnippet(params: DocsParams) { const config = buildSdkConfig({ params, - staticParts: [ - `dsn: "${params.dsn.public}"`, - `dataCollection: { - // To disable sending user data and HTTP bodies, uncomment the lines below. For more info visit: - // https://docs.sentry.io/platforms/javascript/guides/ember/configuration/options/#dataCollection - // userInfo: false, - // httpBodies: [] - }`, - ], + staticParts: [`dsn: "${params.dsn.public}"`], getIntegrations, getDynamicParts, }); diff --git a/static/app/gettingStartedDocs/javascript-gatsby/onboarding.spec.tsx b/static/app/gettingStartedDocs/javascript-gatsby/onboarding.spec.tsx index ee0e356052c5..1f25bb19fab6 100644 --- a/static/app/gettingStartedDocs/javascript-gatsby/onboarding.spec.tsx +++ b/static/app/gettingStartedDocs/javascript-gatsby/onboarding.spec.tsx @@ -1,5 +1,5 @@ import {renderWithOnboardingLayout} from 'sentry-test/onboarding/renderWithOnboardingLayout'; -import {screen} from 'sentry-test/reactTestingLibrary'; +import {screen, userEvent} from 'sentry-test/reactTestingLibrary'; import {textWithMarkupMatcher} from 'sentry-test/utils'; import {ProductSolution} from 'sentry/components/onboarding/gettingStartedDoc/types'; @@ -140,14 +140,19 @@ describe('javascript-gatsby onboarding docs', () => { ).toBeInTheDocument(); }); - it('includes dataCollection configuration', () => { + it('includes the data collection step', async () => { renderWithOnboardingLayout(docs); + // The step is collapsible, so its content only renders once expanded. + await userEvent.click( + screen.getByText('Control the Data You Send to Sentry (Optional)') + ); + expect( screen.getByText(textWithMarkupMatcher(/dataCollection: \{/)) ).toBeInTheDocument(); expect( - screen.getByText(textWithMarkupMatcher(/\/\/ httpBodies: \[\]/)) + screen.getByText(textWithMarkupMatcher(/userInfo: false/)) ).toBeInTheDocument(); }); diff --git a/static/app/gettingStartedDocs/javascript-gatsby/onboarding.tsx b/static/app/gettingStartedDocs/javascript-gatsby/onboarding.tsx index ad9b3968f35e..20e42e46949e 100644 --- a/static/app/gettingStartedDocs/javascript-gatsby/onboarding.tsx +++ b/static/app/gettingStartedDocs/javascript-gatsby/onboarding.tsx @@ -3,7 +3,10 @@ import type { OnboardingConfig, } from 'sentry/components/onboarding/gettingStartedDoc/types'; import {StepType} from 'sentry/components/onboarding/gettingStartedDoc/types'; -import {getUploadSourceMapsStep} from 'sentry/components/onboarding/gettingStartedDoc/utils'; +import { + getDataCollectionStep, + getUploadSourceMapsStep, +} from 'sentry/components/onboarding/gettingStartedDoc/utils'; import {t, tct} from 'sentry/locale'; import {getConfigureStep, installSnippetBlock} from './utils'; @@ -60,6 +63,10 @@ export const onboarding: OnboardingConfig = { guideLink: 'https://docs.sentry.io/platforms/javascript/guides/gatsby/sourcemaps/', ...params, }), + getDataCollectionStep({ + docsLink: + 'https://docs.sentry.io/platforms/javascript/guides/gatsby/configuration/options/#dataCollection', + }), ], verify: (params: DocsParams) => [ { diff --git a/static/app/gettingStartedDocs/javascript-gatsby/utils.tsx b/static/app/gettingStartedDocs/javascript-gatsby/utils.tsx index acc8d89785fb..f6066d8d09dd 100644 --- a/static/app/gettingStartedDocs/javascript-gatsby/utils.tsx +++ b/static/app/gettingStartedDocs/javascript-gatsby/utils.tsx @@ -67,15 +67,7 @@ const getDynamicParts = (params: DocsParams): string[] => { export function getSdkSetupSnippet(params: DocsParams) { const config = buildSdkConfig({ params, - staticParts: [ - `dsn: "${params.dsn.public}"`, - `dataCollection: { - // To disable sending user data and HTTP bodies, uncomment the lines below. For more info visit: - // https://docs.sentry.io/platforms/javascript/guides/gatsby/configuration/options/#dataCollection - // userInfo: false, - // httpBodies: [] - }`, - ], + staticParts: [`dsn: "${params.dsn.public}"`], getIntegrations, getDynamicParts, }); diff --git a/static/app/gettingStartedDocs/javascript-nextjs/onboarding.tsx b/static/app/gettingStartedDocs/javascript-nextjs/onboarding.tsx index af8beee47e88..3ea2baa5a3d0 100644 --- a/static/app/gettingStartedDocs/javascript-nextjs/onboarding.tsx +++ b/static/app/gettingStartedDocs/javascript-nextjs/onboarding.tsx @@ -6,7 +6,10 @@ import type { OnboardingConfig, } from 'sentry/components/onboarding/gettingStartedDoc/types'; import {StepType} from 'sentry/components/onboarding/gettingStartedDoc/types'; -import {getAISetupStep} from 'sentry/components/onboarding/gettingStartedDoc/utils'; +import { + getAISetupStep, + getDataCollectionStep, +} from 'sentry/components/onboarding/gettingStartedDoc/utils'; import {t, tct} from 'sentry/locale'; import {getInstallSnippet} from './utils'; @@ -62,6 +65,10 @@ export const onboarding: OnboardingConfig = { ], }, getAISetupStep({sdkName: 'Next.js'}), + getDataCollectionStep({ + docsLink: + 'https://docs.sentry.io/platforms/javascript/guides/nextjs/configuration/options/#dataCollection', + }), ], verify: () => [ { diff --git a/static/app/gettingStartedDocs/javascript-nuxt/onboarding.tsx b/static/app/gettingStartedDocs/javascript-nuxt/onboarding.tsx index 73436bb74126..a69dc65bd1aa 100644 --- a/static/app/gettingStartedDocs/javascript-nuxt/onboarding.tsx +++ b/static/app/gettingStartedDocs/javascript-nuxt/onboarding.tsx @@ -6,6 +6,7 @@ import type { OnboardingConfig, } from 'sentry/components/onboarding/gettingStartedDoc/types'; import {StepType} from 'sentry/components/onboarding/gettingStartedDoc/types'; +import {getDataCollectionStep} from 'sentry/components/onboarding/gettingStartedDoc/utils'; import {t, tct} from 'sentry/locale'; import {getInstallContent} from './utils'; @@ -36,6 +37,10 @@ export const onboarding: OnboardingConfig = { copyDsnFieldBlock(params), ], }, + getDataCollectionStep({ + docsLink: + 'https://docs.sentry.io/platforms/javascript/guides/nuxt/configuration/options/#dataCollection', + }), ], verify: () => [ { diff --git a/static/app/gettingStartedDocs/javascript-react-router/onboarding.tsx b/static/app/gettingStartedDocs/javascript-react-router/onboarding.tsx index 4f4b34e5902e..74a78b203427 100644 --- a/static/app/gettingStartedDocs/javascript-react-router/onboarding.tsx +++ b/static/app/gettingStartedDocs/javascript-react-router/onboarding.tsx @@ -8,6 +8,7 @@ import type { OnboardingConfig, } from 'sentry/components/onboarding/gettingStartedDoc/types'; import {StepType} from 'sentry/components/onboarding/gettingStartedDoc/types'; +import {getDataCollectionStep} from 'sentry/components/onboarding/gettingStartedDoc/utils'; import {t, tct} from 'sentry/locale'; import {getInstallSnippet} from './utils'; @@ -70,6 +71,10 @@ export const onboarding: OnboardingConfig = { copyDsnFieldBlock(params), ], }, + getDataCollectionStep({ + docsLink: + 'https://docs.sentry.io/platforms/javascript/guides/react-router/configuration/options/#dataCollection', + }), ], verify: () => [ { diff --git a/static/app/gettingStartedDocs/javascript-react-router/utils.tsx b/static/app/gettingStartedDocs/javascript-react-router/utils.tsx index 0793800439a7..872e21163fe9 100644 --- a/static/app/gettingStartedDocs/javascript-react-router/utils.tsx +++ b/static/app/gettingStartedDocs/javascript-react-router/utils.tsx @@ -59,13 +59,7 @@ import { hydrateRoot } from "react-dom/client"; import { HydratedRouter } from "react-router/dom"; Sentry.init({ - dsn: "${params.dsn.public}", - dataCollection: { - // To disable sending user data and HTTP bodies, uncomment the lines below. For more info visit: - // https://docs.sentry.io/platforms/javascript/guides/react-router/configuration/options/#dataCollection - // userInfo: false, - // httpBodies: [], - },${integrationsCode}${performanceSnippet}${replaySnippet} + dsn: "${params.dsn.public}",${integrationsCode}${performanceSnippet}${replaySnippet} }); startTransition(() => { diff --git a/static/app/gettingStartedDocs/javascript-react/onboarding.tsx b/static/app/gettingStartedDocs/javascript-react/onboarding.tsx index 9ee4351e699b..7cca4977b07f 100644 --- a/static/app/gettingStartedDocs/javascript-react/onboarding.tsx +++ b/static/app/gettingStartedDocs/javascript-react/onboarding.tsx @@ -6,6 +6,7 @@ import type { import {StepType} from 'sentry/components/onboarding/gettingStartedDoc/types'; import { getAISetupStep, + getDataCollectionStep, getUploadSourceMapsStep, } from 'sentry/components/onboarding/gettingStartedDoc/utils'; import {t, tct} from 'sentry/locale'; @@ -98,6 +99,10 @@ export const onboarding: OnboardingConfig = { ...params, }), getAISetupStep({sdkName: 'React'}), + getDataCollectionStep({ + docsLink: + 'https://docs.sentry.io/platforms/javascript/guides/react/configuration/options/#dataCollection', + }), ], verify: (params: DocsParams) => [ { diff --git a/static/app/gettingStartedDocs/javascript-react/utils.tsx b/static/app/gettingStartedDocs/javascript-react/utils.tsx index a84b4f6b7187..0ed187516746 100644 --- a/static/app/gettingStartedDocs/javascript-react/utils.tsx +++ b/static/app/gettingStartedDocs/javascript-react/utils.tsx @@ -64,15 +64,7 @@ const getIntegrations = (params: DocsParams): string[] => { export function getSdkSetupSnippet(params: DocsParams) { const config = buildSdkConfig({ params, - staticParts: [ - `dsn: "${params.dsn.public}"`, - `dataCollection: { - // To disable sending user data and HTTP bodies, uncomment the lines below. For more info visit: - // https://docs.sentry.io/platforms/javascript/guides/react/configuration/options/#dataCollection - // userInfo: false, - // httpBodies: [] - }`, - ], + staticParts: [`dsn: "${params.dsn.public}"`], getIntegrations, getDynamicParts, }); diff --git a/static/app/gettingStartedDocs/javascript-remix/onboarding.tsx b/static/app/gettingStartedDocs/javascript-remix/onboarding.tsx index 9248763c12f5..8adee9176b2e 100644 --- a/static/app/gettingStartedDocs/javascript-remix/onboarding.tsx +++ b/static/app/gettingStartedDocs/javascript-remix/onboarding.tsx @@ -6,6 +6,7 @@ import type { OnboardingConfig, } from 'sentry/components/onboarding/gettingStartedDoc/types'; import {StepType} from 'sentry/components/onboarding/gettingStartedDoc/types'; +import {getDataCollectionStep} from 'sentry/components/onboarding/gettingStartedDoc/utils'; import {t, tct} from 'sentry/locale'; import {getInstallContent} from './utils'; @@ -43,6 +44,10 @@ export const onboarding: OnboardingConfig = { copyDsnFieldBlock(params), ], }, + getDataCollectionStep({ + docsLink: + 'https://docs.sentry.io/platforms/javascript/guides/remix/configuration/options/#dataCollection', + }), ], verify: () => [ { diff --git a/static/app/gettingStartedDocs/javascript-solid/onboarding.tsx b/static/app/gettingStartedDocs/javascript-solid/onboarding.tsx index e8d35dc3d747..916cab17aeed 100644 --- a/static/app/gettingStartedDocs/javascript-solid/onboarding.tsx +++ b/static/app/gettingStartedDocs/javascript-solid/onboarding.tsx @@ -6,7 +6,10 @@ import type { OnboardingConfig, } from 'sentry/components/onboarding/gettingStartedDoc/types'; import {StepType} from 'sentry/components/onboarding/gettingStartedDoc/types'; -import {getUploadSourceMapsStep} from 'sentry/components/onboarding/gettingStartedDoc/utils'; +import { + getDataCollectionStep, + getUploadSourceMapsStep, +} from 'sentry/components/onboarding/gettingStartedDoc/utils'; import {t, tct} from 'sentry/locale'; import {getSdkSetupSnippet, installSnippetBlock} from './utils'; @@ -99,6 +102,10 @@ export const onboarding: OnboardingConfig = { guideLink: 'https://docs.sentry.io/platforms/javascript/guides/solid/sourcemaps/', ...params, }), + getDataCollectionStep({ + docsLink: + 'https://docs.sentry.io/platforms/javascript/guides/solid/configuration/options/#dataCollection', + }), ], verify: (params: DocsParams) => [ { diff --git a/static/app/gettingStartedDocs/javascript-solid/utils.tsx b/static/app/gettingStartedDocs/javascript-solid/utils.tsx index c2d42a131d92..6548d7a0e1d0 100644 --- a/static/app/gettingStartedDocs/javascript-solid/utils.tsx +++ b/static/app/gettingStartedDocs/javascript-solid/utils.tsx @@ -67,15 +67,7 @@ const getDynamicParts = (params: DocsParams): string[] => { export function getSdkSetupSnippet(params: DocsParams) { const config = buildSdkConfig({ params, - staticParts: [ - `dsn: "${params.dsn.public}"`, - `dataCollection: { - // To disable sending user data and HTTP bodies, uncomment the lines below. For more info visit: - // https://docs.sentry.io/platforms/javascript/guides/solid/configuration/options/#dataCollection - // userInfo: false, - // httpBodies: [] - }`, - ], + staticParts: [`dsn: "${params.dsn.public}"`], getIntegrations, getDynamicParts, }); diff --git a/static/app/gettingStartedDocs/javascript-solidstart/onboarding.tsx b/static/app/gettingStartedDocs/javascript-solidstart/onboarding.tsx index e7b52da48763..dda4486ed235 100644 --- a/static/app/gettingStartedDocs/javascript-solidstart/onboarding.tsx +++ b/static/app/gettingStartedDocs/javascript-solidstart/onboarding.tsx @@ -6,7 +6,10 @@ import type { OnboardingConfig, } from 'sentry/components/onboarding/gettingStartedDoc/types'; import {StepType} from 'sentry/components/onboarding/gettingStartedDoc/types'; -import {getUploadSourceMapsStep} from 'sentry/components/onboarding/gettingStartedDoc/utils'; +import { + getDataCollectionStep, + getUploadSourceMapsStep, +} from 'sentry/components/onboarding/gettingStartedDoc/utils'; import {t, tct} from 'sentry/locale'; import {getSdkClientSetupSnippet, installSnippetBlock} from './utils'; @@ -33,12 +36,6 @@ Sentry.init({ profileSessionSampleRate: 1.0,` : '' } - dataCollection: { - // To disable sending user data and HTTP bodies, uncomment the lines below. For more info visit: - // https://docs.sentry.io/platforms/javascript/guides/solidstart/configuration/options/#dataCollection - // userInfo: false, - // httpBodies: [], - }, }); `; } @@ -293,6 +290,10 @@ export const onboarding: OnboardingConfig = { ), ...params, }), + getDataCollectionStep({ + docsLink: + 'https://docs.sentry.io/platforms/javascript/guides/solidstart/configuration/options/#dataCollection', + }), ], verify: (params: DocsParams) => [ { diff --git a/static/app/gettingStartedDocs/javascript-solidstart/utils.tsx b/static/app/gettingStartedDocs/javascript-solidstart/utils.tsx index 288b58517623..63fb0c470464 100644 --- a/static/app/gettingStartedDocs/javascript-solidstart/utils.tsx +++ b/static/app/gettingStartedDocs/javascript-solidstart/utils.tsx @@ -67,15 +67,7 @@ const getDynamicParts = (params: DocsParams): string[] => { export function getSdkClientSetupSnippet(params: DocsParams) { const config = buildSdkConfig({ params, - staticParts: [ - `dsn: "${params.dsn.public}"`, - `dataCollection: { - // To disable sending user data and HTTP bodies, uncomment the lines below. For more info visit: - // https://docs.sentry.io/platforms/javascript/guides/solidstart/configuration/options/#dataCollection - // userInfo: false, - // httpBodies: [] - }`, - ], + staticParts: [`dsn: "${params.dsn.public}"`], getIntegrations, getDynamicParts, }); diff --git a/static/app/gettingStartedDocs/javascript-svelte/onboarding.tsx b/static/app/gettingStartedDocs/javascript-svelte/onboarding.tsx index fed3527d4875..e3a853935f80 100644 --- a/static/app/gettingStartedDocs/javascript-svelte/onboarding.tsx +++ b/static/app/gettingStartedDocs/javascript-svelte/onboarding.tsx @@ -3,7 +3,10 @@ import type { OnboardingConfig, } from 'sentry/components/onboarding/gettingStartedDoc/types'; import {StepType} from 'sentry/components/onboarding/gettingStartedDoc/types'; -import {getUploadSourceMapsStep} from 'sentry/components/onboarding/gettingStartedDoc/utils'; +import { + getDataCollectionStep, + getUploadSourceMapsStep, +} from 'sentry/components/onboarding/gettingStartedDoc/utils'; import {t, tct} from 'sentry/locale'; import {getSdkSetupSnippet, installSnippetBlock} from './utils'; @@ -96,6 +99,10 @@ export const onboarding: OnboardingConfig = { guideLink: 'https://docs.sentry.io/platforms/javascript/guides/svelte/sourcemaps/', ...params, }), + getDataCollectionStep({ + docsLink: + 'https://docs.sentry.io/platforms/javascript/guides/svelte/configuration/options/#dataCollection', + }), ], verify: (params: DocsParams) => [ { diff --git a/static/app/gettingStartedDocs/javascript-svelte/utils.tsx b/static/app/gettingStartedDocs/javascript-svelte/utils.tsx index 06e8570bbb29..269d73f5cc50 100644 --- a/static/app/gettingStartedDocs/javascript-svelte/utils.tsx +++ b/static/app/gettingStartedDocs/javascript-svelte/utils.tsx @@ -64,15 +64,7 @@ const getDynamicParts = (params: DocsParams): string[] => { export function getSdkSetupSnippet(params: DocsParams, isVersion5: boolean) { const config = buildSdkConfig({ params, - staticParts: [ - `dsn: "${params.dsn.public}"`, - `dataCollection: { - // To disable sending user data and HTTP bodies, uncomment the lines below. For more info visit: - // https://docs.sentry.io/platforms/javascript/guides/svelte/configuration/options/#dataCollection - // userInfo: false, - // httpBodies: [] - }`, - ], + staticParts: [`dsn: "${params.dsn.public}"`], getIntegrations, getDynamicParts, }); diff --git a/static/app/gettingStartedDocs/javascript-sveltekit/onboarding.tsx b/static/app/gettingStartedDocs/javascript-sveltekit/onboarding.tsx index f5ba517bfabd..9d70bbf1fed1 100644 --- a/static/app/gettingStartedDocs/javascript-sveltekit/onboarding.tsx +++ b/static/app/gettingStartedDocs/javascript-sveltekit/onboarding.tsx @@ -3,6 +3,7 @@ import {ExternalLink} from '@sentry/scraps/link'; import {copyDsnFieldBlock} from 'sentry/components/onboarding/gettingStartedDoc/copyDsnField'; import type {OnboardingConfig} from 'sentry/components/onboarding/gettingStartedDoc/types'; import {StepType} from 'sentry/components/onboarding/gettingStartedDoc/types'; +import {getDataCollectionStep} from 'sentry/components/onboarding/gettingStartedDoc/utils'; import {t, tct} from 'sentry/locale'; import {getConfigStep} from './utils'; @@ -33,6 +34,10 @@ export const onboarding: OnboardingConfig = { copyDsnFieldBlock(params), ], }, + getDataCollectionStep({ + docsLink: + 'https://docs.sentry.io/platforms/javascript/guides/sveltekit/configuration/options/#dataCollection', + }), ], verify: () => [ { diff --git a/static/app/gettingStartedDocs/javascript-tanstackstart-react/onboarding.tsx b/static/app/gettingStartedDocs/javascript-tanstackstart-react/onboarding.tsx index 93587b607896..c32e5678cb24 100644 --- a/static/app/gettingStartedDocs/javascript-tanstackstart-react/onboarding.tsx +++ b/static/app/gettingStartedDocs/javascript-tanstackstart-react/onboarding.tsx @@ -2,6 +2,7 @@ import {ExternalLink} from '@sentry/scraps/link'; import type {OnboardingConfig} from 'sentry/components/onboarding/gettingStartedDoc/types'; import {StepType} from 'sentry/components/onboarding/gettingStartedDoc/types'; +import {getDataCollectionStep} from 'sentry/components/onboarding/gettingStartedDoc/utils'; import {t, tct} from 'sentry/locale'; export const onboarding: OnboardingConfig = { @@ -69,40 +70,34 @@ export const onboarding: OnboardingConfig = { Sentry.init({ dsn: "${params.dsn.public}", - - dataCollection: { - // To disable sending user data and HTTP bodies, uncomment the lines below. For more info visit: - // https://docs.sentry.io/platforms/javascript/guides/tanstackstart-react/configuration/options/#dataCollection - // userInfo: false, - // httpBodies: [], - },${ - params.isReplaySelected - ? ` +${ + params.isReplaySelected + ? ` integrations: [ Sentry.replayIntegration(), ],` - : '' - }${ - params.isPerformanceSelected - ? ` + : '' +}${ + params.isPerformanceSelected + ? ` // Set tracesSampleRate to 1.0 to capture 100% // of transactions for tracing. // We recommend adjusting this value in production. // Learn more at https://docs.sentry.io/platforms/javascript/configuration/options/#traces-sample-rate tracesSampleRate: 1.0,` - : '' - }${ - params.isReplaySelected - ? ` + : '' + }${ + params.isReplaySelected + ? ` // Capture Replay for 10% of all sessions, // plus for 100% of sessions with an error. replaysSessionSampleRate: 0.1, replaysOnErrorSampleRate: 1.0,` - : '' - } + : '' + } });`, }, ], @@ -200,23 +195,17 @@ export const getRouter = () => { Sentry.init({ dsn: "${params.dsn.public}", - - dataCollection: { - // To disable sending user data and HTTP bodies, uncomment the lines below. For more info visit: - // https://docs.sentry.io/platforms/javascript/guides/tanstackstart-react/configuration/options/#dataCollection - // userInfo: false, - // httpBodies: [], - },${ - params.isPerformanceSelected - ? ` +${ + params.isPerformanceSelected + ? ` // Set tracesSampleRate to 1.0 to capture 100% // of transactions for tracing. // We recommend adjusting this value in production. // Learn more at https://docs.sentry.io/platforms/javascript/configuration/options/#traces-sample-rate tracesSampleRate: 1.0,` - : '' - } + : '' +} });`, }, ], @@ -520,6 +509,10 @@ const route = createRoute({ ], collapsible: true, }, + getDataCollectionStep({ + docsLink: + 'https://docs.sentry.io/platforms/javascript/guides/tanstackstart-react/configuration/options/#dataCollection', + }), ], verify: params => [ { diff --git a/static/app/gettingStartedDocs/javascript-vue/onboarding.spec.tsx b/static/app/gettingStartedDocs/javascript-vue/onboarding.spec.tsx index c18255adbc9f..3cbb86d2f42f 100644 --- a/static/app/gettingStartedDocs/javascript-vue/onboarding.spec.tsx +++ b/static/app/gettingStartedDocs/javascript-vue/onboarding.spec.tsx @@ -68,8 +68,12 @@ describe('javascript-vue onboarding docs', () => { ); const setup = screen.getByText(textWithMarkupMatcher(/Sentry\.init\(/)); - expect(setup).toHaveTextContent('dataCollection:'); expect(setup).not.toHaveTextContent(/sendDefaultPii|enableLogs|enableMetrics/); + + // `dataCollection` is presented as its own step rather than in the snippet. + expect( + screen.getByText('Control the Data You Send to Sentry (Optional)') + ).toBeInTheDocument(); }); it.each([VueVersion.VUE2, VueVersion.VUE3])( diff --git a/static/app/gettingStartedDocs/javascript-vue/onboarding.tsx b/static/app/gettingStartedDocs/javascript-vue/onboarding.tsx index 43315fa97719..65b0f0241444 100644 --- a/static/app/gettingStartedDocs/javascript-vue/onboarding.tsx +++ b/static/app/gettingStartedDocs/javascript-vue/onboarding.tsx @@ -1,6 +1,9 @@ import type {OnboardingConfig} from 'sentry/components/onboarding/gettingStartedDoc/types'; import {StepType} from 'sentry/components/onboarding/gettingStartedDoc/types'; -import {getUploadSourceMapsStep} from 'sentry/components/onboarding/gettingStartedDoc/utils'; +import { + getDataCollectionStep, + getUploadSourceMapsStep, +} from 'sentry/components/onboarding/gettingStartedDoc/utils'; import {t, tct} from 'sentry/locale'; import { @@ -86,6 +89,10 @@ export const onboarding: OnboardingConfig = { guideLink: 'https://docs.sentry.io/platforms/javascript/guides/vue/sourcemaps/', ...params, }), + getDataCollectionStep({ + docsLink: + 'https://docs.sentry.io/platforms/javascript/guides/vue/configuration/options/#dataCollection', + }), ], verify: (params: Params) => [ { diff --git a/static/app/gettingStartedDocs/javascript-vue/utils.tsx b/static/app/gettingStartedDocs/javascript-vue/utils.tsx index bcf185c8c8e4..f4ea01f16e73 100644 --- a/static/app/gettingStartedDocs/javascript-vue/utils.tsx +++ b/static/app/gettingStartedDocs/javascript-vue/utils.tsx @@ -142,12 +142,6 @@ function getSentryInitLayout(params: Params, siblingOption: string): string { staticParts: [ siblingOption === VueVersion.VUE2 ? 'Vue' : 'app', `dsn: "${params.dsn.public}"`, - `dataCollection: { - // To disable sending user data and HTTP bodies, uncomment the lines below. For more info visit: - // https://docs.sentry.io/platforms/javascript/guides/vue/configuration/options/#dataCollection - // userInfo: false, - // httpBodies: [] - }`, ], getIntegrations, getDynamicParts, diff --git a/static/app/gettingStartedDocs/javascript/performance.tsx b/static/app/gettingStartedDocs/javascript/performance.tsx index 236342dbe6a9..00c35468f5f0 100644 --- a/static/app/gettingStartedDocs/javascript/performance.tsx +++ b/static/app/gettingStartedDocs/javascript/performance.tsx @@ -52,12 +52,6 @@ Sentry.init({ tracesSampleRate: 1.0, // Set \`tracePropagationTargets\` to control for which URLs distributed tracing should be enabled tracePropagationTargets: ["localhost", /^https:\\/\\/yourserver\\.io\\/api/], - dataCollection: { - // To disable sending user data and HTTP bodies, uncomment the lines below. For more info visit: - // https://docs.sentry.io/platforms/javascript/configuration/options/#dataCollection - // userInfo: false, - // httpBodies: [], - }, }); `, }, @@ -104,12 +98,6 @@ Sentry.init({ dsn: "${params.dsn.public}", integrations: [Sentry.browserTracingIntegration()], tracePropagationTargets: ["localhost", /^https:\\/\\/yourserver\\.io\\/api/], - dataCollection: { - // To disable sending user data and HTTP bodies, uncomment the lines below. For more info visit: - // https://docs.sentry.io/platforms/javascript/configuration/options/#dataCollection - // userInfo: false, - // httpBodies: [], - }, }); `, }, diff --git a/static/app/gettingStartedDocs/javascript/utils.tsx b/static/app/gettingStartedDocs/javascript/utils.tsx index e0929f163a3f..a7aaea61c08e 100644 --- a/static/app/gettingStartedDocs/javascript/utils.tsx +++ b/static/app/gettingStartedDocs/javascript/utils.tsx @@ -17,6 +17,7 @@ import { } from 'sentry/components/onboarding/gettingStartedDoc/types'; import { getAISetupStep, + getDataCollectionStep, getUploadSourceMapsStep, } from 'sentry/components/onboarding/gettingStartedDoc/utils'; import {getFeedbackConfigOptions} from 'sentry/components/onboarding/gettingStartedDoc/utils/feedbackOnboarding'; @@ -112,15 +113,7 @@ const getDynamicParts = (params: Params): string[] => { export const getSdkSetupSnippet = (params: Params) => { const config = buildSdkConfig({ params, - staticParts: [ - `dsn: "${params.dsn.public}"`, - `dataCollection: { - // To disable sending user data and HTTP bodies, uncomment the lines below. For more info visit: - // https://docs.sentry.io/platforms/javascript/configuration/options/#dataCollection - // userInfo: false, - // httpBodies: [] - }`, - ], + staticParts: [`dsn: "${params.dsn.public}"`], getIntegrations, getDynamicParts, }); @@ -321,6 +314,10 @@ export const loaderScriptOnboarding: OnboardingConfig = { }, }, getAiSetupConfig(), + getDataCollectionStep({ + docsLink: + 'https://docs.sentry.io/platforms/javascript/configuration/options/#dataCollection', + }), ], verify: (params: Params) => getVerifyConfig(params), nextSteps: (params: Params) => { @@ -448,6 +445,10 @@ export const packageManagerOnboarding: OnboardingConfig = { ...params, }), getAiSetupConfig(), + getDataCollectionStep({ + docsLink: + 'https://docs.sentry.io/platforms/javascript/configuration/options/#dataCollection', + }), ], verify: (params: Params) => getVerifyConfig(params), nextSteps: (params: Params) => { diff --git a/static/app/gettingStartedDocs/node-awslambda/onboarding.tsx b/static/app/gettingStartedDocs/node-awslambda/onboarding.tsx index 277793645494..d0c7ccb0f11a 100644 --- a/static/app/gettingStartedDocs/node-awslambda/onboarding.tsx +++ b/static/app/gettingStartedDocs/node-awslambda/onboarding.tsx @@ -2,7 +2,10 @@ import {ExternalLink} from '@sentry/scraps/link'; import {StepType} from 'sentry/components/onboarding/gettingStartedDoc/types'; import type {OnboardingConfig} from 'sentry/components/onboarding/gettingStartedDoc/types'; -import {getUploadSourceMapsStep} from 'sentry/components/onboarding/gettingStartedDoc/utils'; +import { + getDataCollectionStep, + getUploadSourceMapsStep, +} from 'sentry/components/onboarding/gettingStartedDoc/utils'; import {AwsLambdaArn} from 'sentry/gettingStartedDocs/node-awslambda/awslambdaArnSelector'; import {InstallationMethod} from 'sentry/gettingStartedDocs/node-awslambda/utils'; import {getInstallCodeBlock} from 'sentry/gettingStartedDocs/node/utils'; @@ -57,6 +60,10 @@ const commonOnboarding = { ), ...params, }), + getDataCollectionStep({ + docsLink: + 'https://docs.sentry.io/platforms/javascript/guides/aws-lambda/configuration/options/#dataCollection', + }), ], verify: params => [ { diff --git a/static/app/gettingStartedDocs/node-azurefunctions/onboarding.tsx b/static/app/gettingStartedDocs/node-azurefunctions/onboarding.tsx index fb130afb117e..149721476660 100644 --- a/static/app/gettingStartedDocs/node-azurefunctions/onboarding.tsx +++ b/static/app/gettingStartedDocs/node-azurefunctions/onboarding.tsx @@ -3,7 +3,10 @@ import type { OnboardingConfig, } from 'sentry/components/onboarding/gettingStartedDoc/types'; import {StepType} from 'sentry/components/onboarding/gettingStartedDoc/types'; -import {getUploadSourceMapsStep} from 'sentry/components/onboarding/gettingStartedDoc/utils'; +import { + getDataCollectionStep, + getUploadSourceMapsStep, +} from 'sentry/components/onboarding/gettingStartedDoc/utils'; import { getInstallCodeBlock, getSdkInitSnippet, @@ -87,6 +90,10 @@ export const onboarding: OnboardingConfig = { 'https://docs.sentry.io/platforms/javascript/guides/azure-functions/sourcemaps/', ...params, }), + getDataCollectionStep({ + docsLink: + 'https://docs.sentry.io/platforms/javascript/guides/azure-functions/configuration/options/#dataCollection', + }), ], verify: params => [ { diff --git a/static/app/gettingStartedDocs/node-cloudflare-workers/onboarding.tsx b/static/app/gettingStartedDocs/node-cloudflare-workers/onboarding.tsx index 38925114fd73..f63592a11136 100644 --- a/static/app/gettingStartedDocs/node-cloudflare-workers/onboarding.tsx +++ b/static/app/gettingStartedDocs/node-cloudflare-workers/onboarding.tsx @@ -2,7 +2,10 @@ import {ExternalLink} from '@sentry/scraps/link'; import type {OnboardingConfig} from 'sentry/components/onboarding/gettingStartedDoc/types'; import {StepType} from 'sentry/components/onboarding/gettingStartedDoc/types'; -import {getUploadSourceMapsStep} from 'sentry/components/onboarding/gettingStartedDoc/utils'; +import { + getDataCollectionStep, + getUploadSourceMapsStep, +} from 'sentry/components/onboarding/gettingStartedDoc/utils'; import {getInstallCodeBlock} from 'sentry/gettingStartedDocs/node/utils'; import {t, tct} from 'sentry/locale'; @@ -36,14 +39,7 @@ ${indent}// Learn more at ${indent}// https://docs.sentry.io/platforms/javascript/configuration/options/#traces-sample-rate ${indent}tracesSampleRate: 1.0,` : '' - } - -${indent}dataCollection: { -${indent} // To disable sending user data and HTTP bodies, uncomment the lines below. For more info visit: -${indent} // https://docs.sentry.io/platforms/javascript/guides/cloudflare/configuration/options/#dataCollection -${indent} // userInfo: false, -${indent} // httpBodies: [], -${indent}},`; + }`; const getViteConfigSnippet = () => ` import { cloudflare } from "@cloudflare/vite-plugin"; @@ -270,6 +266,10 @@ export const onboarding: OnboardingConfig = { 'https://docs.sentry.io/platforms/javascript/guides/cloudflare/sourcemaps/', ...params, }), + getDataCollectionStep({ + docsLink: + 'https://docs.sentry.io/platforms/javascript/guides/cloudflare/configuration/options/#dataCollection', + }), ], verify: params => [ { diff --git a/static/app/gettingStartedDocs/node-express/onboarding.tsx b/static/app/gettingStartedDocs/node-express/onboarding.tsx index 99319b91ab88..8bc64209ffff 100644 --- a/static/app/gettingStartedDocs/node-express/onboarding.tsx +++ b/static/app/gettingStartedDocs/node-express/onboarding.tsx @@ -5,7 +5,10 @@ import type { OnboardingConfig, } from 'sentry/components/onboarding/gettingStartedDoc/types'; import {StepType} from 'sentry/components/onboarding/gettingStartedDoc/types'; -import {getUploadSourceMapsStep} from 'sentry/components/onboarding/gettingStartedDoc/utils'; +import { + getDataCollectionStep, + getUploadSourceMapsStep, +} from 'sentry/components/onboarding/gettingStartedDoc/utils'; import { getImport, getInstallCodeBlock, @@ -124,6 +127,10 @@ export const onboarding: OnboardingConfig = { guideLink: 'https://docs.sentry.io/platforms/javascript/guides/express/sourcemaps/', ...params, }), + getDataCollectionStep({ + docsLink: + 'https://docs.sentry.io/platforms/javascript/guides/express/configuration/options/#dataCollection', + }), ], verify: (params: DocsParams) => [ { diff --git a/static/app/gettingStartedDocs/node-fastify/onboarding.tsx b/static/app/gettingStartedDocs/node-fastify/onboarding.tsx index 891f1a23b0e4..365d333e3816 100644 --- a/static/app/gettingStartedDocs/node-fastify/onboarding.tsx +++ b/static/app/gettingStartedDocs/node-fastify/onboarding.tsx @@ -5,7 +5,10 @@ import type { OnboardingConfig, } from 'sentry/components/onboarding/gettingStartedDoc/types'; import {StepType} from 'sentry/components/onboarding/gettingStartedDoc/types'; -import {getUploadSourceMapsStep} from 'sentry/components/onboarding/gettingStartedDoc/utils'; +import { + getDataCollectionStep, + getUploadSourceMapsStep, +} from 'sentry/components/onboarding/gettingStartedDoc/utils'; import { getImport, getInstallCodeBlock, @@ -119,6 +122,10 @@ export const onboarding: OnboardingConfig = { guideLink: 'https://docs.sentry.io/platforms/javascript/guides/fastify/sourcemaps/', ...params, }), + getDataCollectionStep({ + docsLink: + 'https://docs.sentry.io/platforms/javascript/guides/fastify/configuration/options/#dataCollection', + }), ], verify: (params: DocsParams) => [ { diff --git a/static/app/gettingStartedDocs/node-gcpfunctions/onboarding.tsx b/static/app/gettingStartedDocs/node-gcpfunctions/onboarding.tsx index 86cd1f8c63af..5018a1e213fc 100644 --- a/static/app/gettingStartedDocs/node-gcpfunctions/onboarding.tsx +++ b/static/app/gettingStartedDocs/node-gcpfunctions/onboarding.tsx @@ -3,7 +3,10 @@ import type { OnboardingConfig, } from 'sentry/components/onboarding/gettingStartedDoc/types'; import {StepType} from 'sentry/components/onboarding/gettingStartedDoc/types'; -import {getUploadSourceMapsStep} from 'sentry/components/onboarding/gettingStartedDoc/utils'; +import { + getDataCollectionStep, + getUploadSourceMapsStep, +} from 'sentry/components/onboarding/gettingStartedDoc/utils'; import { getInstallCodeBlock, getSdkInitSnippet, @@ -98,6 +101,10 @@ export const onboarding: OnboardingConfig = { 'https://docs.sentry.io/platforms/javascript/guides/gcp-functions/sourcemaps/', ...params, }), + getDataCollectionStep({ + docsLink: + 'https://docs.sentry.io/platforms/javascript/guides/gcp-functions/configuration/options/#dataCollection', + }), ], verify: (params: DocsParams) => [ { diff --git a/static/app/gettingStartedDocs/node-hapi/onboarding.tsx b/static/app/gettingStartedDocs/node-hapi/onboarding.tsx index 7defb6f775f8..87ae693dbee3 100644 --- a/static/app/gettingStartedDocs/node-hapi/onboarding.tsx +++ b/static/app/gettingStartedDocs/node-hapi/onboarding.tsx @@ -5,7 +5,10 @@ import type { OnboardingConfig, } from 'sentry/components/onboarding/gettingStartedDoc/types'; import {StepType} from 'sentry/components/onboarding/gettingStartedDoc/types'; -import {getUploadSourceMapsStep} from 'sentry/components/onboarding/gettingStartedDoc/utils'; +import { + getDataCollectionStep, + getUploadSourceMapsStep, +} from 'sentry/components/onboarding/gettingStartedDoc/utils'; import { getImport, getInstallCodeBlock, @@ -148,6 +151,10 @@ export const onboarding: OnboardingConfig = { guideLink: 'https://docs.sentry.io/platforms/javascript/guides/hapi/sourcemaps/', ...params, }), + getDataCollectionStep({ + docsLink: + 'https://docs.sentry.io/platforms/javascript/guides/hapi/configuration/options/#dataCollection', + }), ], verify: (params: DocsParams) => [ { diff --git a/static/app/gettingStartedDocs/node-hono/onboarding.tsx b/static/app/gettingStartedDocs/node-hono/onboarding.tsx index 8ab405abd93f..56a58c37bdf6 100644 --- a/static/app/gettingStartedDocs/node-hono/onboarding.tsx +++ b/static/app/gettingStartedDocs/node-hono/onboarding.tsx @@ -3,7 +3,10 @@ import type { OnboardingConfig, } from 'sentry/components/onboarding/gettingStartedDoc/types'; import {StepType} from 'sentry/components/onboarding/gettingStartedDoc/types'; -import {getUploadSourceMapsStep} from 'sentry/components/onboarding/gettingStartedDoc/utils'; +import { + getDataCollectionStep, + getUploadSourceMapsStep, +} from 'sentry/components/onboarding/gettingStartedDoc/utils'; import {getInstallCodeBlock} from 'sentry/gettingStartedDocs/node/utils'; import {t, tct} from 'sentry/locale'; @@ -72,12 +75,6 @@ Sentry.init({ profileLifecycle: 'trace',` : '' } - dataCollection: { - // To disable sending user data and HTTP bodies, uncomment the lines below. For more info visit: - // https://docs.sentry.io/platforms/javascript/guides/hono/configuration/options/#dataCollection - // userInfo: false, - // httpBodies: [], - }, });`; } @@ -113,12 +110,6 @@ app.use( tracesSampleRate: 1.0,` : '' } - dataCollection: { - // To disable sending user data and HTTP bodies, uncomment the lines below. For more info visit: - // https://docs.sentry.io/platforms/javascript/guides/hono/configuration/options/#dataCollection - // userInfo: false, - // httpBodies: [], - }, }), ); @@ -144,12 +135,6 @@ app.use( tracesSampleRate: 1.0,` : '' } - dataCollection: { - // To disable sending user data and HTTP bodies, uncomment the lines below. For more info visit: - // https://docs.sentry.io/platforms/javascript/guides/hono/configuration/options/#dataCollection - // userInfo: false, - // httpBodies: [], - }, }), ); @@ -343,6 +328,10 @@ const runtimeOnboarding: Record> = { ], }, getSourceMapsStep(params), + getDataCollectionStep({ + docsLink: + 'https://docs.sentry.io/platforms/javascript/guides/hono/configuration/options/#dataCollection', + }), ], verify: (params: Params) => [getVerifyStep(params)], }, @@ -401,6 +390,10 @@ const runtimeOnboarding: Record> = { ], }, getSourceMapsStep(params), + getDataCollectionStep({ + docsLink: + 'https://docs.sentry.io/platforms/javascript/guides/hono/configuration/options/#dataCollection', + }), ], verify: (params: Params) => [getVerifyStep(params)], }, @@ -433,6 +426,10 @@ const runtimeOnboarding: Record> = { ], }, getSourceMapsStep(params), + getDataCollectionStep({ + docsLink: + 'https://docs.sentry.io/platforms/javascript/guides/hono/configuration/options/#dataCollection', + }), ], verify: (params: Params) => [getVerifyStep(params)], }, @@ -465,6 +462,10 @@ const runtimeOnboarding: Record> = { ], }, getSourceMapsStep(params), + getDataCollectionStep({ + docsLink: + 'https://docs.sentry.io/platforms/javascript/guides/hono/configuration/options/#dataCollection', + }), ], verify: (params: Params) => [getVerifyStep(params)], }, diff --git a/static/app/gettingStartedDocs/node-koa/onboarding.tsx b/static/app/gettingStartedDocs/node-koa/onboarding.tsx index 16b7cb76ce4c..3287b00287b7 100644 --- a/static/app/gettingStartedDocs/node-koa/onboarding.tsx +++ b/static/app/gettingStartedDocs/node-koa/onboarding.tsx @@ -5,7 +5,10 @@ import type { OnboardingConfig, } from 'sentry/components/onboarding/gettingStartedDoc/types'; import {StepType} from 'sentry/components/onboarding/gettingStartedDoc/types'; -import {getUploadSourceMapsStep} from 'sentry/components/onboarding/gettingStartedDoc/utils'; +import { + getDataCollectionStep, + getUploadSourceMapsStep, +} from 'sentry/components/onboarding/gettingStartedDoc/utils'; import { getImport, getInstallCodeBlock, @@ -136,6 +139,10 @@ export const onboarding: OnboardingConfig = { guideLink: 'https://docs.sentry.io/platforms/javascript/guides/koa/sourcemaps/', ...params, }), + getDataCollectionStep({ + docsLink: + 'https://docs.sentry.io/platforms/javascript/guides/koa/configuration/options/#dataCollection', + }), ], verify: (params: DocsParams) => [ { diff --git a/static/app/gettingStartedDocs/node-nestjs/onboarding.tsx b/static/app/gettingStartedDocs/node-nestjs/onboarding.tsx index 59087b185a60..2c712251eec8 100644 --- a/static/app/gettingStartedDocs/node-nestjs/onboarding.tsx +++ b/static/app/gettingStartedDocs/node-nestjs/onboarding.tsx @@ -3,7 +3,10 @@ import type { OnboardingConfig, } from 'sentry/components/onboarding/gettingStartedDoc/types'; import {StepType} from 'sentry/components/onboarding/gettingStartedDoc/types'; -import {getUploadSourceMapsStep} from 'sentry/components/onboarding/gettingStartedDoc/utils'; +import { + getDataCollectionStep, + getUploadSourceMapsStep, +} from 'sentry/components/onboarding/gettingStartedDoc/utils'; import { getImportInstrumentSnippet, getInstallCodeBlock, @@ -222,6 +225,10 @@ export const onboarding: OnboardingConfig = { guideLink: 'https://docs.sentry.io/platforms/javascript/guides/nestjs/sourcemaps/', ...params, }), + getDataCollectionStep({ + docsLink: + 'https://docs.sentry.io/platforms/javascript/guides/nestjs/configuration/options/#dataCollection', + }), ], verify: (params: DocsParams) => [ { diff --git a/static/app/gettingStartedDocs/node/agentMonitoring.spec.tsx b/static/app/gettingStartedDocs/node/agentMonitoring.spec.tsx index 382191f2631a..85cb2c4aa425 100644 --- a/static/app/gettingStartedDocs/node/agentMonitoring.spec.tsx +++ b/static/app/gettingStartedDocs/node/agentMonitoring.spec.tsx @@ -305,4 +305,38 @@ describe('node agentMonitoring onboarding', () => { expect(collectText(steps)).toContain('Trigger your agent'); }); }); + describe('data collection step', () => { + const DATA_COLLECTION_TITLE = 'Control the Data You Send to Sentry (Optional)'; + + it.each([ + ['vercel_ai', {integration: 'vercel_ai'}], + ['manual', {integration: 'manual'}], + ['mastra', {integration: 'mastra'}], + ['flue', {integration: 'flue'}], + ['on Cloudflare', {integration: 'openai', deploymentTarget: 'cloudflare'}], + [ + 'cloudflare_agents', + {integration: 'cloudflare_agents', deploymentTarget: 'cloudflare'}, + ], + ])('offers the genAI opt-out for %s', (_label, platformOptions) => { + const steps = config.configure(makeParams(platformOptions)); + const dataCollectionSteps = steps.filter( + step => step.title === DATA_COLLECTION_TITLE + ); + + // Exactly one, even though several integrations reuse another config's steps. + expect(dataCollectionSteps).toHaveLength(1); + expect(collectCode(dataCollectionSteps)).toContain( + 'genAI: { inputs: false, outputs: false }' + ); + // GuidedSteps drops collapsible steps, so a collapsible step would never render. + expect(dataCollectionSteps[0]!.collapsible).toBeFalsy(); + }); + + it('omits the step for Eve, which never configures the Sentry SDK', () => { + const steps = config.configure(makeParams({integration: 'eve'})); + + expect(steps.filter(step => step.title === DATA_COLLECTION_TITLE)).toHaveLength(0); + }); + }); }); diff --git a/static/app/gettingStartedDocs/node/agentMonitoring.tsx b/static/app/gettingStartedDocs/node/agentMonitoring.tsx index 578aef27dc67..373bf9ea49e8 100644 --- a/static/app/gettingStartedDocs/node/agentMonitoring.tsx +++ b/static/app/gettingStartedDocs/node/agentMonitoring.tsx @@ -7,6 +7,11 @@ import { type OnboardingConfig, type OnboardingStep, } from 'sentry/components/onboarding/gettingStartedDoc/types'; +import { + GEN_AI_DATA_COLLECTION_SNIPPET, + getJsDataCollectionDocsLink, + getDataCollectionStep, +} from 'sentry/components/onboarding/gettingStartedDoc/utils'; import {getImport, getInstallCodeBlock} from 'sentry/gettingStartedDocs/node/utils'; import {t, tct} from 'sentry/locale'; import {SdkUpdateAlert} from 'sentry/views/insights/pages/agents/components/sdkUpdateAlert'; @@ -28,6 +33,7 @@ const CLOUDFLARE_AGENTS_MIN_VERSION = '10.69.0'; const CLOUDFLARE_AGENT_TRACING_DOCS = 'https://docs.sentry.io/platforms/javascript/guides/cloudflare/agent-tracing/'; + const CLOUDFLARE_DURABLE_OBJECTS_DOCS = 'https://docs.sentry.io/platforms/javascript/guides/cloudflare/features/durableobject/'; const CLOUDFLARE_AGENTS_SDK_DOCS = @@ -59,6 +65,29 @@ export function getMinRequiredVersion(params: DocsParams, fallback: string): str : fallback; } +/** + * The data collection step for agent monitoring, leading with generative AI + * content. Returns no step for Eve, which never configures the Sentry SDK. + */ +export function getAgentDataCollectionStep(params: DocsParams): OnboardingStep[] { + if (getAgentIntegration(params) === AgentIntegration.EVE) { + return []; + } + + return [ + getDataCollectionStep({ + // GuidedSteps surfaces drop collapsible steps, so this must be a plain step. + collapsible: false, + // Shared across platforms, so resolve the link from the project's platform. + docsLink: getJsDataCollectionDocsLink(params.platformKey), + description: t( + 'By default, the SDK sends the inputs and outputs of your LLM and tool calls, such as prompts, responses, and tool arguments. This gives you rich debugging context.' + ), + code: GEN_AI_DATA_COLLECTION_SNIPPET, + }), + ]; +} + /** * Cloudflare Workers don't expose the public `Sentry.init()` API. Instead the * SDK is bootstrapped by wrapping the worker with `Sentry.withSentry`. The @@ -88,12 +117,7 @@ export default Sentry.withSentry( (env) => ({ dsn: "${dsn}", // Tracing must be enabled for agent monitoring to work - tracesSampleRate: 1.0, - dataCollection: { - // Control data collection of LLMs and tools. - // For more info visit: https://docs.sentry.io/platforms/javascript/data-management/data-collected/ - // genAI: { inputs: false, outputs: false }, - },${integrationsLine} + tracesSampleRate: 1.0,${integrationsLine} }), { async fetch(request, env, ctx) { @@ -687,11 +711,6 @@ Sentry.init({ dsn: "${params.dsn.public}", // Tracing must be enabled for agent monitoring to work tracesSampleRate: 1.0, - dataCollection: { - // Control data collection of LLMs and tools. - // For more info visit: https://docs.sentry.io/platforms/javascript/data-management/data-collected/ - // genAI: { inputs: false, outputs: false }, - }, });`; return [ @@ -944,11 +963,6 @@ Sentry.init({ dsn: "${params.dsn.public}", // Tracing must be enabled for agent monitoring to work tracesSampleRate: 1.0, - dataCollection: { - // Control data collection of LLMs and tools. - // For more info visit: https://docs.sentry.io/platforms/javascript/data-management/data-collected/ - // genAI: { inputs: false, outputs: false }, - }, });`; // On Node the SDK auto-instruments the integration; on Cloudflare the worker is @@ -1191,6 +1205,48 @@ const text = lastMessage.content;`, ]; } +/** + * The configure steps without the data collection step; the factory appends it + * once around these, so no branch can miss or repeat it. + */ +function getAgentConfigureSteps( + params: DocsParams, + { + packageName = '@sentry/node', + configFileName, + }: { + configFileName?: string; + packageName?: `@sentry/${string}`; + } = {} +): OnboardingStep[] { + const selected = getAgentIntegration(params); + + if (selected === AgentIntegration.MANUAL) { + return getManualConfigureStep(params, { + packageName, + }); + } + + if (selected === AgentIntegration.FLUE) { + return flueOnboarding.configure(params); + } + + if (selected === AgentIntegration.EVE) { + return eveOnboarding.configure(params); + } + + if (selected === AgentIntegration.CLOUDFLARE_AGENTS) { + return getCloudflareAgentsConfigureStep(params); + } + + return getConfigureStep({ + params, + integration: selected, + packageName, + configFileName, + }); +} + export const agentMonitoring = ({ packageName = '@sentry/node', configFileName, @@ -1210,33 +1266,9 @@ export const agentMonitoring = ({ packageName, minVersion: MIN_REQUIRED_VERSION, }), - configure: params => { - const selected = getAgentIntegration(params); - - if (selected === AgentIntegration.MANUAL) { - return getManualConfigureStep(params, { - packageName, - }); - } - - if (selected === AgentIntegration.FLUE) { - return flueOnboarding.configure(params); - } - - if (selected === AgentIntegration.EVE) { - return eveOnboarding.configure(params); - } - - if (selected === AgentIntegration.CLOUDFLARE_AGENTS) { - return getCloudflareAgentsConfigureStep(params); - } - - return getConfigureStep({ - params, - integration: selected, - packageName, - configFileName, - }); - }, + configure: params => [ + ...getAgentConfigureSteps(params, {packageName, configFileName}), + ...getAgentDataCollectionStep(params), + ], verify: getVerifyStep, }); diff --git a/static/app/gettingStartedDocs/node/mcp.spec.tsx b/static/app/gettingStartedDocs/node/mcp.spec.tsx new file mode 100644 index 000000000000..c956d5490694 --- /dev/null +++ b/static/app/gettingStartedDocs/node/mcp.spec.tsx @@ -0,0 +1,45 @@ +import type { + DocsParams, + OnboardingStep, +} from 'sentry/components/onboarding/gettingStartedDoc/types'; +import {getNodeMcpOnboarding} from 'sentry/gettingStartedDocs/node/utils'; + +const DATA_COLLECTION_TITLE = 'Control the Data You Send to Sentry (Optional)'; + +function makeParams(platformOptions: Record = {}): DocsParams { + return { + dsn: {public: 'https://public@o1.ingest.sentry.io/1'}, + platformOptions, + project: {id: '1', slug: 'project-slug', platform: 'node'}, + isProfilingSelected: false, + isLogsSelected: false, + isFeedbackSelected: false, + isMetricsSelected: false, + isPerformanceSelected: true, + isReplaySelected: false, + sourcePackageRegistries: {isLoading: false, data: undefined}, + } as unknown as DocsParams; +} + +describe('getNodeMcpOnboarding data collection step', () => { + const config = getNodeMcpOnboarding(); + + it.each(['mcp_sdk', 'manual'])('offers the genAI opt-out for %s', integration => { + const steps = config.configure!(makeParams({integration})); + const dataCollectionSteps = steps.filter( + (step: OnboardingStep) => step.title === DATA_COLLECTION_TITLE + ); + + expect(dataCollectionSteps).toHaveLength(1); + // GuidedSteps drops collapsible steps, so a collapsible step would never render. + expect(dataCollectionSteps[0]!.collapsible).toBeFalsy(); + + const code = dataCollectionSteps + .flatMap(step => step.content ?? []) + .flatMap(block => + block.type === 'code' && 'tabs' in block ? block.tabs.map(tab => tab.code) : [] + ) + .join('\n'); + expect(code).toContain('genAI: { inputs: false, outputs: false }'); + }); +}); diff --git a/static/app/gettingStartedDocs/node/onboarding.tsx b/static/app/gettingStartedDocs/node/onboarding.tsx index 04a12af0b0be..cf267adcc01a 100644 --- a/static/app/gettingStartedDocs/node/onboarding.tsx +++ b/static/app/gettingStartedDocs/node/onboarding.tsx @@ -7,6 +7,7 @@ import type { import {StepType} from 'sentry/components/onboarding/gettingStartedDoc/types'; import { getAISetupStep, + getDataCollectionStep, getUploadSourceMapsStep, } from 'sentry/components/onboarding/gettingStartedDoc/utils'; import {t, tct} from 'sentry/locale'; @@ -158,6 +159,10 @@ export const onboarding: OnboardingConfig = { ...params, }), getAISetupStep({sdkName: 'Node.js'}), + getDataCollectionStep({ + docsLink: + 'https://docs.sentry.io/platforms/javascript/guides/node/configuration/options/#dataCollection', + }), ], verify: (params: DocsParams) => [ { diff --git a/static/app/gettingStartedDocs/node/utils.tsx b/static/app/gettingStartedDocs/node/utils.tsx index b361ca535349..bba6f8386df8 100644 --- a/static/app/gettingStartedDocs/node/utils.tsx +++ b/static/app/gettingStartedDocs/node/utils.tsx @@ -7,6 +7,11 @@ import type { OnboardingConfig, } from 'sentry/components/onboarding/gettingStartedDoc/types'; import {StepType} from 'sentry/components/onboarding/gettingStartedDoc/types'; +import { + GEN_AI_DATA_COLLECTION_SNIPPET, + getJsDataCollectionDocsLink, + getDataCollectionStep, +} from 'sentry/components/onboarding/gettingStartedDoc/utils'; import {t, tct} from 'sentry/locale'; function getInstallSnippet({ @@ -232,13 +237,6 @@ Sentry.init({ // Set sampling rate for profiling - this is evaluated only once per SDK.init call profilesSampleRate: 1.0,` } - - dataCollection: { - // To disable sending user data and HTTP bodies, uncomment the lines below. For more info visit: - // https://docs.sentry.io/platforms/javascript/guides/node/configuration/options/#dataCollection - // userInfo: false, - // httpBodies: [], - }, });${ params.profilingOptions?.defaultProfilingMode === 'continuous' && profilingLifecycle === 'trace' @@ -312,6 +310,23 @@ Sentry.profiler.stopProfiler(); ], }); +/** + * The data collection step for MCP monitoring; `recordInputs`/`recordOutputs` + * on `wrapMcpServerWithSentry` override it per server. Not collapsible because + * the MCP onboarding's `GuidedSteps` drops collapsible steps. + */ +function getMcpDataCollectionStep(params: DocsParams) { + return getDataCollectionStep({ + collapsible: false, + // Shared across platforms, so resolve the link from the project's platform. + docsLink: getJsDataCollectionDocsLink(params.platformKey), + description: t( + 'By default, the SDK sends the inputs and outputs of your MCP tool calls, prompt retrievals, and resource reads. This gives you rich debugging context.' + ), + code: GEN_AI_DATA_COLLECTION_SNIPPET, + }); +} + export const getNodeMcpOnboarding = ({ packageName = '@sentry/node', importPath, @@ -360,12 +375,6 @@ Sentry.init({ dsn: "${params.dsn.public}", // Tracing must be enabled for MCP monitoring to work tracesSampleRate: 1.0, - dataCollection: { - // To disable sending user data and HTTP bodies, uncomment the lines below. For more info visit: - // https://docs.sentry.io/platforms/javascript/guides/node/configuration/options/#dataCollection - // userInfo: false, - // httpBodies: [], - }, });`, }, ], @@ -438,6 +447,7 @@ Sentry.init({ type: StepType.CONFIGURE, content, }, + getMcpDataCollectionStep(params), ]; }, verify: () => [ @@ -612,12 +622,6 @@ Sentry.init({ profileLifecycle: 'trace',` : '' } - dataCollection: { - // To disable sending user data and HTTP bodies, uncomment the lines below. For more info visit: - // https://docs.sentry.io/platforms/javascript/guides/node/configuration/options/#dataCollection - // userInfo: false, - // httpBodies: [], - }, });${ params.isProfilingSelected && params.profilingOptions?.defaultProfilingMode === 'continuous' diff --git a/static/app/views/insights/pages/agents/llmOnboardingInstructions.spec.tsx b/static/app/views/insights/pages/agents/llmOnboardingInstructions.spec.tsx index cda1659bc195..af0615295109 100644 --- a/static/app/views/insights/pages/agents/llmOnboardingInstructions.spec.tsx +++ b/static/app/views/insights/pages/agents/llmOnboardingInstructions.spec.tsx @@ -30,6 +30,42 @@ describe('getAgentSetupPrompt', () => { expect(prompt).toContain('https://docs.sentry.io/ai/agent-plugin/'); } ); + + it.each([ + ['node', 'guides/node'], + ['javascript-nextjs', 'guides/nextjs'], + ['bun', 'guides/bun'], + ['deno', 'guides/deno'], + ] as const)( + 'asks about AI inputs and outputs on %s, linking its guide', + (platform, guidePath) => { + const prompt = getAgentSetupPrompt({ + organizationSlug: OrganizationFixture().slug, + project: ProjectFixture({platform}), + dsn: ProjectKeysFixture()[0]!.dsn.public, + }); + + expect(prompt).toContain('which AI inputs and outputs the SDK sends'); + expect(prompt).toContain( + `https://docs.sentry.io/platforms/javascript/${guidePath}/configuration/options/#dataCollection` + ); + } + ); + + // The prompt also renders for unsupported platforms; they must not get JS guidance. + it.each(['python', 'python-fastapi', 'php-laravel', 'other', undefined] as const)( + 'omits the question on %s, which does not expose dataCollection', + platform => { + const prompt = getAgentSetupPrompt({ + organizationSlug: OrganizationFixture().slug, + project: ProjectFixture({platform}), + dsn: ProjectKeysFixture()[0]!.dsn.public, + }); + + expect(prompt).not.toContain('which AI inputs and outputs the SDK sends'); + expect(prompt).not.toContain('#dataCollection'); + } + ); }); describe('ManualInstrumentationNote', () => { diff --git a/static/app/views/insights/pages/agents/llmOnboardingInstructions.tsx b/static/app/views/insights/pages/agents/llmOnboardingInstructions.tsx index 650d8d02bfc2..a91e6cb7475c 100644 --- a/static/app/views/insights/pages/agents/llmOnboardingInstructions.tsx +++ b/static/app/views/insights/pages/agents/llmOnboardingInstructions.tsx @@ -1,5 +1,9 @@ import {Button} from '@sentry/scraps/button'; +import { + getJsDataCollectionDocsLink, + isJavaScriptPlatform, +} from 'sentry/components/onboarding/gettingStartedDoc/utils'; import {IconCopy} from 'sentry/icons'; import {t, tct} from 'sentry/locale'; import type {Project} from 'sentry/types/project'; @@ -58,11 +62,19 @@ export function getAgentSetupPrompt({ organizationSlug: string; project: Pick; }) { + // `dataCollection` is a JavaScript SDK option, so only JavaScript projects get + // the question. The instrument skill keeps AI capture on unless the user asks. + const dataCollectionStep = isJavaScriptPlatform(project.platform) + ? ` + +Then ask me whether I want to control which AI inputs and outputs the SDK sends, and point me to the [data collection options](${getJsDataCollectionDocsLink(project.platform)}).` + : ''; + return `Read and follow https://skills.sentry.dev/instrument to set up Sentry agent tracing and conversations. Use this existing project: ${organizationSlug}/${project.slug} DSN: ${dsn} -Platform hint: ${project.platform || 'unknown'} +Platform hint: ${project.platform || 'unknown'}${dataCollectionStep} Then offer to set up the [Sentry plugin](https://docs.sentry.io/ai/agent-plugin/) so I can find and fix production issues from my coding agent.`; }