# Error Tracking

Capture exceptions manually or automatically with full stack traces, source context, and rate limiting.

## Capabilities

### Capture Exception

Capture an exception manually and queue it for sending.

```typescript { .api }
/**
 * Capture an error exception as an event
 * @param error - The error to capture
 * @param distinctId - Optional user distinct ID
 * @param additionalProperties - Optional additional properties to include
 */
captureException(
  error: unknown,
  distinctId?: string,
  additionalProperties?: Record<string | number, any>
): void;
```

**Usage Examples:**

```typescript
import { PostHog } from 'posthog-node';

const client = new PostHog('phc_your_api_key');

// Basic exception capture
try {
  riskyOperation();
} catch (error) {
  client.captureException(error, 'user_123');
}

// With additional properties
try {
  await apiCall();
} catch (error) {
  client.captureException(error, 'user_123', {
    endpoint: '/api/users',
    method: 'POST',
    status_code: 500
  });
}

// Without user ID (uses personless processing)
try {
  backgroundJob();
} catch (error) {
  client.captureException(error);
}

// With context properties
try {
  processPayment(orderId);
} catch (error) {
  client.captureException(error, 'user_123', {
    order_id: orderId,
    payment_method: 'credit_card',
    amount: 99.99
  });
}
```

### Capture Exception Immediately

Capture an exception and send it immediately without queuing. Use for serverless environments or critical errors.

```typescript { .api }
/**
 * Capture an error exception as an event immediately (synchronously)
 * @param error - The error to capture
 * @param distinctId - Optional user distinct ID
 * @param additionalProperties - Optional additional properties to include
 * @returns Promise that resolves when the error is captured
 */
async captureExceptionImmediate(
  error: unknown,
  distinctId?: string,
  additionalProperties?: Record<string | number, any>
): Promise<void>;
```

**Usage Examples:**

```typescript
// Immediate capture in serverless function
export async function handler(event) {
  try {
    return await processEvent(event);
  } catch (error) {
    await client.captureExceptionImmediate(error, event.userId);
    throw error;
  }
}

// With additional context
try {
  await criticalOperation();
} catch (error) {
  await client.captureExceptionImmediate(error, 'user_123', {
    operation: 'critical_operation',
    severity: 'high'
  });
  throw error;
}

// In API endpoint
app.post('/api/action', async (req, res) => {
  try {
    const result = await performAction(req.body);
    res.json({ success: true, result });
  } catch (error) {
    await client.captureExceptionImmediate(error, req.user?.id, {
      endpoint: '/api/action',
      body: req.body
    });
    res.status(500).json({ error: 'Internal error' });
  }
});
```

## Automatic Exception Capture

Enable automatic capture of uncaught exceptions and unhandled promise rejections.

### Configuration

```typescript
import { PostHog } from 'posthog-node';

// Enable automatic exception capture
const client = new PostHog('phc_your_api_key', {
  enableExceptionAutocapture: true
});
```

### How It Works

When `enableExceptionAutocapture` is enabled:
- Uncaught exceptions are automatically captured
- Unhandled promise rejections are automatically captured
- Rate limiting prevents flood of duplicate errors (10 per type, refills 1 per 10 seconds)
- Fatal errors trigger graceful shutdown
- Uses personless processing (no distinct ID required)

**Usage Examples:**

```typescript
// Enable autocapture
const client = new PostHog('phc_your_api_key', {
  enableExceptionAutocapture: true
});

// Uncaught exceptions are automatically captured
function riskyFunction() {
  throw new Error('This will be automatically captured');
}

// Unhandled rejections are automatically captured
async function asyncRiskyFunction() {
  throw new Error('This will be automatically captured');
}

// The application continues running after non-fatal errors
setTimeout(() => {
  riskyFunction();
}, 1000);

// Fatal errors trigger shutdown
process.on('uncaughtException', (error) => {
  // PostHog captures this automatically and then exits
});
```

## Usage Patterns

### Express.js Error Handling

```typescript
import express from 'express';
import { PostHog } from 'posthog-node';

const app = express();
const client = new PostHog('phc_your_api_key');

// Route-level error handling
app.get('/api/users/:id', async (req, res) => {
  try {
    const user = await getUser(req.params.id);
    res.json(user);
  } catch (error) {
    client.captureException(error, req.user?.id, {
      endpoint: '/api/users/:id',
      user_id: req.params.id
    });
    res.status(500).json({ error: 'Failed to fetch user' });
  }
});

// Application-level error middleware
app.use((error, req, res, next) => {
  client.captureException(error, req.user?.id, {
    endpoint: req.path,
    method: req.method,
    status: error.status || 500
  });
  res.status(error.status || 500).json({
    error: error.message || 'Internal server error'
  });
});
```

