# Error Tracking

Error tracking allows you to capture and monitor exceptions, errors, and crashes in your application automatically or manually.

## Capabilities

### Capture Exception Manually

Manually captures an exception with optional additional properties.

```typescript { .api }
/**
 * Manually captures an exception
 * @param error - Error object or any throwable value
 * @param additionalProperties - Additional properties to attach to the exception event
 * @returns CaptureResult if successful, undefined otherwise
 */
function captureException(
    error: unknown,
    additionalProperties?: Properties
): CaptureResult | undefined;
```

**Usage Examples:**

```typescript
// Capture basic error
try {
    riskyOperation();
} catch (error) {
    posthog.captureException(error);
}

// Capture with additional context
try {
    await processPayment(amount);
} catch (error) {
    posthog.captureException(error, {
        payment_amount: amount,
        user_id: userId,
        payment_method: 'credit_card'
    });
}

// Capture custom error
const customError = new Error('Custom error message');
customError.code = 'CUSTOM_ERROR_CODE';
posthog.captureException(customError, {
    context: 'user_action',
    severity: 'high'
});

// Capture non-Error objects
posthog.captureException('String error message', {
    source: 'api_response'
});

posthog.captureException({
    message: 'Custom error object',
    details: { foo: 'bar' }
});
```

### Start Exception Autocapture

Starts automatic capture of uncaught errors and unhandled promise rejections.

```typescript { .api }
/**
 * Starts automatic exception capture
 * @param config - Configuration for autocapture behavior
 */
function startExceptionAutocapture(config?: ExceptionAutoCaptureConfig): void;
```

**Usage Examples:**

```typescript
// Start with default settings (all error types)
posthog.startExceptionAutocapture();

// Customize what to capture
posthog.startExceptionAutocapture({
    capture_unhandled_errors: true,      // window.onerror
    capture_unhandled_rejections: true,   // unhandledrejection
    capture_console_errors: false         // console.error
});

// Capture only promise rejections
posthog.startExceptionAutocapture({
    capture_unhandled_errors: false,
    capture_unhandled_rejections: true,
    capture_console_errors: false
});

// Start after user consent
function onErrorTrackingConsent() {
    posthog.startExceptionAutocapture();
}
```

### Stop Exception Autocapture

Stops automatic exception capture.

```typescript { .api }
/**
 * Stops automatic exception capture
 */
function stopExceptionAutocapture(): void;
```

**Usage Examples:**

```typescript
// Stop autocapture
posthog.stopExceptionAutocapture();

// Stop on user opt-out
function onUserOptOut() {
    posthog.stopExceptionAutocapture();
    localStorage.setItem('error_tracking_disabled', 'true');
}

// Stop temporarily
function pauseErrorTracking() {
    posthog.stopExceptionAutocapture();
}

function resumeErrorTracking() {
    posthog.startExceptionAutocapture();
}
```

## Types

### ExceptionAutoCaptureConfig

```typescript { .api }
/**
 * Configuration for automatic exception capture
 */
interface ExceptionAutoCaptureConfig {
    /**
     * Capture uncaught errors (window.onerror)
     * @default true
     */
    capture_unhandled_errors?: boolean;

    /**
     * Capture unhandled promise rejections
     * @default true
     */
    capture_unhandled_rejections?: boolean;

    /**
     * Capture console.error calls
     * @default false
     */
    capture_console_errors?: boolean;
}
```

### CaptureResult

```typescript { .api }
/**
 * Result of capturing an exception
 */
interface CaptureResult {
    /**
     * Event name that was captured
     */
    event: string;

    /**
     * Properties that were sent with the event
     */
    properties: Record<string, any>;
}
```

### Properties

```typescript { .api }
/**
 * Object containing event properties
 */
type Properties = Record<string, Property | Property[]>;
```

### Property

```typescript { .api }
/**
 * A single property value
 */
type Property = string | number | boolean | null | undefined;
```

## Configuration

### Enable Exception Capture on Init

Configure exception capture during initialization:

