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 sentryFull Sentry with Performance
bash
/template setup-crash-reporting sentry --performance --breadcrumbs --source-mapsFirebase Crashlytics
bash
/template setup-crash-reporting crashlytics --nativeWhat 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.tsSentry 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();
});
}
}Breadcrumb Tracking
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
endBuild 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.ExceptionSource 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
- User Privacy: Never log PII without consent
- Error Grouping: Use fingerprinting for similar errors
- Performance: Sample performance monitoring in production
- Testing: Test crash reporting in release builds
- Monitoring: Set up alerts for error spikes
Related Commands
setup-push-notifications- Track notification errorsadd-offline-sync- Handle offline errorsoptimize-bundle-size- Include in performance monitoring
