Skip to content

feat(onboarding): Add logs to react router onboarding #97438

New issue

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

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

Already on GitHub? Sign in to your account

Merged
merged 4 commits into from
Aug 8, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions static/app/components/onboarding/productSelection.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -137,6 +137,7 @@ export const platformProductAvailability = {
'javascript-react-router': [
ProductSolution.PERFORMANCE_MONITORING,
ProductSolution.SESSION_REPLAY,
ProductSolution.LOGS,
],
'javascript-vue': [
ProductSolution.PERFORMANCE_MONITORING,
Expand Down
108 changes: 108 additions & 0 deletions static/app/gettingStartedDocs/javascript/react-router.spec.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,108 @@
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 './react-router';

describe('javascript-react-router onboarding docs', function () {
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 Sentry import statement in multiple configure sections
expect(
screen.getAllByText(
textWithMarkupMatcher(/import \* as Sentry from ["']@sentry\/react-router["'];/)
)
).toHaveLength(4);
});

it('displays sample rates by default', () => {
renderWithOnboardingLayout(docs, {
selectedProducts: [
ProductSolution.ERROR_MONITORING,
ProductSolution.PERFORMANCE_MONITORING,
ProductSolution.SESSION_REPLAY,
],
});

expect(
screen.getAllByText(textWithMarkupMatcher(/tracesSampleRate/)).length
).toBeGreaterThan(0);
expect(
screen.getAllByText(textWithMarkupMatcher(/replaysSessionSampleRate/)).length
).toBeGreaterThan(0);
expect(
screen.getAllByText(textWithMarkupMatcher(/replaysOnErrorSampleRate/)).length
).toBeGreaterThan(0);
});

it('enables performance by setting tracesSampleRate to 1 and adding integration', () => {
renderWithOnboardingLayout(docs, {
selectedProducts: [
ProductSolution.ERROR_MONITORING,
ProductSolution.PERFORMANCE_MONITORING,
],
});

expect(
screen.getAllByText(textWithMarkupMatcher(/tracesSampleRate: 1\.0/)).length
).toBeGreaterThan(0);
expect(
screen.getByText(textWithMarkupMatcher(/Sentry\.reactRouterTracingIntegration\(\)/))
).toBeInTheDocument();
});

it('enables replay by setting replay sample rates', () => {
renderWithOnboardingLayout(docs, {
selectedProducts: [
ProductSolution.ERROR_MONITORING,
ProductSolution.SESSION_REPLAY,
],
});

expect(
screen.getByText(textWithMarkupMatcher(/replaysSessionSampleRate: 0\.1/))
).toBeInTheDocument();
expect(
screen.getByText(textWithMarkupMatcher(/replaysOnErrorSampleRate: 1\.0/))
).toBeInTheDocument();
});

it('enables profiling by setting profiling sample rates', () => {
renderWithOnboardingLayout(docs, {
selectedProducts: [ProductSolution.ERROR_MONITORING, ProductSolution.PROFILING],
});

expect(
screen.getByText(textWithMarkupMatcher(/profilesSampleRate: 1\.0/))
).toBeInTheDocument();
expect(
screen.getByText(textWithMarkupMatcher(/nodeProfilingIntegration\(\)/))
).toBeInTheDocument();
});

it('enables logs by setting enableLogs to true and shows logger usage in verify step', () => {
renderWithOnboardingLayout(docs, {
selectedProducts: [ProductSolution.ERROR_MONITORING, ProductSolution.LOGS],
});

expect(
screen.getAllByText(textWithMarkupMatcher(/enableLogs: true/)).length
).toBeGreaterThan(0);

// When logs are selected, verify step includes a Sentry logger call
expect(
screen.getByText(textWithMarkupMatcher(/Sentry\.logger\.info\(/))
).toBeInTheDocument();
});
});
175 changes: 104 additions & 71 deletions static/app/gettingStartedDocs/javascript/react-router.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -25,61 +25,68 @@ import {getNodeAgentMonitoringOnboarding} from 'sentry/utils/gettingStartedDocs/

type Params = DocsParams;

const getClientSetupSnippet = (params: Params) => `
import * as Sentry from "@sentry/react-router";
import { startTransition, StrictMode } from "react";
import { hydrateRoot } from "react-dom/client";
import { HydratedRouter } from "react-router/dom";
const getClientSetupSnippet = (params: Params) => {
const logsSnippet = params.isLogsSelected
? `
// Enable logs to be sent to Sentry
enableLogs: true,`
: '';

Sentry.init({
dsn: "${params.dsn.public}",
const performanceSnippet = params.isPerformanceSelected
? `
tracesSampleRate: 1.0, // Capture 100% of the transactions
// Set \`tracePropagationTargets\` to declare which URL(s) should have trace propagation enabled
tracePropagationTargets: [/^\\//, /^https:\\/\\/yourserver\\.io\\/api/],`
: '';

// Adds request headers and IP for users, for more info visit:
// https://docs.sentry.io/platforms/javascript/guides/react-router/configuration/options/#sendDefaultPii
sendDefaultPii: true,
const replaySnippet = params.isReplaySelected
? `
replaysSessionSampleRate: 0.1, // Capture 10% of all sessions
replaysOnErrorSampleRate: 1.0, // Capture 100% of sessions with an error`
: '';

integrations: [${
params.isPerformanceSelected
? `
// Performance
Sentry.reactRouterTracingIntegration(),`
: ''
}${
params.isReplaySelected
? `
const integrationsList = [];

if (params.isPerformanceSelected) {
integrationsList.push(`
// Tracing
Sentry.reactRouterTracingIntegration(),`);
}

if (params.isReplaySelected) {
integrationsList.push(`
// Session Replay
Sentry.replayIntegration(${getReplayConfigOptions(params.replayOptions)}),`
: ''
}${
params.isFeedbackSelected
? `
Sentry.replayIntegration(${getReplayConfigOptions(params.replayOptions)}),`);
}

if (params.isFeedbackSelected) {
integrationsList.push(`
// User Feedback
Sentry.feedbackIntegration({
// Additional SDK configuration goes in here, for example:
colorScheme: "system",
${getFeedbackConfigOptions(params.feedbackOptions)}
}),`
: ''
}),`);
}
],${
params.isPerformanceSelected
? `

tracesSampleRate: 1.0, // Capture 100% of the transactions

// Set \`tracePropagationTargets\` to declare which URL(s) should have trace propagation enabled
tracePropagationTargets: [/^\\//, /^https:\\/\\/yourserver\\.io\\/api/],`
: ''
}${
params.isReplaySelected
const integrationsCode =
integrationsList.length > 0
? `
integrations: [${integrationsList.join('')}
],`
: '';

// Capture Replay for 10% of all sessions,
// plus 100% of sessions with an error
replaysSessionSampleRate: 0.1,
replaysOnErrorSampleRate: 1.0,`
: ''
}
return `
import * as Sentry from "@sentry/react-router";
import { startTransition, StrictMode } from "react";
import { hydrateRoot } from "react-dom/client";
import { HydratedRouter } from "react-router/dom";

Sentry.init({
dsn: "${params.dsn.public}",
// Adds request headers and IP for users, for more info visit:
// https://docs.sentry.io/platforms/javascript/guides/react-router/configuration/options/#sendDefaultPii
sendDefaultPii: true,${integrationsCode}${logsSnippet}${performanceSnippet}${replaySnippet}
});

startTransition(() => {
Expand All @@ -90,6 +97,7 @@ startTransition(() => {
</StrictMode>
);
});`;
};

const getRootErrorBoundarySnippet = () => `
import * as Sentry from "@sentry/react-router";
Expand Down Expand Up @@ -127,37 +135,53 @@ export function ErrorBoundary({ error }: Route.ErrorBoundaryProps) {
);
}`;

const getServerSetupSnippet = (params: Params) => `
import * as Sentry from "@sentry/react-router";${
params.isProfilingSelected
const getServerSetupSnippet = (params: Params) => {
const logsSnippet = params.isLogsSelected
? `
// Enable logs to be sent to Sentry
enableLogs: true,`
: '';

const performanceSnippet = params.isPerformanceSelected
? `
tracesSampleRate: 1.0, // Capture 100% of the transactions`
: '';

const profilingSnippet = params.isProfilingSelected
? `
profilesSampleRate: 1.0, // profile every transaction`
: '';

const profilingImport = params.isProfilingSelected
? `
import { nodeProfilingIntegration } from '@sentry/profiling-node';`
: ''
}
: '';

const integrationsList = [];

if (params.isProfilingSelected) {
integrationsList.push(`
// Profiling
nodeProfilingIntegration(),`);
}

const integrationsCode =
integrationsList.length > 0
? `
integrations: [${integrationsList.join('')}
],`
: '';

return `
import * as Sentry from "@sentry/react-router";${profilingImport}

Sentry.init({
dsn: "${params.dsn.public}",

// Adds request headers and IP for users, for more info visit:
// https://docs.sentry.io/platforms/javascript/guides/react-router/configuration/options/#sendDefaultPii
sendDefaultPii: true,${
params.isProfilingSelected
? `

integrations: [nodeProfilingIntegration()],`
: ''
}${
params.isPerformanceSelected
? `
tracesSampleRate: 1.0, // Capture 100% of the transactions`
: ''
}${
params.isProfilingSelected
? `
profilesSampleRate: 1.0, // profile every transaction`
: ''
}
sendDefaultPii: true,${integrationsCode}${logsSnippet}${performanceSnippet}${profilingSnippet}
});`;
};

const getServerEntrySnippet = () => `
import * as Sentry from '@sentry/react-router';
Expand All @@ -178,21 +202,30 @@ export const handleError: HandleErrorFunction = (error, { request }) => {
// React Router may abort some interrupted requests, don't log those
if (!request.signal.aborted) {
Sentry.captureException(error);
// optionally log the error so you can see it
// optionally log the error to the console so you can see it
console.error(error);
}
};`;

const getVerifySnippet = () => `
const getVerifySnippet = (params: Params) => {
const logsCode = params.isLogsSelected
? `
// Send a log before throwing the error
Sentry.logger.info("User triggered test error", {
'action': 'test_loader_error',
});`
: '';
return `
import type { Route } from "./+types/error-page";

export async function loader() {
export async function loader() {${logsCode}
throw new Error("Sentry Test Error");
}

export default function ErrorPage() {
return <div>This page will throw an error!</div>;
}`;
};

const getPackageJsonScriptsSnippet = () => `
{
Expand Down Expand Up @@ -297,7 +330,7 @@ const onboarding: OnboardingConfig = {
},
{
description: tct(
'Initialize the Sentry React Router SDK in your [code:entry.client.tsx] file:',
'Initialize the Sentry React Router SDK in your [code:entry.client.tsx] file, above where you call [code:hydrateRoot]:',
{code: <code />}
),
title: t('Client Setup'),
Expand Down Expand Up @@ -369,7 +402,7 @@ const onboarding: OnboardingConfig = {
collapsible: true,
},
],
verify: () => [
verify: params => [
{
type: StepType.VERIFY,
description: t(
Expand All @@ -378,7 +411,7 @@ const onboarding: OnboardingConfig = {
configurations: [
{
language: 'tsx',
code: getVerifySnippet(),
code: getVerifySnippet(params),
},
],
},
Expand Down
Loading