8.9 KiB
8.9 KiB
Sentry Best Practices
Error Handling Patterns
1. Structured Error Handling
Use consistent error handling across the application:
import { logger } from '@/lib/logger';
import * as Sentry from '@sentry/nextjs';
export async function performOperation() {
try {
// Operation logic
const result = await someOperation();
logger.info('Operation succeeded', { result });
return result;
} catch (error) {
// Log with context
logger.error('Operation failed', {
error: error as Error,
operation: 'performOperation',
timestamp: new Date().toISOString(),
});
// Re-throw for caller to handle
throw error;
}
}
2. User-Facing vs Internal Errors
Distinguish between errors shown to users and internal errors:
class UserFacingError extends Error {
constructor(
message: string,
public userMessage: string,
public statusCode: number = 400
) {
super(message);
this.name = 'UserFacingError';
}
}
// Usage
try {
await createEntity(data);
} catch (error) {
if (error instanceof UserFacingError) {
// Show to user
return { error: error.userMessage };
}
// Internal error - log to Sentry, show generic message
logger.error('Entity creation failed', { error });
return { error: 'An unexpected error occurred' };
}
3. Add Context to Errors
Enrich errors with relevant context:
export async function updateEntity(entityId: string, data: unknown) {
// Set context for all errors in this scope
Sentry.setContext('entity', {
id: entityId,
type: 'character',
data,
});
try {
const result = await prisma.entity.update({
where: { id: entityId },
data,
});
return result;
} catch (error) {
// Error automatically includes entity context
logger.error('Entity update failed', { error, entityId });
throw error;
}
}
Performance Monitoring
1. Custom Transactions
Track important operations:
import * as Sentry from '@sentry/nextjs';
export async function generateWorld(params: WorldParams) {
const transaction = Sentry.startTransaction({
name: 'generate-world',
op: 'task',
tags: {
worldType: params.type,
complexity: params.complexity,
},
});
try {
// Track each step
const terrainSpan = transaction.startChild({
op: 'generate-terrain',
description: 'Generate terrain data',
});
const terrain = await generateTerrain(params);
terrainSpan.finish();
const biomesSpan = transaction.startChild({
op: 'generate-biomes',
description: 'Generate biome data',
});
const biomes = await generateBiomes(terrain);
biomesSpan.finish();
transaction.setStatus('ok');
return { terrain, biomes };
} catch (error) {
transaction.setStatus('internal_error');
throw error;
} finally {
transaction.finish();
}
}
2. Database Query Monitoring
Track slow queries:
import { prisma } from '@/lib/prisma';
// Prisma automatically creates spans for queries when OTel is configured
const users = await prisma.user.findMany({
where: { active: true },
include: { profile: true },
});
// Shows up in Sentry as:
// - db.query
// - duration
// - query details
3. API Call Monitoring
Track external API calls:
export async function fetchExternalData(url: string) {
return await Sentry.startSpan(
{
name: 'external-api-call',
op: 'http.client',
attributes: {
'http.url': url,
},
},
async () => {
const response = await fetch(url);
// Add response details to span
Sentry.getCurrentScope().setContext('http', {
status: response.status,
statusText: response.statusText,
});
return response.json();
}
);
}
Quota Management
1. Sample Rates
Adjust sampling to control quota usage:
Sentry.init({
// Errors
sampleRate: 1.0, // 100% of errors
// Performance monitoring
tracesSampleRate: process.env.NODE_ENV === 'production' ? 0.1 : 1.0,
// Session replay
replaysSessionSampleRate: 0.05, // 5% of normal sessions
replaysOnErrorSampleRate: 1.0, // 100% of error sessions
});
2. Filter Out Noise
Prevent known non-issues from consuming quota:
Sentry.init({
beforeSend(event, hint) {
const error = hint.originalException;
// Filter by error message
if (error && typeof error === 'object' && 'message' in error) {
const message = String(error.message);
const ignoredPatterns = [
'ResizeObserver loop',
'Non-Error promise rejection',
'Loading chunk',
'Script error.',
];
if (ignoredPatterns.some((pattern) => message.includes(pattern))) {
return null;
}
}
// Filter by URL
if (event.request?.url?.includes('localhost')) {
return null;
}
return event;
},
});
3. Inbound Filters
Configure in Sentry dashboard:
- Settings > Inbound Filters
- Filter by:
- Browser version
- Error message
- Release version
- IP address
User Context
1. Set User on Authentication
// In middleware or auth utility
import * as Sentry from '@sentry/nextjs';
import { getCurrentUser } from '@/lib/auth/utils';
export async function setUserContext() {
const user = await getCurrentUser();
if (user) {
Sentry.setUser({
id: user.id,
email: user.email,
username: user.name,
});
}
}
2. Clear User on Logout
export async function logout() {
// Clear Sentry context
Sentry.setUser(null);
logger.clearUser();
// Logout logic
await supabase.auth.signOut();
}
Breadcrumbs
Track user journey:
// Navigation
Sentry.addBreadcrumb({
category: 'navigation',
message: 'User navigated to world editor',
level: 'info',
data: { worldId: 'world-123' },
});
// User actions
Sentry.addBreadcrumb({
category: 'user.action',
message: 'Created new character',
level: 'info',
data: {
characterName: 'Hero',
worldId: 'world-123',
},
});
// Data changes
Sentry.addBreadcrumb({
category: 'data',
message: 'Updated world settings',
level: 'info',
data: {
worldId: 'world-123',
settings: { theme: 'dark' },
},
});
Source Maps
1. Verify Upload
Check source maps are uploaded:
# Build with source maps
npm run build
# Verify in Sentry dashboard
# Settings > Source Maps > [Release]
2. Configure Properly
// next.config.js
const { withSentryConfig } = require('@sentry/nextjs');
module.exports = withSentryConfig(
nextConfig,
{
// Sentry webpack plugin options
silent: true,
org: process.env.SENTRY_ORG,
project: process.env.SENTRY_PROJECT,
authToken: process.env.SENTRY_AUTH_TOKEN,
},
{
// Sentry SDK options
hideSourceMaps: true, // Don't expose source maps to public
widenClientFileUpload: true, // Upload more files for better stack traces
disableLogger: true, // Reduce noise in build logs
}
);
3. Troubleshooting
Source maps not working:
- Verify
SENTRY_AUTH_TOKENis set - Check build logs for upload errors
- Ensure release version matches between app and Sentry
Alerting
1. Configure Alert Rules
In Sentry dashboard:
- Alerts > Create Alert Rule
- Set conditions (frequency, affected users)
- Choose notification channels (email, Slack, PagerDuty)
Recommended alerts:
- High error rate (>10 errors/minute)
- New error type
- Regression (error in new release)
- Performance degradation
2. Alert Fatigue
Avoid alert fatigue:
- Use appropriate thresholds
- Filter out noisy errors
- Set up different alerts for different severity
- Use digest emails instead of immediate notifications
Release Tracking
Associate errors with releases:
Sentry.init({
release: process.env.NEXT_PUBLIC_VERCEL_GIT_COMMIT_SHA || 'dev',
environment: process.env.NODE_ENV,
});
Benefits:
- Track which release introduced errors
- Compare error rates between releases
- Verify if deploy fixed issues
Testing
1. Test Error Capture
// Test error handling
export async function testSentry() {
try {
throw new Error('Test error from Sentry setup');
} catch (error) {
logger.error('Testing Sentry integration', { error });
}
}
2. Verify in Dashboard
After testing:
- Go to Sentry dashboard
- Check Issues for test error
- Verify context, breadcrumbs, and user info
- Check source maps resolve correctly
Common Pitfalls
- Not filtering development errors - Always disable Sentry in development or filter out
- Missing source maps - Stack traces are unreadable without them
- Not setting user context - Makes debugging user-specific issues hard
- Over-sampling in production - Wastes quota and money
- Ignoring performance monitoring - Only tracking errors misses slow operations
- Not reviewing regularly - Set aside time weekly to review Sentry issues