440 lines
8.9 KiB
Markdown
440 lines
8.9 KiB
Markdown
# Sentry Best Practices
|
|
|
|
## Error Handling Patterns
|
|
|
|
### 1. Structured Error Handling
|
|
|
|
Use consistent error handling across the application:
|
|
|
|
```typescript
|
|
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:
|
|
|
|
```typescript
|
|
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:
|
|
|
|
```typescript
|
|
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:
|
|
|
|
```typescript
|
|
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:
|
|
|
|
```typescript
|
|
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:
|
|
|
|
```typescript
|
|
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:
|
|
|
|
```typescript
|
|
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:
|
|
|
|
```typescript
|
|
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
|
|
|
|
```typescript
|
|
// 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
|
|
|
|
```typescript
|
|
export async function logout() {
|
|
// Clear Sentry context
|
|
Sentry.setUser(null);
|
|
logger.clearUser();
|
|
|
|
// Logout logic
|
|
await supabase.auth.signOut();
|
|
}
|
|
```
|
|
|
|
## Breadcrumbs
|
|
|
|
Track user journey:
|
|
|
|
```typescript
|
|
// 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:
|
|
|
|
```bash
|
|
# Build with source maps
|
|
npm run build
|
|
|
|
# Verify in Sentry dashboard
|
|
# Settings > Source Maps > [Release]
|
|
```
|
|
|
|
### 2. Configure Properly
|
|
|
|
```typescript
|
|
// 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_TOKEN` is 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:
|
|
|
|
```typescript
|
|
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
|
|
|
|
```typescript
|
|
// 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:
|
|
1. Go to Sentry dashboard
|
|
2. Check Issues for test error
|
|
3. Verify context, breadcrumbs, and user info
|
|
4. Check source maps resolve correctly
|
|
|
|
## Common Pitfalls
|
|
|
|
1. **Not filtering development errors** - Always disable Sentry in development or filter out
|
|
2. **Missing source maps** - Stack traces are unreadable without them
|
|
3. **Not setting user context** - Makes debugging user-specific issues hard
|
|
4. **Over-sampling in production** - Wastes quota and money
|
|
5. **Ignoring performance monitoring** - Only tracking errors misses slow operations
|
|
6. **Not reviewing regularly** - Set aside time weekly to review Sentry issues
|