Skip to content

setup-crash-reporting

Add crash reporting and analytics with Sentry, Bugsnag, or Firebase Crashlytics.

Overview

The setup-crash-reporting command integrates crash reporting and error tracking into your React Native app, providing real-time crash reports, error grouping, and performance monitoring.

Usage

bash
/template setup-crash-reporting <provider> [options]

Parameters

  • <provider> - Crash reporting service: sentry, bugsnag, crashlytics

Options

  • --performance - Enable performance monitoring
  • --breadcrumbs - Track user interactions
  • --source-maps - Upload source maps
  • --native - Include native crash reporting

Examples

Basic Sentry Setup

bash
/template setup-crash-reporting sentry

Full Sentry with Performance

bash
/template setup-crash-reporting sentry --performance --breadcrumbs --source-maps

Firebase Crashlytics

bash
/template setup-crash-reporting crashlytics --native

What It Creates

Crash Reporting Structure

src/
├── services/
│   ├── crashReporting/
│   │   ├── config.ts          # Provider configuration
│   │   ├── ErrorBoundary.tsx  # React error boundary
│   │   ├── native.ts          # Native crash setup
│   │   ├── breadcrumbs.ts     # User tracking
│   │   ├── performance.ts     # Performance monitoring
│   │   └── utils.ts           # Helper functions
│   └── CrashReportingService.ts

Sentry Configuration

typescript
// src/services/CrashReportingService.ts
import * as Sentry from '@sentry/react-native';

export class CrashReportingService {
  static initialize() {
    Sentry.init({
      dsn: process.env.SENTRY_DSN,
      debug: __DEV__,
      environment: __DEV__ ? 'development' : 'production',
      
      // Performance monitoring
      tracesSampleRate: __DEV__ ? 1.0 : 0.1,
      
      // Release tracking
      release: `${packageJson.name}@${packageJson.version}`,
      dist: Platform.OS,
      
      // Integrations
      integrations: [
        new Sentry.ReactNativeTracing({
          routingInstrumentation: new Sentry.ReactNavigationInstrumentation(
            navigation,
            {
              routeChangeTimeoutMs: 500,
            }
          ),
          tracingOrigins: ['localhost', /^https:\/\/api\.myapp\.com/],
        }),
      ],
      
      // Filters
      beforeSend: (event, hint) => {
        // Filter out known issues
        if (event.exception?.values?.[0]?.value?.includes('Network request failed')) {
          return null;
        }
        
        // Add user context
        event.user = {
          id: getUserId(),
          email: getUserEmail(),
        };
        
        return event;
      },
      
      // Breadcrumbs
      beforeBreadcrumb: (breadcrumb) => {
        // Filter sensitive data
        if (breadcrumb.category === 'console' && breadcrumb.level === 'debug') {
          return null;
        }
        
        return breadcrumb;
      },
    });
  }
  
  static captureException(error: Error, context?: any) {
    Sentry.captureException(error, {
      contexts: {
        custom: context,
      },
    });
  }
  
  static captureMessage(message: string, level: Sentry.SeverityLevel = 'info') {
    Sentry.captureMessage(message, level);
  }
  
  static setUser(user: { id: string; email?: string; username?: string }) {
    Sentry.setUser(user);
  }
  
  static addBreadcrumb(breadcrumb: Sentry.Breadcrumb) {
    Sentry.addBreadcrumb(breadcrumb);
  }
}

Error Boundary

typescript
// src/services/crashReporting/ErrorBoundary.tsx
import * as Sentry from '@sentry/react-native';

interface Props {
  children: React.ReactNode;
  fallback?: React.ComponentType<{ error: Error; resetError: () => void }>;
}

interface State {
  hasError: boolean;
  error: Error | null;
}

export class ErrorBoundary extends React.Component<Props, State> {
  state: State = {
    hasError: false,
    error: null,
  };
  
  static getDerivedStateFromError(error: Error): State {
    return {
      hasError: true,
      error,
    };
  }
  
