Sentry for Production: Error Monitoring and Performance Tracking
Master Sentry for production applications: error tracking, performance monitoring, distributed tracing, and alerting strategies that catch issues before users do.
Sentry for Production: Error Monitoring and Performance Tracking
Sentry is the industry standard for error tracking and performance monitoring. Here's how to instrument your application to catch issues before they impact users.
Why Sentry?
Traditional logging:
try { await processPayment(orderId); } catch (error) { console.error('Payment failed:', error); // Error buried in logs, no alert, no context }
Sentry:
- Aggregates duplicate errors
- Shows stack traces with source maps
- Captures user context and breadcrumbs
- Alerts when new issues appear
- Tracks performance metrics
- Integrates with distributed tracing
Result: Find and fix errors 10x faster.
Setting Up Sentry
1. Installation
// npm install @sentry/nextjs import * as Sentry from '@sentry/nextjs'; Sentry.init({ dsn: process.env.NEXT_PUBLIC_SENTRY_DSN, environment: process.env.NODE_ENV, // Release tracking release: process.env.VERCEL_GIT_COMMIT_SHA || 'dev', // Sample rate for errors sampleRate: 1.0, // 100% of errors // Performance monitoring tracesSampleRate: 0.1, // 10% of transactions // Ignore common noise ignoreErrors: [ 'ResizeObserver loop limit exceeded', 'Non-Error promise rejection captured', ], beforeSend(event, hint) { // Don't send certain errors to Sentry if (event.exception?.values?.[0]?.value?.includes('Network request failed')) { return null; // Drop the event } return event; }, });
2. Source Maps (Critical!)
Without source maps, stack traces show minified code:
at e (chunk-abc123.js:1:4567)
at n (chunk-abc123.js:1:8901)
With source maps:
at processPayment (src/lib/payments.ts:45:12)
at handleCheckout (src/app/checkout/page.tsx:89:5)
Next.js configuration:
// next.config.js module.exports = { sentry: { hideSourceMaps: true, // Don't expose source maps to users widenClientFileUpload: true, // Upload all source maps }, };
For other frameworks:
# Upload source maps after build npx @sentry/cli releases files $RELEASE upload-sourcemaps ./dist
Error Tracking Best Practices
1. Add Context to Errors
import * as Sentry from '@sentry/nextjs'; async function processPayment(userId: string, amount: number) { // Set user context Sentry.setUser({ id: userId, email: user.email, username: user.name, }); // Set custom context Sentry.setContext('payment', { amount, currency: 'USD', gateway: 'stripe', }); // Add tags for filtering Sentry.setTag('payment_type', 'subscription'); Sentry.setTag('plan', user.plan); try { const charge = await stripe.charges.create({ amount: amount * 100, currency: 'usd', customer: userId, }); return charge; } catch (error) { // Capture with additional context Sentry.captureException(error, { level: 'error', fingerprint: ['payment-failure', userId], contexts: { stripe: { charge_id: error.charge?.id, decline_code: error.decline_code, }, }, }); throw error; } }
2. Breadcrumbs: What Led to the Error
Breadcrumbs show the trail of events before an error:
// Automatically captured by Sentry: // - HTTP requests // - Console logs // - Navigation events // - User clicks // Manually add custom breadcrumbs Sentry.addBreadcrumb({ category: 'payment', message: 'User initiated checkout', level: 'info', data: { cart_total: 99.99, items: 3, }, }); Sentry.addBreadcrumb({ category: 'payment', message: 'Payment method validated', level: 'info', }); // Later, if an error occurs, Sentry shows: // 1. User initiated checkout (cart_total: 99.99) // 2. Payment method validated // 3. Error: Stripe card_declined
3. Issue Grouping
Sentry groups similar errors. Control grouping with fingerprints:
// Default: Groups by stack trace Sentry.captureException(new Error('Database timeout')); // Custom grouping Sentry.captureException(error, { fingerprint: [ 'database-timeout', error.query, // Group by query type ], }); // Example: Group all Stripe errors together if (error.type === 'StripeCardError') { Sentry.captureException(error, { fingerprint: ['stripe-card-error'], }); }
Performance Monitoring
Track slow operations:
import * as Sentry from '@sentry/nextjs'; async function fetchUserData(userId: string) { // Start a transaction const transaction = Sentry.startTransaction({ name: 'fetchUserData', op: 'http.server', }); // Add to current scope Sentry.getCurrentHub().configureScope(scope => { scope.setSpan(transaction); }); try { // Child span: Database query const dbSpan = transaction.startChild({ op: 'db.query', description: 'SELECT * FROM users WHERE id = ?', }); const user = await db.query('SELECT * FROM users WHERE id = ?', [userId]); dbSpan.finish(); // Another child span: API call const apiSpan = transaction.startChild({ op: 'http.client', description: 'GET https://api.example.com/profile', }); const profile = await fetch(`https://api.example.com/profile/${userId}`); apiSpan.setHttpStatus(profile.status); apiSpan.finish(); return { user, profile: await profile.json() }; } catch (error) { transaction.setStatus('internal_error'); Sentry.captureException(error); throw error; } finally { transaction.finish(); } }
Result in Sentry: Waterfall view showing:
fetchUserData (450ms)
├─ db.query (200ms)
└─ http.client (240ms)
Auto-Instrumentation
For common frameworks, Sentry auto-instruments:
import * as Sentry from '@sentry/nextjs'; Sentry.init({ dsn: '...', integrations: [ // Auto-instrument database queries new Sentry.Integrations.Prisma({ client: prisma }), // Auto-instrument HTTP requests new Sentry.Integrations.Http({ tracing: true }), ], tracesSampleRate: 0.1, // Track 10% of requests });
Distributed Tracing
Connect frontend and backend errors:
Frontend (Next.js):
// Sentry automatically injects trace headers const response = await fetch('/api/checkout', { method: 'POST', body: JSON.stringify({ cart }), });
Backend (API route):
// app/api/checkout/route.ts import * as Sentry from '@sentry/nextjs'; export async function POST(request: Request) { // Sentry extracts trace from headers automatically const transaction = Sentry.getCurrentHub().getScope()?.getTransaction(); try { const { cart } = await request.json(); // This span is linked to frontend transaction const span = transaction?.startChild({ op: 'process_checkout', description: 'Process checkout', }); await processCheckout(cart); span?.finish(); return Response.json({ success: true }); } catch (error) { Sentry.captureException(error); return Response.json({ error: error.message }, { status: 500 }); } }
Result: See the full request path:
Frontend Click
└─ API Request (200ms)
└─ Backend Processing (150ms)
├─ Database Query (50ms)
└─ External API Call (80ms)
Alerting Strategies
1. Issue Alerts
# Alert when new issue appears alert: conditions: - first_seen actions: - notify: slack channel: "#alerts" - notify: pagerduty service: "production" # Alert on regression (issue reappears after being resolved) alert: conditions: - regression actions: - notify: email users: ["[email protected]"] # Alert on spike alert: conditions: - event_frequency interval: 1h value: 100 actions: - notify: slack
2. Metric Alerts
# Alert on high error rate alert: metric: "error_rate" threshold: 5 # 5% error rate window: 5m actions: - notify: pagerduty # Alert on slow transactions alert: metric: "p95(transaction.duration)" threshold: 2000 # 2 seconds window: 10m actions: - notify: slack
Release Tracking
Track which releases introduce bugs:
// Track releases Sentry.init({ release: process.env.VERCEL_GIT_COMMIT_SHA, environment: process.env.NODE_ENV, });
Deploy hooks:
# After deployment, notify Sentry sentry-cli releases new $RELEASE sentry-cli releases set-commits $RELEASE --auto sentry-cli releases finalize $RELEASE # Mark as deployed sentry-cli releases deploys $RELEASE new --env production
Benefits:
- See which release introduced each error
- Track error rate by release
- Get alerts when new release has high error rate
- Automatic suspect commits
Filtering Noise
1. Ignore Known Errors
Sentry.init({ ignoreErrors: [ // Browser extensions 'top.GLOBALS', // Third-party scripts 'fb_xd_fragment', // Browsers 'ResizeObserver loop limit exceeded', ], denyUrls: [ // Ignore errors from browser extensions /extensions\//i, /^chrome:\/\//i, /^moz-extension:\/\//i, ], });
2. beforeSend Hook
Sentry.init({ beforeSend(event, hint) { // Don't send certain errors if (event.exception?.values) { const error = event.exception.values[0]; // Ignore network errors (user's internet, not our bug) if (error.type === 'NetworkError') { return null; } // Ignore specific error messages if (error.value?.includes('Script error')) { return null; } } // Scrub sensitive data if (event.request?.data) { delete event.request.data.password; delete event.request.data.creditCard; } return event; }, });
Privacy and Compliance
1. Scrub PII
Sentry.init({ beforeSend(event) { // Remove user email from all events if (event.user) { delete event.user.email; delete event.user.ip_address; } return event; }, });
2. Data Scrubbing Rules
In Sentry dashboard:
- Settings → Security & Privacy → Data Scrubbing
- Add rules to scrub: passwords, credit cards, SSNs, etc.
3. GDPR Compliance
// Allow users to opt out if (userPreferences.disableErrorTracking) { Sentry.close(); // Stop sending events } // Delete user data via Sentry API await fetch('https://sentry.io/api/0/users/USER_ID/', { method: 'DELETE', headers: { Authorization: `Bearer ${SENTRY_TOKEN}` }, });
Cost Optimization
Sentry charges per event. Optimize:
1. Sampling
Sentry.init({ // Sample errors (still get 100% in most cases) sampleRate: 1.0, // Sample performance transactions tracesSampleRate: 0.1, // 10% of requests // Sample by transaction name tracesSampler: (samplingContext) => { // Always sample checkout if (samplingContext.transactionContext.name.includes('checkout')) { return 1.0; } // Sample health checks rarely if (samplingContext.transactionContext.name.includes('health')) { return 0.01; } // Default: 10% return 0.1; }, });
2. Rate Limiting
// Limit errors per issue const errorCounts = new Map(); Sentry.init({ beforeSend(event) { const issueId = event.fingerprint?.join('-'); // Track events per issue in memory (or Redis) const count = errorCounts.get(issueId) || 0; // Max 10 events per issue per hour if (count > 10) { return null; // Drop event } errorCounts.set(issueId, count + 1); return event; }, });
Best Practices
- Set user context - Know which users are affected
- Use breadcrumbs - Understand the journey to the error
- Tag everything - Filter by feature, plan, region
- Sample transactions - 10% is usually enough
- Upload source maps - Readable stack traces are critical
- Set up alerts - Respond to new issues immediately
- Track releases - Know which deploy broke things
- Scrub PII - Protect user privacy
Sentry vs Alternatives
Sentry:
- Error tracking: Excellent (best-in-class)
- Performance monitoring: Very good
- Pricing: Moderate ($$)
- Ease of use: Excellent
- Source maps: Excellent
Datadog:
- Error tracking: Good
- Performance monitoring: Excellent (full APM suite)
- Pricing: Premium ($$$$)
- Ease of use: Good (steeper learning curve)
- Source maps: Good
New Relic:
- Error tracking: Very good
- Performance monitoring: Excellent
- Pricing: High ($$$)
- Ease of use: Good
- Source maps: Very good
Verdict: Sentry is the best dedicated error tracker. Use Datadog/New Relic if you need full observability stack.
Conclusion
Sentry transforms error handling from "hope users report bugs" to "know about issues before users do."
Key benefits:
- Aggregate and deduplicate errors
- Rich context (user, breadcrumbs, tags)
- Performance monitoring
- Distributed tracing across services
- Release tracking and suspect commits
- Smart alerting
For production apps, Sentry is essential.
Need help setting up comprehensive monitoring? Let's talk about your observability strategy.
You might also like
Production Observability: OpenTelemetry and Distributed Tracing
Implement comprehensive observability with OpenTelemetry: distributed tracing, metrics, and logs in a unified pipeline for production systems.
Incident Response at Scale: From Alert to Resolution
Build resilient systems with effective incident response: on-call best practices, runbooks, blameless postmortems, and SLO-driven reliability.
AWS ECS Production Deployment: The Complete Guide
Deploy containerized applications on AWS ECS with auto-scaling, blue/green deployments, and production-grade monitoring.