```typescript { .api }
// Enable with default settings
posthog.init('token', {
    capture_exceptions: true
});

// Enable with custom configuration
posthog.init('token', {
    capture_exceptions: {
        capture_unhandled_errors: true,
        capture_unhandled_rejections: true,
        capture_console_errors: true
    }
});

// Disable exception capture
posthog.init('token', {
    capture_exceptions: false
});
```

## Best Practices

### Error Context

Always include relevant context when capturing errors:

```typescript
// ✅ Good: Rich context
try {
    await fetchUserData(userId);
} catch (error) {
    posthog.captureException(error, {
        user_id: userId,
        operation: 'fetch_user_data',
        api_endpoint: '/api/users',
        timestamp: Date.now(),
        retry_count: retryCount,
        network_status: navigator.onLine ? 'online' : 'offline'
    });
}

// ❌ Bad: No context
try {
    await fetchUserData(userId);
} catch (error) {
    posthog.captureException(error);
}
```

### Error Severity Levels

Categorize errors by severity:

```typescript
function captureErrorWithSeverity(error, severity, context = {}) {
    posthog.captureException(error, {
        severity: severity,
        ...context
    });
}

// Usage
try {
    criticalOperation();
} catch (error) {
    captureErrorWithSeverity(error, 'critical', {
        operation: 'payment_processing'
    });
}

try {
    nonCriticalOperation();
} catch (error) {
    captureErrorWithSeverity(error, 'warning', {
        operation: 'load_recommendations'
    });
}
```

### Error Filtering

Filter out noise and expected errors:

```typescript
// Filter known/expected errors
function shouldCaptureError(error) {
    // Don't capture user cancellations
    if (error.name === 'AbortError') {
        return false;
    }

    // Don't capture network timeouts below threshold
    if (error.code === 'ETIMEDOUT' && error.timeout < 5000) {
        return false;
    }

    // Don't capture 3rd party script errors
    if (error.filename?.includes('third-party-script')) {
        return false;
    }

    return true;
}

// Wrap captureException
function captureFilteredError(error, properties = {}) {
    if (shouldCaptureError(error)) {
        posthog.captureException(error, properties);
    }
}

// Usage
try {
    await operation();
} catch (error) {
    captureFilteredError(error, { operation: 'user_action' });
}
```

### Link to Session Replays

Include session replay URLs with exceptions:

```typescript
// Always include replay URL
try {
    dangerousOperation();
} catch (error) {
    posthog.captureException(error, {
        session_replay_url: posthog.get_session_replay_url({
            withTimestamp: true,
            timestampLookBack: 30
        }),
        session_id: posthog.get_session_id()
    });
}

// Automatic integration
function captureExceptionWithReplay(error, properties = {}) {
    posthog.captureException(error, {
        ...properties,
        session_replay_url: posthog.get_session_replay_url({
            withTimestamp: true
        }),
        session_id: posthog.get_session_id(),
        user_id: posthog.get_distinct_id()
    });
}
```

### Error Boundaries

Integrate with React error boundaries:

```typescript
class ErrorBoundary extends React.Component {
    componentDidCatch(error, errorInfo) {
        posthog.captureException(error, {
            error_boundary: true,
            component_stack: errorInfo.componentStack,
            location: window.location.pathname,
            session_replay_url: posthog.get_session_replay_url({
                withTimestamp: true
            })
        });
    }

    render() {
        if (this.state.hasError) {
            return <ErrorFallback />;
        }

        return this.props.children;
    }
}
```

## Common Patterns

### Global Error Handler

Set up comprehensive error tracking:

```typescript
// Initialize error tracking
function initializeErrorTracking() {
    // Start autocapture
    posthog.startExceptionAutocapture({
        capture_unhandled_errors: true,
        capture_unhandled_rejections: true,
        capture_console_errors: false // Handle manually for filtering
    });

    // Manual console.error capture with filtering
    const originalConsoleError = console.error;
    console.error = function(...args) {
        // Filter out known warnings
        const message = args[0]?.toString() || '';
        if (!message.includes('React DevTools') &&
            !message.includes('Download the React DevTools')) {
            posthog.captureException(new Error(message), {
                source: 'console.error',
                args: args
            });
        }

        originalConsoleError.apply(console, args);
    };
}

// Call on app initialization
initializeErrorTracking();
```