  componentDidCatch(error: Error, errorInfo: React.ErrorInfo) {
    // Log to crash reporting
    Sentry.withScope((scope) => {
      scope.setContext('errorInfo', errorInfo);
      Sentry.captureException(error);
    });
    
    // Log to console in development
    if (__DEV__) {
      console.error('Error caught by boundary:', error, errorInfo);
    }
  }
  
  resetError = () => {
    this.setState({ hasError: false, error: null });
  };
  
  render() {
    if (this.state.hasError && this.state.error) {
      const FallbackComponent = this.props.fallback || DefaultErrorFallback;
      
      return (
        <FallbackComponent
          error={this.state.error}
          resetError={this.resetError}
        />
      );
    }
    
    return this.props.children;
  }
}

// Default fallback UI
function DefaultErrorFallback({ error, resetError }: {
  error: Error;
  resetError: () => void;
}) {
  return (
    <View style={styles.container}>
      <Text style={styles.title}>Something went wrong</Text>
      <Text style={styles.message}>{error.message}</Text>
      <Button title="Try Again" onPress={resetError} />
    </View>
  );
}

Performance Monitoring

typescript
// src/services/crashReporting/performance.ts
export class PerformanceMonitoring {
  static startTransaction(name: string, op: string = 'navigation') {
    return Sentry.startTransaction({ name, op });
  }
  
  static measureScreenLoad(screenName: string) {
    const transaction = this.startTransaction(screenName, 'navigation');
    
    return {
      finish: () => {
        transaction.finish();
      },
      
      setData: (key: string, value: any) => {
        transaction.setData(key, value);
      },
      
      setMeasurement: (name: string, value: number, unit: string = 'millisecond') => {
        transaction.setMeasurement(name, value, unit);
      },
    };
  }
  
  static measureApiCall(url: string) {
    const transaction = this.startTransaction(url, 'http.client');
    const startTime = Date.now();
    
    return {
      setStatus: (status: number) => {
        transaction.setHttpStatus(status);
      },
      
      finish: () => {
        const duration = Date.now() - startTime;
        transaction.setMeasurement('duration', duration);
        transaction.finish();
      },
    };
  }
  
  static trackAppStartup() {
    const span = Sentry.startTransaction({
      name: 'app.startup',
      op: 'app.startup',
    });
    
    // Track cold start
    span.setMeasurement('cold_start', true);
    
    // Track time to interactive
    InteractionManager.runAfterInteractions(() => {
      span.finish();
    });
  }
}
typescript
// src/services/crashReporting/breadcrumbs.ts
export function setupBreadcrumbTracking() {
  // Navigation breadcrumbs
  navigation.addListener('state', (e) => {
    const route = navigation.getCurrentRoute();
    
    Sentry.addBreadcrumb({
      category: 'navigation',
      message: `Navigated to ${route?.name}`,
      level: 'info',
      data: route?.params,
    });
  });
  
  // Touch breadcrumbs
  const originalTouchableOpacity = TouchableOpacity.prototype.render;
  TouchableOpacity.prototype.render = function() {
    const onPress = this.props.onPress;
    
    this.props.onPress = (...args) => {
      Sentry.addBreadcrumb({
        category: 'touch',
        message: `Touch on ${this.props.testID || 'TouchableOpacity'}`,
        level: 'info',
      });
      
      onPress?.(...args);
    };
    
    return originalTouchableOpacity.call(this);
  };
  
  // API breadcrumbs
  const originalFetch = global.fetch;
  global.fetch = async (...args) => {
    const [url, options] = args;
    
    Sentry.addBreadcrumb({
      category: 'fetch',
      message: `${options?.method || 'GET'} ${url}`,
      level: 'info',
    });
    
    try {
      const response = await originalFetch(...args);
      
      if (!response.ok) {
        Sentry.addBreadcrumb({
          category: 'fetch',
          message: `Failed: ${response.status} ${url}`,
          level: 'error',
        });
      }
      
      return response;
    } catch (error) {
      Sentry.addBreadcrumb({
        category: 'fetch',
        message: `Error: ${error.message} ${url}`,
        level: 'error',
      });
      throw error;
    }
  };
}

Platform Configuration

iOS Setup

Sentry

