import {StepType} from 'sentry/components/onboarding/gettingStartedDoc/step'; import type { Docs, DocsParams, OnboardingConfig, } from 'sentry/components/onboarding/gettingStartedDoc/types'; import {getUploadSourceMapsStep} from 'sentry/components/onboarding/gettingStartedDoc/utils'; import { getCrashReportApiIntroduction, getCrashReportInstallDescription, } from 'sentry/components/onboarding/gettingStartedDoc/utils/feedbackOnboarding'; import {getJSServerMetricsOnboarding} from 'sentry/components/onboarding/gettingStartedDoc/utils/metricsOnboarding'; import {ProductSolution} from 'sentry/components/onboarding/productSelection'; import {t, tct} from 'sentry/locale'; import type {ProductSelectionMap} from 'sentry/utils/gettingStartedDocs/node'; import { getDefaultNodeImports, getInstallConfig, } from 'sentry/utils/gettingStartedDocs/node'; type Params = DocsParams; const productSelection = (params: Params): ProductSelectionMap => { return { [ProductSolution.ERROR_MONITORING]: true, [ProductSolution.PROFILING]: params.isProfilingSelected, [ProductSolution.PERFORMANCE_MONITORING]: params.isPerformanceSelected, [ProductSolution.SESSION_REPLAY]: params.isReplaySelected, }; }; const getSdkSetupSnippet = (params: Params) => ` ${getDefaultNodeImports({productSelection: productSelection(params)}).join('\n')} import { stripUrlQueryAndFragment } from "@sentry/utils"; import Koa from "koa"; const app = new Koa(); Sentry.init({ dsn: "${params.dsn}", integrations: [${ params.isPerformanceSelected ? ` // Automatically instrument Node.js libraries and frameworks ...Sentry.autoDiscoverNodePerformanceMonitoringIntegrations(),` : '' }${ params.isProfilingSelected ? ` new ProfilingIntegration(),` : '' } ],${ params.isPerformanceSelected ? ` // Performance Monitoring tracesSampleRate: 1.0, // Capture 100% of the transactions` : '' }${ params.isProfilingSelected ? ` // Set sampling rate for profiling - this is relative to tracesSampleRate profilesSampleRate: 1.0,` : '' } });${ params.isPerformanceSelected ? ` const requestHandler = (ctx, next) => { return new Promise((resolve, reject) => { Sentry.runWithAsyncContext(async () => { const hub = Sentry.getCurrentHub(); hub.configureScope((scope) => scope.addEventProcessor((event) => Sentry.addRequestDataToEvent(event, ctx.request, { include: { user: false, }, }) ) ); try { await next(); } catch (err) { reject(err); } resolve(); }); }); }; // This tracing middleware creates a transaction per request const tracingMiddleWare = async (ctx, next) => { const reqMethod = (ctx.method || "").toUpperCase(); const reqUrl = ctx.url && stripUrlQueryAndFragment(ctx.url); // Connect to trace of upstream app let traceparentData; if (ctx.request.get("sentry-trace")) { traceparentData = Sentry.extractTraceparentData( ctx.request.get("sentry-trace") ); } const transaction = Sentry.startTransaction({ name: \`\${reqMethod} \${reqUrl}\`, op: "http.server", ...traceparentData, }); ctx.__sentry_transaction = transaction; // We put the transaction on the scope so users can attach children to it Sentry.getCurrentHub().configureScope((scope) => { scope.setSpan(transaction); }); ctx.res.on("finish", () => { // Push \`transaction.finish\` to the next event loop so open spans have a chance to finish before the transaction closes setImmediate(() => { // If you're using koa router, set the matched route as transaction name if (ctx._matchedRoute) { const mountPath = ctx.mountPath || ""; transaction.setName(\`\${reqMethod} \${mountPath}\${ctx._matchedRoute}\`); } transaction.setHttpStatus(ctx.status); transaction.finish(); }); }); await next(); }; app.use(requestHandler); app.use(tracingMiddleWare);` : '' } // Send errors to Sentry app.on("error", (err, ctx) => { Sentry.withScope((scope) => { scope.addEventProcessor((event) => { return Sentry.addRequestDataToEvent(event, ctx.request); }); Sentry.captureException(err); }); }); app.listen(3000);`; const getVerifySnippet = () => ` app.use(async function () { throw new Error("My first Sentry error!"); }); `; const onboarding: OnboardingConfig = { install: params => [ { type: StepType.INSTALL, description: t('Add the Sentry Node SDK as a dependency:'), configurations: getInstallConfig(params, { additionalPackages: params.isPerformanceSelected ? ['@sentry/utils'] : [], }), }, ], configure: params => [ { type: StepType.CONFIGURE, description: tct( "Initialize Sentry as early as possible in your application's lifecycle, for example in your [code:index.ts/js] entry point:", {code: } ), configurations: [ { language: 'javascript', code: getSdkSetupSnippet(params), }, ], }, getUploadSourceMapsStep({ guideLink: 'https://docs.sentry.io/platforms/node/guides/koa/sourcemaps/', ...params, }), ], verify: () => [ { type: StepType.VERIFY, description: t( "This snippet contains an intentional error and can be used as a test to make sure that everything's working as expected." ), configurations: [ { language: 'javascript', code: getVerifySnippet(), }, ], }, ], }; const feedbackOnboardingNode: OnboardingConfig = { introduction: () => getCrashReportApiIntroduction(), install: () => [ { type: StepType.INSTALL, description: getCrashReportInstallDescription(), configurations: [ { code: [ { label: 'JavaScript', value: 'javascript', language: 'javascript', code: `import * as Sentry from "@sentry/node"; const eventId = Sentry.captureMessage("User Feedback"); // OR: const eventId = Sentry.lastEventId(); const userFeedback = { event_id: eventId, name: "John Doe", email: "john@doe.com", comments: "I really like your App, thanks!", }; Sentry.captureUserFeedback(userFeedback); `, }, ], }, ], }, ], configure: () => [], verify: () => [], nextSteps: () => [], }; const docs: Docs = { onboarding, feedbackOnboardingCrashApi: feedbackOnboardingNode, customMetricsOnboarding: getJSServerMetricsOnboarding(), }; export default docs;