# Error Handling & Logging Standards - Maternal Organization App ## Error Philosophy ### Core Principles - **Parent-Friendly Messages**: Never show technical jargon to users - **Graceful Degradation**: App remains usable even with errors - **Recovery Guidance**: Always suggest next steps - **Preserve User Work**: Never lose unsaved data due to errors - **Privacy First**: Never log sensitive data (PII, health info) --- ## Error Code Hierarchy ### Error Code Structure Format: `[CATEGORY]_[SPECIFIC_ERROR]` ### Categories ```typescript enum ErrorCategory { AUTH = 'AUTH', // Authentication/Authorization VALIDATION = 'VAL', // Input validation SYNC = 'SYNC', // Synchronization issues NETWORK = 'NET', // Network/connectivity DATA = 'DATA', // Database/storage AI = 'AI', // AI service errors LIMIT = 'LIMIT', // Rate limiting/quotas PAYMENT = 'PAY', // Subscription/payment SYSTEM = 'SYS', // System/internal errors COMPLIANCE = 'COMP' // COPPA/GDPR compliance } ``` ### Complete Error Code Registry ```typescript export const ErrorCodes = { // Authentication AUTH_INVALID_CREDENTIALS: 'AUTH_001', AUTH_TOKEN_EXPIRED: 'AUTH_002', AUTH_TOKEN_INVALID: 'AUTH_003', AUTH_DEVICE_NOT_TRUSTED: 'AUTH_004', AUTH_MFA_REQUIRED: 'AUTH_005', AUTH_ACCOUNT_LOCKED: 'AUTH_006', // Validation VAL_REQUIRED_FIELD: 'VAL_001', VAL_INVALID_EMAIL: 'VAL_002', VAL_WEAK_PASSWORD: 'VAL_003', VAL_INVALID_DATE: 'VAL_004', VAL_FUTURE_BIRTHDATE: 'VAL_005', VAL_INVALID_AMOUNT: 'VAL_006', // Sync SYNC_CONFLICT: 'SYNC_001', SYNC_OFFLINE_QUEUE_FULL: 'SYNC_002', SYNC_VERSION_MISMATCH: 'SYNC_003', SYNC_FAMILY_UPDATE_FAILED: 'SYNC_004', // Network NET_OFFLINE: 'NET_001', NET_TIMEOUT: 'NET_002', NET_SERVER_ERROR: 'NET_003', NET_SLOW_CONNECTION: 'NET_004', // AI AI_SERVICE_UNAVAILABLE: 'AI_001', AI_QUOTA_EXCEEDED: 'AI_002', AI_INAPPROPRIATE_REQUEST: 'AI_003', AI_CONTEXT_TOO_LARGE: 'AI_004', // Limits LIMIT_RATE_EXCEEDED: 'LIMIT_001', LIMIT_CHILDREN_EXCEEDED: 'LIMIT_002', LIMIT_FAMILY_SIZE_EXCEEDED: 'LIMIT_003', LIMIT_STORAGE_EXCEEDED: 'LIMIT_004', // Compliance COMP_PARENTAL_CONSENT_REQUIRED: 'COMP_001', COMP_AGE_VERIFICATION_FAILED: 'COMP_002', COMP_DATA_RETENTION_EXPIRED: 'COMP_003' }; ``` --- ## User-Facing Error Messages ### Message Structure ```typescript interface UserErrorMessage { title: string; // Brief, clear title message: string; // Detailed explanation action?: string; // What user should do retryable: boolean; // Can user retry? severity: 'info' | 'warning' | 'error'; } ``` ### Localized Error Messages ```typescript // errors/locales/en-US.json { "AUTH_001": { "title": "Sign in failed", "message": "The email or password you entered doesn't match our records.", "action": "Please check your credentials and try again.", "retryable": true }, "SYNC_001": { "title": "Update conflict", "message": "This activity was updated by another family member.", "action": "We've merged the changes. Please review.", "retryable": false }, "AI_002": { "title": "AI assistant limit reached", "message": "You've used all 10 free AI questions today.", "action": "Upgrade to Premium for unlimited questions.", "retryable": false }, "NET_001": { "title": "You're offline", "message": "Don't worry! Your activities are saved locally.", "action": "They'll sync when you're back online.", "retryable": true } } ``` ### Localization for Other Languages ```typescript // errors/locales/es-ES.json { "AUTH_001": { "title": "Error al iniciar sesión", "message": "El correo o contraseña no coinciden con nuestros registros.", "action": "Por favor verifica tus credenciales e intenta nuevamente.", "retryable": true } } // errors/locales/fr-FR.json { "AUTH_001": { "title": "Échec de connexion", "message": "L'email ou le mot de passe ne correspond pas.", "action": "Veuillez vérifier vos identifiants et réessayer.", "retryable": true } } ``` --- ## Logging Strategy ### Log Levels ```typescript enum LogLevel { DEBUG = 0, // Development only INFO = 1, // General information WARN = 2, // Warning conditions ERROR = 3, // Error conditions FATAL = 4 // System is unusable } // Environment-based levels const LOG_LEVELS = { development: LogLevel.DEBUG, staging: LogLevel.INFO, production: LogLevel.WARN }; ``` ### Structured Logging Format ```typescript interface LogEntry { timestamp: string; level: LogLevel; service: string; userId?: string; // Hashed for privacy familyId?: string; // For family-related issues deviceId?: string; // Device fingerprint errorCode?: string; message: string; context?: Record; stack?: string; duration?: number; // For performance logs correlationId: string; // Trace requests } // Example log entry { "timestamp": "2024-01-10T14:30:00.123Z", "level": "ERROR", "service": "ActivityService", "userId": "hash_2n4k8m9p", "errorCode": "SYNC_001", "message": "Sync conflict detected", "context": { "activityId": "act_123", "conflictType": "simultaneous_edit" }, "correlationId": "req_8k3m9n2p" } ``` ### Logger Implementation ```typescript // logger/index.ts import winston from 'winston'; const logger = winston.createLogger({ level: process.env.LOG_LEVEL || 'info', format: winston.format.combine( winston.format.timestamp(), winston.format.errors({ stack: true }), winston.format.json() ), defaultMeta: { service: process.env.SERVICE_NAME, version: process.env.APP_VERSION }, transports: [ new winston.transports.Console({ format: winston.format.simple(), silent: process.env.NODE_ENV === 'test' }), new winston.transports.File({ filename: 'error.log', level: 'error' }) ] }); // Privacy wrapper export const log = { info: (message: string, meta?: any) => { logger.info(message, sanitizePII(meta)); }, error: (message: string, error: Error, meta?: any) => { logger.error(message, { ...sanitizePII(meta), errorMessage: error.message, stack: error.stack }); } }; ``` --- ## Sentry Configuration ### Sentry Setup ```typescript // sentry.config.ts import * as Sentry from '@sentry/node'; Sentry.init({ dsn: process.env.SENTRY_DSN, environment: process.env.NODE_ENV, integrations: [ new Sentry.Integrations.Http({ tracing: true }), new Sentry.Integrations.Express({ app }), ], tracesSampleRate: process.env.NODE_ENV === 'production' ? 0.1 : 1.0, beforeSend(event, hint) { // Remove sensitive data if (event.request) { delete event.request.cookies; delete event.request.headers?.authorization; } // Filter out user-caused errors if (event.exception?.values?.[0]?.type === 'ValidationError') { return null; // Don't send to Sentry } return sanitizeEvent(event); }, ignoreErrors: [ 'NetworkError', 'Request aborted', 'Non-Error promise rejection' ] }); ``` ### React Native Sentry ```typescript // Mobile sentry config import * as Sentry from '@sentry/react-native'; Sentry.init({ dsn: process.env.SENTRY_DSN, debug: __DEV__, environment: __DEV__ ? 'development' : 'production', attachScreenshot: true, attachViewHierarchy: true, beforeSend: (event) => { // Don't send events in dev if (__DEV__) return null; // Remove sensitive context delete event.user?.email; delete event.contexts?.app?.device_name; return event; } }); ``` --- ## Error Recovery Procedures ### Automatic Recovery ```typescript // services/errorRecovery.ts class ErrorRecoveryService { async handleError(error: AppError): Promise { switch (error.code) { case 'NET_OFFLINE': return this.queueForOfflineSync(error.context); case 'AUTH_TOKEN_EXPIRED': return this.refreshToken(); case 'SYNC_CONFLICT': return this.resolveConflict(error.context); case 'AI_SERVICE_UNAVAILABLE': return this.fallbackToOfflineAI(); default: return this.defaultRecovery(error); } } private async queueForOfflineSync(context: any) { await offlineQueue.add(context); return { recovered: true, message: 'Saved locally, will sync when online' }; } } ``` ### User-Guided Recovery ```typescript // components/ErrorBoundary.tsx class ErrorBoundary extends React.Component { componentDidCatch(error: Error, errorInfo: ErrorInfo) { // Log to Sentry Sentry.captureException(error, { contexts: { react: errorInfo } }); // Show recovery UI this.setState({ hasError: true, error, recovery: this.getRecoveryOptions(error) }); } render() { if (this.state.hasError) { return ( ); } return this.props.children; } } ``` --- ## Audit Logging ### COPPA/GDPR Compliance Logging ```typescript interface AuditLog { timestamp: string; userId: string; action: AuditAction; entityType: string; entityId: string; changes?: Record; ipAddress: string; userAgent: string; result: 'success' | 'failure'; reason?: string; } enum AuditAction { // Data access VIEW_CHILD_DATA = 'VIEW_CHILD_DATA', EXPORT_DATA = 'EXPORT_DATA', // Data modification CREATE_CHILD_PROFILE = 'CREATE_CHILD_PROFILE', UPDATE_CHILD_DATA = 'UPDATE_CHILD_DATA', DELETE_CHILD_DATA = 'DELETE_CHILD_DATA', // Consent GRANT_CONSENT = 'GRANT_CONSENT', REVOKE_CONSENT = 'REVOKE_CONSENT', // Account DELETE_ACCOUNT = 'DELETE_ACCOUNT', CHANGE_PASSWORD = 'CHANGE_PASSWORD' } ``` ### Audit Log Implementation ```sql -- Audit log table with partitioning CREATE TABLE audit_logs ( id BIGSERIAL, timestamp TIMESTAMP NOT NULL, user_id VARCHAR(20), action VARCHAR(50) NOT NULL, entity_type VARCHAR(50), entity_id VARCHAR(20), changes JSONB, ip_address INET, user_agent TEXT, result VARCHAR(20), PRIMARY KEY (id, timestamp) ) PARTITION BY RANGE (timestamp); ``` --- ## Performance Monitoring ### Response Time Logging ```typescript // middleware/performanceLogger.ts export const performanceLogger = (req: Request, res: Response, next: Next) => { const start = Date.now(); res.on('finish', () => { const duration = Date.now() - start; if (duration > 1000) { // Log slow requests logger.warn('Slow request detected', { method: req.method, path: req.path, duration, statusCode: res.statusCode }); // Send to monitoring metrics.histogram('request.duration', duration, { path: req.path, method: req.method }); } }); next(); }; ``` --- ## Alert Configuration ### Critical Alerts ```yaml # alerts/critical.yml alerts: - name: high_error_rate condition: error_rate > 5% duration: 5m action: page_on_call - name: auth_failures_spike condition: auth_failures > 100 duration: 1m action: security_team_alert - name: ai_service_down condition: ai_availability < 99% duration: 2m action: notify_team - name: database_connection_pool_exhausted condition: available_connections < 5 action: scale_database ``` --- ## Client-Side Error Tracking ### React Native Global Handler ```typescript // errorHandler.ts import { setJSExceptionHandler } from 'react-native-exception-handler'; setJSExceptionHandler((error, isFatal) => { if (isFatal) { logger.fatal('Fatal JS error', { error }); Alert.alert( 'Unexpected error occurred', 'The app needs to restart. Your data has been saved.', [{ text: 'Restart', onPress: () => RNRestart.Restart() }] ); } else { logger.error('Non-fatal JS error', { error }); // Show toast notification Toast.show({ type: 'error', text1: 'Something went wrong', text2: 'Please try again' }); } }, true); // Allow in production ``` --- ## Error Analytics Dashboard ### Key Metrics ```typescript interface ErrorMetrics { errorRate: number; // Errors per 1000 requests errorTypes: Record; // Count by error code affectedUsers: number; // Unique users with errors recoveryRate: number; // % of errors recovered meanTimeToRecovery: number; // Seconds criticalErrors: ErrorEvent[]; // P0 errors } // Monitoring queries const getErrorMetrics = async (timeRange: TimeRange): Promise => { const errors = await db.query(` SELECT COUNT(*) as total_errors, COUNT(DISTINCT user_id) as affected_users, AVG(recovery_time) as mttr, error_code, COUNT(*) as count FROM error_logs WHERE timestamp > $1 GROUP BY error_code `, [timeRange.start]); return processMetrics(errors); }; ``` --- ## Development Error Tools ### Debug Mode Enhancements ```typescript // Development only error overlay if (__DEV__) { // Show detailed error information ErrorUtils.setGlobalHandler((error, isFatal) => { console.group('🔴 Error Details'); console.error('Error:', error.message); console.error('Stack:', error.stack); console.error('Component Stack:', error.componentStack); console.error('Fatal:', isFatal); console.groupEnd(); }); // Network request inspector global.XMLHttpRequest = decorateXHR(global.XMLHttpRequest); } ```