### Async/Await Error Handling

```typescript
// Basic async error handling
async function fetchData() {
  try {
    const response = await fetch('https://api.example.com/data');
    const data = await response.json();
    return data;
  } catch (error) {
    client.captureException(error, 'user_123', {
      operation: 'fetch_data',
      url: 'https://api.example.com/data'
    });
    throw error;
  }
}

// With retry logic
async function fetchDataWithRetry(url, retries = 3) {
  for (let i = 0; i < retries; i++) {
    try {
      const response = await fetch(url);
      return await response.json();
    } catch (error) {
      if (i === retries - 1) {
        client.captureException(error, undefined, {
          operation: 'fetch_data_with_retry',
          url: url,
          attempts: retries
        });
        throw error;
      }
    }
  }
}
```

### Promise Error Handling

```typescript
// Promise chain error handling
fetchUser('user_123')
  .then(user => processUser(user))
  .then(result => saveResult(result))
  .catch(error => {
    client.captureException(error, 'user_123', {
      operation: 'user_processing_pipeline'
    });
  });

// Promise.all error handling
Promise.all([
  fetchUsers(),
  fetchOrders(),
  fetchProducts()
])
  .then(([users, orders, products]) => {
    return combineData(users, orders, products);
  })
  .catch(error => {
    client.captureException(error, undefined, {
      operation: 'parallel_data_fetch'
    });
  });
```

### Database Error Handling

```typescript
// MongoDB error handling
async function createUser(userData) {
  try {
    const user = await db.users.insertOne(userData);
    return user;
  } catch (error) {
    client.captureException(error, userData.id, {
      operation: 'create_user',
      database: 'mongodb',
      collection: 'users',
      duplicate_key: error.code === 11000
    });
    throw error;
  }
}

// PostgreSQL error handling
async function queryUsers(filters) {
  try {
    const result = await pool.query('SELECT * FROM users WHERE ...', filters);
    return result.rows;
  } catch (error) {
    client.captureException(error, undefined, {
      operation: 'query_users',
      database: 'postgresql',
      query: 'SELECT * FROM users WHERE ...',
      error_code: error.code
    });
    throw error;
  }
}
```

### Background Job Error Handling

```typescript
// Worker queue error handling
async function processJob(job) {
  try {
    await job.process();
    await job.complete();
  } catch (error) {
    await client.captureExceptionImmediate(error, job.userId, {
      job_id: job.id,
      job_type: job.type,
      attempt: job.attemptsMade,
      max_attempts: job.maxAttempts
    });
    await job.fail(error);
  }
}

// Cron job error handling
cron.schedule('0 * * * *', async () => {
  try {
    await runHourlyTask();
  } catch (error) {
    client.captureException(error, undefined, {
      job_type: 'cron',
      schedule: '0 * * * *',
      task: 'hourly_task'
    });
  }
});
```

### Third-party API Error Handling

```typescript
// Stripe API error handling
async function createCharge(amount, currency, customer) {
  try {
    const charge = await stripe.charges.create({
      amount,
      currency,
      customer
    });
    return charge;
  } catch (error) {
    client.captureException(error, customer, {
      operation: 'stripe_charge',
      amount: amount,
      currency: currency,
      stripe_error_type: error.type,
      stripe_error_code: error.code
    });
    throw error;
  }
}

// SendGrid API error handling
async function sendEmail(to, subject, body) {
  try {
    await sgMail.send({ to, subject, html: body });
  } catch (error) {
    client.captureException(error, to, {
      operation: 'send_email',
      email_to: to,
      subject: subject,
      response_code: error.code
    });
    throw error;
  }
}
```

### Serverless Error Handling

```typescript
// AWS Lambda
export async function handler(event, context) {
  try {
    const result = await processEvent(event);
    return {
      statusCode: 200,
      body: JSON.stringify(result)
    };
  } catch (error) {
    await client.captureExceptionImmediate(error, event.userId, {
      function_name: context.functionName,
      request_id: context.awsRequestId,
      event_type: event.type
    });
    return {
      statusCode: 500,
      body: JSON.stringify({ error: 'Internal error' })
    };
  }
}

// Vercel Serverless Function
export default async function handler(req, res) {
  try {
    const result = await processRequest(req);
    res.json(result);
  } catch (error) {
    await client.captureExceptionImmediate(error, req.user?.id, {
      url: req.url,
      method: req.method
    });
    res.status(500).json({ error: 'Internal error' });
  }
}
```