### Async Error Handling

Handle async errors consistently:

```typescript
// Async wrapper with error tracking
async function withErrorTracking(fn, context = {}) {
    try {
        return await fn();
    } catch (error) {
        posthog.captureException(error, {
            ...context,
            function_name: fn.name
        });
        throw error;
    }
}

// Usage
const userData = await withErrorTracking(
    () => fetchUserData(userId),
    { operation: 'fetch_user', user_id: userId }
);

// Promise wrapper
function trackPromiseErrors(promise, context = {}) {
    return promise.catch(error => {
        posthog.captureException(error, context);
        throw error;
    });
}

// Usage
trackPromiseErrors(
    fetch('/api/data'),
    { endpoint: '/api/data' }
);
```

### API Error Tracking

Track API errors with rich context:

```typescript
class APIClient {
    async request(url, options = {}) {
        try {
            const response = await fetch(url, options);

            if (!response.ok) {
                const error = new Error(`API Error: ${response.status}`);
                error.status = response.status;
                error.url = url;
                error.method = options.method || 'GET';

                posthog.captureException(error, {
                    error_type: 'api_error',
                    status_code: response.status,
                    endpoint: url,
                    method: options.method || 'GET',
                    response_headers: Object.fromEntries(response.headers)
                });

                throw error;
            }

            return response.json();
        } catch (error) {
            if (!error.status) {
                // Network error
                posthog.captureException(error, {
                    error_type: 'network_error',
                    endpoint: url,
                    method: options.method || 'GET'
                });
            }

            throw error;
        }
    }
}
```

### Error Aggregation

Group similar errors together:

```typescript
function getErrorFingerprint(error) {
    // Create unique fingerprint for error grouping
    const message = error.message || '';
    const name = error.name || 'Error';
    const stackLine = error.stack?.split('\n')[1] || '';

    return `${name}:${message}:${stackLine}`;
}

function captureExceptionWithFingerprint(error, properties = {}) {
    const fingerprint = getErrorFingerprint(error);

    posthog.captureException(error, {
        ...properties,
        error_fingerprint: fingerprint,
        error_name: error.name,
        error_message: error.message
    });
}

// Usage
try {
    operation();
} catch (error) {
    captureExceptionWithFingerprint(error, {
        context: 'user_action'
    });
}
```

### Performance Error Tracking

Track performance-related errors:

```typescript
// Track slow operations as errors
async function trackSlowOperation(fn, threshold = 5000, context = {}) {
    const startTime = Date.now();

    try {
        const result = await fn();
        const duration = Date.now() - startTime;

        if (duration > threshold) {
            const error = new Error('Operation exceeded time threshold');
            error.duration = duration;
            error.threshold = threshold;

            posthog.captureException(error, {
                error_type: 'performance',
                duration_ms: duration,
                threshold_ms: threshold,
                ...context
            });
        }

        return result;
    } catch (error) {
        const duration = Date.now() - startTime;

        posthog.captureException(error, {
            ...context,
            duration_ms: duration
        });

        throw error;
    }
}

// Usage
await trackSlowOperation(
    () => loadLargeDataset(),
    3000,
    { operation: 'load_dataset' }
);
```

### Error Rate Limiting

Prevent error spam:

