# 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: {}, }; } } ```