### Custom Error Types

```typescript
// Custom error classes
class ValidationError extends Error {
  constructor(message, field) {
    super(message);
    this.name = 'ValidationError';
    this.field = field;
  }
}

class DatabaseError extends Error {
  constructor(message, query) {
    super(message);
    this.name = 'DatabaseError';
    this.query = query;
  }
}

// Handling custom errors
try {
  validateInput(data);
} catch (error) {
  if (error instanceof ValidationError) {
    client.captureException(error, 'user_123', {
      error_type: 'validation',
      field: error.field,
      severity: 'low'
    });
  } else if (error instanceof DatabaseError) {
    client.captureException(error, 'user_123', {
      error_type: 'database',
      query: error.query,
      severity: 'high'
    });
  } else {
    client.captureException(error, 'user_123', {
      error_type: 'unknown',
      severity: 'medium'
    });
  }
  throw error;
}
```

## Exception Properties

PostHog automatically extracts the following properties from exceptions:

- `$exception_message` - Error message
- `$exception_type` - Error type/class name
- `$exception_level` - Severity level (error, warning, etc.)
- `$exception_list` - Array of exception details with stack traces
- Stack trace information with:
  - File paths
  - Line numbers
  - Column numbers
  - Function names
  - Source context (Node.js only, not edge)

## Best Practices

### Always Provide Context

```typescript
// Good: Rich context
try {
  await processOrder(orderId);
} catch (error) {
  client.captureException(error, userId, {
    order_id: orderId,
    user_id: userId,
    payment_method: 'stripe',
    amount: 99.99,
    step: 'charge_card'
  });
}

// Avoid: No context
try {
  await processOrder(orderId);
} catch (error) {
  client.captureException(error);
}
```

### Use Distinct IDs When Available

```typescript
// Good: With user ID
try {
  await userOperation();
} catch (error) {
  client.captureException(error, req.user.id);
}

// Acceptable: Without user ID for system errors
try {
  await systemOperation();
} catch (error) {
  client.captureException(error); // Uses personless processing
}
```

### Don't Swallow Errors

```typescript
// Good: Capture and re-throw
try {
  await criticalOperation();
} catch (error) {
  client.captureException(error, userId);
  throw error; // Let caller handle it
}

// Avoid: Swallowing errors
try {
  await criticalOperation();
} catch (error) {
  client.captureException(error, userId);
  // Error is lost!
}
```

### Use Immediate Capture for Serverless

```typescript
// Good: Immediate capture in serverless
export async function handler(event) {
  try {
    return await process(event);
  } catch (error) {
    await client.captureExceptionImmediate(error, event.userId);
    throw error;
  }
}

// Avoid: Queued capture in serverless (may be lost)
export async function handler(event) {
  try {
    return await process(event);
  } catch (error) {
    client.captureException(error, event.userId); // May not send!
    throw error;
  }
}
```

### Enable Autocapture Judiciously

```typescript
// Good: Enable for production with proper monitoring
const client = new PostHog('phc_your_api_key', {
  enableExceptionAutocapture: true
});

// Good: Disable in development
const client = new PostHog('phc_your_api_key', {
  enableExceptionAutocapture: process.env.NODE_ENV === 'production'
});
```

### Add Severity Levels

```typescript
// Categorize errors by severity
try {
  await operation();
} catch (error) {
  const severity = error instanceof ValidationError ? 'low' :
                   error instanceof DatabaseError ? 'high' :
                   'medium';

  client.captureException(error, userId, {
    severity: severity,
    requires_action: severity === 'high'
  });
}
```

### Group Related Errors

```typescript
// Add grouping properties
try {
  await apiCall();
} catch (error) {
  client.captureException(error, userId, {
    error_group: 'external_api',
    service: 'stripe',
    endpoint: '/v1/charges'
  });
}
```

## Rate Limiting

Automatic exception capture includes rate limiting to prevent flooding:
- **Bucket Size**: 10 exceptions per error type
- **Refill Rate**: 1 exception per 10 seconds
- **Strategy**: Captures 10 exceptions of each type, then limits to 1 every 10 seconds

This prevents a single error from dominating your exception tracking while still capturing diverse error types.

## Personless Processing

When no `distinctId` is provided, exceptions use personless processing:
- Sets `$process_person_profile: false`
- Exception is tracked but not associated with a user profile
- Useful for system-level errors, background jobs, and autocapture
- Reduces person profile inflation

```typescript
// Personless processing (good for system errors)
client.captureException(error); // No distinctId

// User-associated processing (good for user actions)
client.captureException(error, 'user_123');
```