```typescript
class ErrorRateLimiter {
    constructor(maxErrors = 10, windowMs = 60000) {
        this.maxErrors = maxErrors;
        this.windowMs = windowMs;
        this.errors = [];
    }

    shouldCapture(error) {
        const now = Date.now();
        const fingerprint = this.getFingerprint(error);

        // Remove old errors outside window
        this.errors = this.errors.filter(e =>
            now - e.timestamp < this.windowMs
        );

        // Count errors with same fingerprint
        const similarErrors = this.errors.filter(e =>
            e.fingerprint === fingerprint
        );

        if (similarErrors.length >= this.maxErrors) {
            console.log(`Error rate limit exceeded for: ${fingerprint}`);
            return false;
        }

        this.errors.push({
            fingerprint: fingerprint,
            timestamp: now
        });

        return true;
    }

    getFingerprint(error) {
        return `${error.name}:${error.message}`;
    }
}

const rateLimiter = new ErrorRateLimiter();

function captureRateLimitedError(error, properties = {}) {
    if (rateLimiter.shouldCapture(error)) {
        posthog.captureException(error, properties);
    }
}
```

### Third-Party Error Integration

Integrate with error tracking services:

```typescript
// Sentry integration
import * as Sentry from '@sentry/browser';

function captureToAllServices(error, properties = {}) {
    // Capture to PostHog
    posthog.captureException(error, properties);

    // Capture to Sentry
    Sentry.captureException(error, {
        extra: properties,
        contexts: {
            posthog: {
                session_id: posthog.get_session_id(),
                distinct_id: posthog.get_distinct_id()
            }
        }
    });
}

// Bugsnag integration
import Bugsnag from '@bugsnag/js';

function captureWithBugsnag(error, properties = {}) {
    posthog.captureException(error, properties);

    Bugsnag.notify(error, (event) => {
        event.addMetadata('posthog', {
            session_id: posthog.get_session_id(),
            distinct_id: posthog.get_distinct_id(),
            ...properties
        });
    });
}
```

### Development vs Production

Handle errors differently by environment:

```typescript
function captureEnvironmentAwareError(error, properties = {}) {
    const isDevelopment = process.env.NODE_ENV === 'development';

    if (isDevelopment) {
        // In development, log to console with full details
        console.error('Error captured:', error);
        console.log('Context:', properties);

        // Optionally still capture to PostHog
        // posthog.captureException(error, properties);
    } else {
        // In production, always capture
        posthog.captureException(error, {
            ...properties,
            environment: 'production'
        });
    }
}
```

### User Impact Tracking

Track how errors affect users:

```typescript
function captureErrorWithImpact(error, impact, properties = {}) {
    posthog.captureException(error, {
        ...properties,
        user_impact: impact,
        user_id: posthog.get_distinct_id(),
        session_id: posthog.get_session_id()
    });
}

// Usage examples
try {
    await submitPayment();
} catch (error) {
    captureErrorWithImpact(error, 'high', {
        operation: 'payment',
        amount: paymentAmount
    });
    showErrorToUser('Payment failed. Please try again.');
}

try {
    await loadRecommendations();
} catch (error) {
    captureErrorWithImpact(error, 'low', {
        operation: 'recommendations'
    });
    // Silently fail, show default recommendations
}
```

## Advanced Error Tracking Patterns

### Error Categorization

```typescript { .api }
// Categorize errors for better analysis
enum ErrorCategory {
    NETWORK = 'network',
    VALIDATION = 'validation',
    BUSINESS_LOGIC = 'business_logic',
    THIRD_PARTY = 'third_party',
    SECURITY = 'security',
    PERFORMANCE = 'performance'
}

function captureErrorWithCategory(
    error: Error,
    category: ErrorCategory,
    properties: Properties = {}
) {
    posthog.captureException(error, {
        ...properties,
        error_category: category,
        error_severity: getSeverity(category),
        environment: process.env.NODE_ENV
    });
}

function getSeverity(category: ErrorCategory): string {
    switch (category) {
        case ErrorCategory.SECURITY:
            return 'critical';
        case ErrorCategory.BUSINESS_LOGIC:
        case ErrorCategory.NETWORK:
            return 'high';
        case ErrorCategory.VALIDATION:
        case ErrorCategory.THIRD_PARTY:
            return 'medium';
        case ErrorCategory.PERFORMANCE:
            return 'low';
    }
}
```

### Error Context Enrichment

