This document serves as a complete reference for LLMs to understand how to add Sentry instrumentation to the Sentry Academy Workshop codebase. All the content listed below was removed during the workshop reset and can be re-added when needed.
The Sentry Academy Workshop is a pnpm monorepo with frontend (React + Vite) and backend (Node.js + Express) applications. Both applications were fully instrumented with Sentry for error monitoring, performance tracking, and distributed tracing.
{
"dependencies": {
"@sentry/react": "^9.27.0"
}
}{
"dependencies": {
"@sentry/node": "^9.27.0"
}
}{
"devDependencies": {
"@sentry/esbuild-plugin": "^2.x.x"
}
}import * as Sentry from "@sentry/react";
import { useEffect } from "react";
import { useLocation, useNavigationType, createRoutesFromChildren, matchRoutes } from "react-router-dom";
Sentry.init({
dsn: "https://238ed7e0b8c33a786e0a19b534bda162@o4508130833793024.ingest.us.sentry.io/4509481458204672",
sendDefaultPii: true,
integrations: [
Sentry.browserTracingIntegration(),
Sentry.replayIntegration(),
Sentry.reactRouterV7BrowserTracingIntegration({
useEffect: useEffect,
useLocation,
useNavigationType,
createRoutesFromChildren,
matchRoutes,
}),
],
_experiments: {
enableLogs: true,
},
tracesSampleRate: 1.0,
tracePropagationTargets: ["localhost:3001"],
replaysSessionSampleRate: 1.0,
replaysOnErrorSampleRate: 1.0,
});import './instrument.js';
import { createRoot } from 'react-dom/client';
import * as Sentry from '@sentry/react';
import App from './App.tsx';
import './index.css';
createRoot(document.getElementById('root')!, {
// Callback called when an error is thrown and not caught by an ErrorBoundary.
onUncaughtError: Sentry.reactErrorHandler((error, errorInfo) => {
console.warn('Uncaught error', error, errorInfo.componentStack);
}),
// Callback called when React catches an error in an ErrorBoundary.
onCaughtError: Sentry.reactErrorHandler(),
// Callback called when React automatically recovers from errors.
onRecoverableError: Sentry.reactErrorHandler(),
}).render(
<App />
);Add Sentry routing integration:
import * as Sentry from '@sentry/react';
const SentryRoutes = Sentry.withSentryReactRouterV7Routing(Routes);
// Replace <Routes> with <SentryRoutes> in the JSXimport * as Sentry from '@sentry/react';
// In useEffect:
React.useEffect(() => {
// Send route errors to Sentry
if (error) {
Sentry.captureException(error);
}
}, [error]);import * as Sentry from '@sentry/react';
const { logger } = Sentry;
// In enrollments.create:
enrollments: {
create: (courseId: string, userId: string | undefined) =>
Sentry.startSpan(
{
name: 'enrollment.create.frontend',
op: 'http.client',
attributes: {
'enrollment.course_id': courseId,
'enrollment.user_id': userId || 'undefined',
'enrollment.user_id_provided': !!userId,
},
},
() => {
logger.info(logger.fmt`Creating enrollment for course: ${courseId}, user: ${userId || 'undefined'}`);
return fetchApi<any>('/enrollments', {
method: 'POST',
body: JSON.stringify({ courseId, userId }),
});
}
),
}
// In search.courses:
search: {
courses: (query: string) =>
Sentry.startSpan(
{
name: 'search.courses.frontend',
op: 'http.client',
attributes: {
'search.query': query,
'http.url': `/search/courses?q=${encodeURIComponent(query)}`,
},
},
() => {
logger.info(logger.fmt`Searching courses with query: ${query}`);
return fetchApi<any[]>(`/search/courses?q=${encodeURIComponent(query)}`);
}
),
}import * as Sentry from '@sentry/react';
const { logger } = Sentry;
// In handleSSO function:
const handleSSO = async (provider: string) => {
try {
await Sentry.startSpan(
{
name: 'sso.authentication.frontend',
op: 'auth.sso',
attributes: {
'auth.provider': provider,
},
},
async (span) => {
const userCredentials = fetchSSOUserCredentials(provider);
logger.info(logger.fmt`Logging user ${userCredentials.email} in using ${provider}`);
span.setAttributes({
'auth.user.id': userCredentials.id,
'auth.user.email': userCredentials.email,
'auth.user.name': userCredentials.name,
'auth.user.avatar': userCredentials.avatar,
});
const loginSignature = createAuthenticationToken(userCredentials, provider);
span.setAttributes({
'auth.login_signature.defined': loginSignature !== undefined && loginSignature !== null,
});
await ssoLogin(provider, loginSignature);
}
);
} catch (err: any) {
logger.error(logger.fmt`Failed to login with ${provider} - issue with loginSignature`);
// Handle error...
}
};import * as Sentry from '@sentry/node';
Sentry.init({
dsn: 'https://3a9b93c0dffb8559153ce45a04fcbc50@o4508130833793024.ingest.us.sentry.io/4509441326120960',
_experiments: {
enableLogs: true,
},
debug: true,
tracesSampleRate: 1.0,
});import './instrument';
import * as Sentry from '@sentry/node';
// Before error handling middleware:
Sentry.setupExpressErrorHandler(app);import { sentryEsbuildPlugin } from '@sentry/esbuild-plugin';
// In esbuild.build plugins:
plugins: [
sentryEsbuildPlugin({
authToken: process.env.SENTRY_AUTH_TOKEN,
org: process.env.SENTRY_ORG,
project: process.env.SENTRY_PROJECT,
sourcemaps: {
filesToDeleteAfterUpload: ['**/*.js.map'],
},
}),
],import * as Sentry from '@sentry/node';
const { logger } = Sentry;
// In POST /enrollments route:
enrollmentRoutes.post('/enrollments', async (req, res) => {
try {
const { userId, courseId } = req.body;
await Sentry.startSpan(
{
name: 'enrollment.create.server',
op: 'enrollment.process',
attributes: {
'enrollment.course_id': courseId || 'undefined',
'enrollment.user_id': userId || 'undefined',
'enrollment.user_id_provided': !!userId,
},
},
async (span) => {
logger.info(logger.fmt`Processing enrollment request for course: ${courseId || 'undefined'}, user: ${userId || 'undefined'}`);
// Add validation and business logic with span attributes...
span.setAttributes({
'enrollment.validation.result': 'passed',
'enrollment.process.success': true,
});
}
);
} catch (error: any) {
Sentry.captureException(error, {
tags: {
operation: 'enrollment.create.backend',
course_id: req.body.courseId || 'undefined',
user_id: req.body.userId || 'undefined',
},
extra: {
requestBody: req.body,
courseId: req.body.courseId,
userId: req.body.userId,
hasUserId: !!req.body.userId,
hasCourseId: !!req.body.courseId,
},
});
}
});import * as Sentry from '@sentry/node';
const { logger } = Sentry;
// In GET /search/courses route:
searchRoutes.get('/search/courses', async (req, res) => {
try {
await Sentry.startSpan(
{
name: 'search.courses.server',
op: 'db.query',
attributes: {
'search.query': q || 'undefined',
'search.type': 'courses',
},
},
async (span) => {
logger.info(logger.fmt`Course search request for: "${q || 'undefined'}"`);
// Add search logic with span attributes...
span.setAttributes({
'search.results.count': results.length,
'search.success': true,
});
}
);
} catch (error: any) {
Sentry.captureException(error, {
tags: {
operation: 'search.courses.backend',
query: q || 'undefined',
},
extra: {
query: q,
queryParams: req.query,
},
});
}
});import * as Sentry from '@sentry/node';
const { logger } = Sentry;
// In POST /sso route:
authRoutes.post('/sso', async (req, res) => {
try {
await Sentry.startSpan(
{
name: 'auth.sso.server',
op: 'auth.verify',
attributes: {
'auth.provider': provider,
},
},
async (span) => {
logger.info(logger.fmt`SSO authentication request for provider: ${provider}`);
// Add authentication logic with span attributes...
span.setAttributes({
'auth.user.id': user.id,
'auth.success': true,
});
}
);
} catch (error: any) {
Sentry.captureException(error, {
tags: {
operation: 'auth.sso.backend',
provider: provider || 'undefined',
},
extra: {
provider,
requestBody: req.body,
},
});
}
});// TOFIX Module 2: Frontend sends 'query' parameter but backend API expects 'q'
// Backend documented API: GET /search/courses?q=searchTerm
// Frontend mistakenly sends: GET /search/courses?query=searchTerm
// Fix: Change 'query' to 'q' to match backend API contract
// TOFIX Module 3: Broken enrollments missing userId
// Let API parameter errors bubble up to Sentry while still handling in state
// Re-throw to let the error bubble up for Sentry to catch
// Do cleanup but re-throw to keep error unhandled for Sentry// TOFIX Module 1: SSO Login with missing login signature
// TOFIX Module 3: Broken enrollments missing userIdCreate .env.sentry-build-plugin for build configuration:
SENTRY_AUTH_TOKEN=your_auth_token_here
SENTRY_ORG=your_org_here
SENTRY_PROJECT=your_project_here
- Error Handling: Use
Sentry.captureException()with contextual tags and extra data - Performance Monitoring: Use
Sentry.startSpan()for tracking operations - Structured Logging: Use
Sentry.loggerwith formatted messages - React Integration: Use
Sentry.reactErrorHandler()for React error boundaries - Express Integration: Use
Sentry.setupExpressErrorHandler()for Express error handling - Router Integration: Use
Sentry.withSentryReactRouterV7Routing()for React Router
- Frontend: React 19 + Vite with browser tracing, replay, and React Router integration
- Backend: Node.js + Express with performance monitoring and profiling
- Distributed Tracing: Cross-service span propagation between frontend and backend
- Error Boundaries: React error boundary with Sentry integration
- Structured Logging: Consistent logging patterns with context
- Build Integration: Source map upload via esbuild plugin
The instrumentation supports these learning modules:
- Module 1: SSO Authentication debugging with missing login signatures
- Module 2: API contract mismatches (query parameter naming)
- Module 3: Missing required parameters in enrollment flows
Each module includes specific Sentry spans, error capture, and contextual attributes designed to help students learn debugging workflows.