Add comprehensive .gitignore

This commit is contained in:
2025-10-01 19:01:52 +00:00
commit f3ff07c0ef
254 changed files with 88254 additions and 0 deletions

View File

@@ -0,0 +1,588 @@
# 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<string, any>;
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<RecoveryAction> {
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 (
<ErrorRecoveryScreen
title={i18n.t('error.something_went_wrong')}
message={i18n.t('error.we_are_sorry')}
actions={[
{ label: 'Try Again', onPress: this.retry },
{ label: 'Go to Dashboard', onPress: this.reset },
{ label: 'Contact Support', onPress: this.support }
]}
/>
);
}
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<string, any>;
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<string, number>; // 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<ErrorMetrics> => {
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);
}
```