```typescript { .api }
// Automatically enrich all errors with context
class ErrorContextEnricher {
    captureException(error: Error, additionalProperties: Properties = {}) {
        const enrichedProperties = {
            ...additionalProperties,
            
            // Session context
            session_id: posthog.get_session_id(),
            session_replay_url: posthog.get_session_replay_url({
                withTimestamp: true,
                timestampLookBack: 30
            }),
            
            // User context
            user_id: posthog.get_distinct_id(),
            user_groups: posthog.getGroups(),
            
            // Page context
            page_url: window.location.href,
            page_path: window.location.pathname,
            page_title: document.title,
            referrer: document.referrer,
            
            // Device context
            user_agent: navigator.userAgent,
            platform: navigator.platform,
            language: navigator.language,
            screen_resolution: `${window.screen.width}x${window.screen.height}`,
            viewport_size: `${window.innerWidth}x${window.innerHeight}`,
            
            // Network context
            online: navigator.onLine,
            connection_type: (navigator as any).connection?.effectiveType,
            
            // Error context
            error_name: error.name,
            error_message: error.message,
            error_stack: error.stack,
            timestamp: new Date().toISOString()
        };
        
        return posthog.captureException(error, enrichedProperties);
    }
}

const enricher = new ErrorContextEnricher();

// Usage
try {
    riskyOperation();
} catch (error) {
    enricher.captureException(error, {
        operation: 'risky_operation',
        input_params: params
    });
}
```

### Error Recovery Tracking

```typescript { .api }
// Track error recovery attempts
class ErrorRecoveryTracker {
    async withRecovery<T>(
        operation: () => Promise<T>,
        operationName: string,
        maxRetries: number = 3
    ): Promise<T> {
        let lastError: Error | null = null;
        
        for (let attempt = 0; attempt <= maxRetries; attempt++) {
            try {
                const result = await operation();
                
                // Track successful recovery
                if (attempt > 0) {
                    posthog.capture('error_recovered', {
                        operation: operationName,
                        attempts: attempt + 1,
                        last_error: lastError?.message
                    });
                }
                
                return result;
            } catch (error) {
                lastError = error as Error;
                
                // Track retry attempt
                posthog.capture('error_retry_attempt', {
                    operation: operationName,
                    attempt: attempt + 1,
                    max_retries: maxRetries,
                    error: lastError.message
                });
                
                // Final attempt failed
                if (attempt === maxRetries) {
                    posthog.captureException(lastError, {
                        operation: operationName,
                        attempts: attempt + 1,
                        recovery_failed: true
                    });
                    
                    throw lastError;
                }
                
                // Wait before retry (exponential backoff)
                await new Promise(resolve =>
                    setTimeout(resolve, Math.pow(2, attempt) * 1000)
                );
            }
        }
        
        throw lastError!;
    }
}

// Usage
const recovery = new ErrorRecoveryTracker();

const data = await recovery.withRecovery(
    () => fetchUserData(userId),
    'fetch_user_data',
    3
);
```

### Source Map Integration

```typescript { .api }
// Enhance errors with source maps
interface EnhancedError extends Error {
    sourceFile?: string;
    sourceLine?: number;
    sourceColumn?: number;
    originalStack?: string;
}

function captureErrorWithSourceMap(error: EnhancedError, properties: Properties = {}) {
    // Source maps are typically handled by browser dev tools
    // But you can add source location if available
    const enhancedProperties = {
        ...properties,
        source_file: error.sourceFile,
        source_line: error.sourceLine,
        source_column: error.sourceColumn,
        stack_trace: error.stack,
        original_stack: error.originalStack
    };
    
    posthog.captureException(error, enhancedProperties);
}
```

## Error Monitoring Dashboards

### Real-Time Error Dashboard