ruby
# ios/Podfile
pod 'RNSentry', :path => '../node_modules/@sentry/react-native'

# Upload dSYMs automatically
post_install do |installer|
  installer.pods_project.targets.each do |target|
    target.build_configurations.each do |config|
      config.build_settings['DEBUG_INFORMATION_FORMAT'] = 'dwarf-with-dsym'
    end
  end
end

Build Phase Script

bash
# Upload debug symbols
export SENTRY_PROPERTIES=sentry.properties
../node_modules/@sentry/cli/bin/sentry-cli upload-dif "$DWARF_DSYM_FOLDER_PATH"

Android Setup

Gradle Configuration

gradle
// android/app/build.gradle
apply plugin: 'io.sentry.android.gradle'

sentry {
    uploadNativeSymbols = true
    includeNativeSources = true
    autoUploadProguardMapping = true
}

ProGuard Rules

# android/app/proguard-rules.pro
-keepattributes SourceFile,LineNumberTable
-keep public class * extends java.lang.Exception

Source Maps

Upload Source Maps

json
// package.json
{
  "scripts": {
    "postbuild:ios": "sentry-cli releases files <release> upload-sourcemaps --dist ios ./ios/build",
    "postbuild:android": "sentry-cli releases files <release> upload-sourcemaps --dist android ./android/app/build"
  }
}

Sentry CLI Configuration

ini
# sentry.properties
defaults.url=https://sentry.io/
defaults.org=my-org
defaults.project=my-app
auth.token=<auth-token>

Custom Error Handling

API Error Tracking

typescript
export async function apiCall(url: string, options?: RequestInit) {
  const transaction = PerformanceMonitoring.measureApiCall(url);
  
  try {
    const response = await fetch(url, options);
    transaction.setStatus(response.status);
    
    if (!response.ok) {
      const error = new ApiError(response.status, await response.text());
      CrashReportingService.captureException(error, {
        url,
        method: options?.method || 'GET',
        status: response.status,
      });
      throw error;
    }
    
    return response.json();
  } catch (error) {
    CrashReportingService.captureException(error, { url });
    throw error;
  } finally {
    transaction.finish();
  }
}

Redux Error Tracking

typescript
// Redux middleware
const crashReportingMiddleware: Middleware = (store) => (next) => (action) => {
  try {
    // Add breadcrumb for action
    Sentry.addBreadcrumb({
      category: 'redux',
      message: action.type,
      data: action.payload,
      level: 'info',
    });
    
    return next(action);
  } catch (error) {
    Sentry.captureException(error, {
      contexts: {
        redux: {
          action,
          state: store.getState(),
        },
      },
    });
    throw error;
  }
};

Testing

Test Crash Reporting

typescript
export function TestCrashReporting() {
  return (
    <View>
      <Button
        title="Test Crash"
        onPress={() => {
          throw new Error('Test crash');
        }}
      />
      
      <Button
        title="Test Native Crash"
        onPress={() => {
          Sentry.nativeCrash();
        }}
      />
      
      <Button
        title="Test Handled Error"
        onPress={() => {
          CrashReportingService.captureException(
            new Error('Test handled error'),
            { context: 'test' }
          );
        }}
      />
    </View>
  );
}

Unit Tests

typescript
describe('CrashReporting', () => {
  it('captures exceptions with context', () => {
    const error = new Error('Test error');
    const context = { userId: '123' };
    
    CrashReportingService.captureException(error, context);
    
    expect(Sentry.captureException).toHaveBeenCalledWith(error, {
      contexts: { custom: context },
    });
  });
  
  it('filters sensitive breadcrumbs', () => {
    const breadcrumb = {
      category: 'console',
      level: 'debug',
      message: 'password: 12345',
    };
    
    const result = beforeBreadcrumb(breadcrumb);
    expect(result).toBeNull();
  });
});

Best Practices

  1. User Privacy: Never log PII without consent
  2. Error Grouping: Use fingerprinting for similar errors
  3. Performance: Sample performance monitoring in production
  4. Testing: Test crash reporting in release builds
  5. Monitoring: Set up alerts for error spikes

Built with ❤️ for the AI Coding community, by Praney Behl