# Crash Monitoring
## Bugsnag Setup
### Installation
```bash
yarn add @bugsnag/react-native
yarn add @bugsnag/react-native-cli
```
### Configuration
**bugsnag.config.js:**
```javascript
import Bugsnag from '@bugsnag/react-native';
import Config from 'react-native-config';
Bugsnag.start({
apiKey: Config.BUGSNAG_API_KEY,
appVersion: Config.APP_VERSION,
releaseStage: Config.APP_ENV,
enabledReleaseStages: ['production', 'staging'],
autoDetectErrors: true,
autoCaptureSessions: true,
enabledErrorTypes: {
unhandledExceptions: true,
unhandledRejections: true,
nativeCrashes: true,
},
onError: (event) => {
// Add custom metadata
event.addMetadata('app', {
buildNumber: Config.BUILD_NUMBER,
environment: Config.APP_ENV,
});
// Filter sensitive data
event.request.url = event.request.url?.replace(/token=[^&]+/, 'token=***');
return true;
},
onSession: (session) => {
// Add user context
session.user = {
id: 'user-id',
name: 'User Name',
email: 'user@example.com',
};
return true;
},
});
export default Bugsnag;
```
### Integration
**App.tsx:**
```typescript
import React from 'react';
import Bugsnag from './src/config/bugsnag.config';
import {AppNavigator} from './src/navigation/AppNavigator';
const App: React.FC = () => {
React.useEffect(() => {
// Set user context when available
Bugsnag.setUser('user-123', 'john@example.com', 'John Doe');
// Leave breadcrumb for app start
Bugsnag.leaveBreadcrumb('App started');
}, []);
return ;
};
export default App;
```
## Firebase Crashlytics
### Installation
```bash
yarn add @react-native-firebase/app
yarn add @react-native-firebase/crashlytics
```
### Configuration
**firebase.config.js:**
```typescript
import crashlytics from '@react-native-firebase/crashlytics';
import Config from 'react-native-config';
class CrashlyticsService {
static initialize() {
if (__DEV__) {
// Disable crashlytics in development
crashlytics().setCrashlyticsCollectionEnabled(false);
} else {
crashlytics().setCrashlyticsCollectionEnabled(true);
}
}
static setUserId(userId: string) {
crashlytics().setUserId(userId);
}
static setUserAttributes(attributes: Record) {
Object.entries(attributes).forEach(([key, value]) => {
crashlytics().setAttribute(key, value);
});
}
static logError(error: Error, context?: Record) {
if (context) {
Object.entries(context).forEach(([key, value]) => {
crashlytics().setAttribute(key, String(value));
});
}
crashlytics().recordError(error);
}
static log(message: string) {
crashlytics().log(message);
}
static crash() {
crashlytics().crash();
}
}
export default CrashlyticsService;
```
## Error Boundary
### React Error Boundary
```typescript
// src/components/ErrorBoundary/ErrorBoundary.tsx
import React, {Component, ErrorInfo, ReactNode} from 'react';
import {View, Text, TouchableOpacity, StyleSheet} from 'react-native';
import Bugsnag from '@config/bugsnag.config';
import CrashlyticsService from '@config/firebase.config';
interface Props {
children: ReactNode;
fallback?: ReactNode;
}
interface State {
hasError: boolean;
error?: Error;
}
export class ErrorBoundary extends Component {
constructor(props: Props) {
super(props);
this.state = {hasError: false};
}
static getDerivedStateFromError(error: Error): State {
return {hasError: true, error};
}
componentDidCatch(error: Error, errorInfo: ErrorInfo) {
console.error('ErrorBoundary caught an error:', error, errorInfo);
// Log to crash reporting services
Bugsnag.notify(error, (event) => {
event.addMetadata('errorBoundary', {
componentStack: errorInfo.componentStack,
errorBoundary: true,
});
});
CrashlyticsService.logError(error, {
componentStack: errorInfo.componentStack,
errorBoundary: 'true',
});
}
handleRetry = () => {
this.setState({hasError: false, error: undefined});
};
render() {
if (this.state.hasError) {
if (this.props.fallback) {
return this.props.fallback;
}
return (
Oops! Something went wrong
We're sorry for the inconvenience. The error has been reported and we're working on a fix.
Try Again
);
}
return this.props.children;
}
}
const styles = StyleSheet.create({
container: {
flex: 1,
justifyContent: 'center',
alignItems: 'center',
padding: 20,
backgroundColor: '#f5f5f5',
},
title: {
fontSize: 24,
fontWeight: 'bold',
marginBottom: 16,
textAlign: 'center',
color: '#333',
},
message: {
fontSize: 16,
textAlign: 'center',
marginBottom: 24,
color: '#666',
lineHeight: 24,
},
button: {
backgroundColor: '#007AFF',
paddingHorizontal: 24,
paddingVertical: 12,
borderRadius: 8,
},
buttonText: {
color: '#FFFFFF',
fontSize: 16,
fontWeight: '600',
},
});
```
## Global Error Handler
### Unhandled Promise Rejections
```typescript
// src/utils/errorHandler.ts
import Bugsnag from '@config/bugsnag.config';
import CrashlyticsService from '@config/firebase.config';
export class GlobalErrorHandler {
static initialize() {
// Handle unhandled promise rejections
const originalHandler = global.Promise.prototype.catch;
global.Promise.prototype.catch = function(onRejected) {
return originalHandler.call(this, (error) => {
GlobalErrorHandler.handleError(error, 'unhandledRejection');
if (onRejected) {
return onRejected(error);
}
throw error;
});
};
// Handle uncaught exceptions
if (global.ErrorUtils) {
const originalGlobalHandler = global.ErrorUtils.getGlobalHandler();
global.ErrorUtils.setGlobalHandler((error, isFatal) => {
GlobalErrorHandler.handleError(error, isFatal ? 'fatal' : 'nonfatal');
originalGlobalHandler(error, isFatal);
});
}
}
static handleError(error: Error, type: string) {
console.error(`${type} error:`, error);
// Log to crash reporting services
Bugsnag.notify(error, (event) => {
event.addMetadata('error', {
type,
timestamp: new Date().toISOString(),
});
});
CrashlyticsService.logError(error, {
errorType: type,
timestamp: new Date().toISOString(),
});
}
static logError(error: Error, context?: Record) {
this.handleError(error, 'manual');
if (context) {
Bugsnag.addMetadata('context', context);
CrashlyticsService.setUserAttributes(
Object.fromEntries(
Object.entries(context).map(([k, v]) => [k, String(v)])
)
);
}
}
}
```
## Network Error Monitoring
### API Error Tracking
```typescript
// src/services/errorTrackingAPI.ts
import {AxiosError, AxiosResponse} from 'axios';
import Bugsnag from '@config/bugsnag.config';
import CrashlyticsService from '@config/firebase.config';
export class APIErrorTracker {
static trackRequest(config: any) {
Bugsnag.leaveBreadcrumb(`API Request: ${config.method?.toUpperCase()} ${config.url}`);
CrashlyticsService.log(`API Request: ${config.method?.toUpperCase()} ${config.url}`);
}
static trackResponse(response: AxiosResponse) {
const {status, config} = response;
Bugsnag.leaveBreadcrumb(`API Response: ${status} ${config.url}`);
CrashlyticsService.log(`API Response: ${status} ${config.url}`);
}
static trackError(error: AxiosError) {
const {response, config, message} = error;
const errorData = {
url: config?.url,
method: config?.method,
status: response?.status,
statusText: response?.statusText,
message,
responseData: response?.data,
};
// Log to crash reporting
Bugsnag.notify(error, (event) => {
event.addMetadata('apiError', errorData);
event.severity = response?.status && response.status >= 500 ? 'error' : 'warning';
});
CrashlyticsService.logError(error, {
apiUrl: config?.url || 'unknown',
apiMethod: config?.method || 'unknown',
apiStatus: String(response?.status || 0),
});
console.error('API Error:', errorData);
}
}
```
## Performance Monitoring
### Performance Metrics
```typescript
// src/utils/performanceMonitor.ts
import Bugsnag from '@config/bugsnag.config';
import CrashlyticsService from '@config/firebase.config';
export class PerformanceMonitor {
private static metrics: Map = new Map();
static startTiming(label: string) {
this.metrics.set(label, Date.now());
CrashlyticsService.log(`Performance: Started ${label}`);
}
static endTiming(label: string): number {
const startTime = this.metrics.get(label);
if (!startTime) {
console.warn(`No start time found for ${label}`);
return 0;
}
const duration = Date.now() - startTime;
this.metrics.delete(label);
// Log performance metrics
Bugsnag.leaveBreadcrumb(`Performance: ${label} took ${duration}ms`);
CrashlyticsService.log(`Performance: ${label} took ${duration}ms`);
// Alert on slow operations
if (duration > 5000) {
Bugsnag.notify(new Error(`Slow operation: ${label}`), (event) => {
event.severity = 'warning';
event.addMetadata('performance', {
operation: label,
duration,
threshold: 5000,
});
});
}
return duration;
}
static measureAsync(label: string, fn: () => Promise): Promise {
this.startTiming(label);
return fn().finally(() => {
this.endTiming(label);
});
}
}
```
## Custom Crash Reports
### User Feedback Integration
```typescript
// src/components/FeedbackModal/FeedbackModal.tsx
import React, {useState} from 'react';
import {Modal, View, Text, TextInput, TouchableOpacity, StyleSheet} from 'react-native';
import Bugsnag from '@config/bugsnag.config';
interface FeedbackModalProps {
visible: boolean;
onClose: () => void;
error?: Error;
}
export const FeedbackModal: React.FC = ({
visible,
onClose,
error,
}) => {
const [feedback, setFeedback] = useState('');
const [email, setEmail] = useState('');
const handleSubmit = () => {
if (error) {
Bugsnag.notify(error, (event) => {
event.addMetadata('userFeedback', {
feedback,
email,
timestamp: new Date().toISOString(),
});
});
} else {
Bugsnag.leaveBreadcrumb('User feedback submitted', {
feedback,
email,
});
}
setFeedback('');
setEmail('');
onClose();
};
return (
Help us improve
Tell us what happened so we can fix the issue
Cancel
Send Feedback
);
};
const styles = StyleSheet.create({
overlay: {
flex: 1,
backgroundColor: 'rgba(0, 0, 0, 0.5)',
justifyContent: 'center',
alignItems: 'center',
},
container: {
backgroundColor: '#FFFFFF',
borderRadius: 12,
padding: 20,
width: '90%',
maxWidth: 400,
},
title: {
fontSize: 18,
fontWeight: '600',
marginBottom: 8,
textAlign: 'center',
},
subtitle: {
fontSize: 14,
color: '#666',
textAlign: 'center',
marginBottom: 20,
},
input: {
borderWidth: 1,
borderColor: '#E5E5E7',
borderRadius: 8,
paddingHorizontal: 12,
paddingVertical: 10,
marginBottom: 12,
fontSize: 16,
},
textArea: {
height: 80,
textAlignVertical: 'top',
},
buttonContainer: {
flexDirection: 'row',
justifyContent: 'space-between',
marginTop: 20,
},
cancelButton: {
flex: 1,
paddingVertical: 12,
marginRight: 8,
borderRadius: 8,
borderWidth: 1,
borderColor: '#E5E5E7',
},
submitButton: {
flex: 1,
paddingVertical: 12,
marginLeft: 8,
borderRadius: 8,
backgroundColor: '#007AFF',
},
cancelText: {
textAlign: 'center',
fontSize: 16,
color: '#666',
},
submitText: {
textAlign: 'center',
fontSize: 16,
color: '#FFFFFF',
fontWeight: '600',
},
});
```
## Crash Report Analysis
### Automated Alerts
```typescript
// src/utils/crashAlerts.ts
export class CrashAlerts {
static setupAlerts() {
// Configure Bugsnag alerts
// This would typically be done in the Bugsnag dashboard
// High-priority alerts:
// - App crashes affecting > 1% of users
// - New error types
// - Errors in critical user flows (login, payment)
// Medium-priority alerts:
// - Performance degradation
// - API errors > 5% error rate
// - Memory warnings
}
static generateCrashReport(timeframe: string = '24h') {
// This would integrate with Bugsnag API to generate reports
return {
totalCrashes: 0,
uniqueErrors: 0,
affectedUsers: 0,
topErrors: [],
performanceMetrics: {},
};
}
}
```