```typescript { .api }
class ErrorDashboard {
    private errors: Array<{
        error: Error;
        timestamp: number;
        properties: Properties;
    }> = [];
    
    private maxErrors = 100;
    
    initialize() {
        // Listen for error events
        posthog.on('eventCaptured', (data) => {
            if (data.event === '$exception') {
                this.addError({
                    error: new Error(data.properties.$exception_message),
                    timestamp: Date.now(),
                    properties: data.properties
                });
            }
        });
    }
    
    addError(errorData: any) {
        this.errors.unshift(errorData);
        
        if (this.errors.length > this.maxErrors) {
            this.errors.pop();
        }
        
        this.updateDashboard();
    }
    
    updateDashboard() {
        const errorCounts = this.errors.reduce((acc, e) => {
            const name = e.error.name;
            acc[name] = (acc[name] || 0) + 1;
            return acc;
        }, {} as Record<string, number>);
        
        console.log('Error dashboard:', {
            total: this.errors.length,
            by_type: errorCounts,
            recent: this.errors.slice(0, 5).map(e => ({
                message: e.error.message,
                time: new Date(e.timestamp).toISOString()
            }))
        });
    }
    
    getErrors() {
        return [...this.errors];
    }
    
    clearErrors() {
        this.errors = [];
        this.updateDashboard();
    }
}
```

## Troubleshooting

### Exceptions Not Being Captured

```typescript { .api }
// Debug exception capture
function debugExceptionCapture() {
    console.log('Exception autocapture:', {
        enabled: posthog.config.capture_exceptions,
        config: posthog.config.capture_exceptions
    });
    
    // Test manual capture
    try {
        throw new Error('Test error');
    } catch (error) {
        const result = posthog.captureException(error, { test: true });
        console.log('Capture result:', result);
    }
    
    // Check if events are being sent
    posthog.on('eventCaptured', (data) => {
        if (data.event === '$exception') {
            console.log('Exception event captured:', data);
        }
    });
}
```

### Too Many Errors Being Captured

```typescript { .api }
// Implement smart filtering
class SmartErrorFilter {
    private errorCounts = new Map<string, number>();
    private readonly MAX_SAME_ERROR = 10;
    private readonly WINDOW_MS = 60000;
    
    shouldCapture(error: Error): boolean {
        const fingerprint = this.getFingerprint(error);
        const count = this.errorCounts.get(fingerprint) || 0;
        
        if (count >= this.MAX_SAME_ERROR) {
            console.log('Error threshold reached:', fingerprint);
            return false;
        }
        
        this.errorCounts.set(fingerprint, count + 1);
        
        // Reset count after window
        setTimeout(() => {
            this.errorCounts.delete(fingerprint);
        }, this.WINDOW_MS);
        
        return true;
    }
    
    private getFingerprint(error: Error): string {
        return `${error.name}:${error.message}`;
    }
}

const filter = new SmartErrorFilter();

// Use in global error handler
window.addEventListener('error', (event) => {
    if (filter.shouldCapture(event.error)) {
        posthog.captureException(event.error);
    }
});
```

### Performance Impact

```typescript { .api }
// Monitor error tracking performance
class ErrorPerformanceMonitor {
    private captureTimings: number[] = [];
    
    trackCapturePerformance(error: Error, properties: Properties = {}) {
        const start = performance.now();
        
        const result = posthog.captureException(error, properties);
        
        const duration = performance.now() - start;
        this.captureTimings.push(duration);
        
        if (this.captureTimings.length > 100) {
            this.captureTimings.shift();
        }
        
        return result;
    }
    
    getAverageCaptureTime(): number {
        if (this.captureTimings.length === 0) return 0;
        
        const sum = this.captureTimings.reduce((a, b) => a + b, 0);
        return sum / this.captureTimings.length;
    }
    
    logPerformanceStats() {
        console.log('Error capture performance:', {
            avg_ms: this.getAverageCaptureTime().toFixed(2),
            count: this.captureTimings.length
        });
    }
}
```

## See Also

- [Session Recording](./session-recording.md) - Link errors to session replays
- [Sentry Integration](./sentry-integration.md) - Integrate with Sentry
- [Events](./events.md) - General event capture
- [Privacy](./privacy.md) - Error data privacy considerations
- [Types](./types.md) - Error tracking type definitions
