Files
gh-hopeoverture-worldbuildi…/skills/sentry-and-otel-setup/references/sentry-best-practices.md
2025-11-29 18:46:44 +08:00

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_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:

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:

  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