or run

npx @tessl/cli init
Log in

Version

Tile

Overview

Evals

Files

docs

client-configuration.mderror-tracking.mdevent-tracking.mdexperimental.mdexpress-integration.mdfeature-flags.mdidentification.mdindex.mdsentry-integration.md
tile.json

error-tracking.mddocs/

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.

/**
 * 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:

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.

/**
 * 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:

// 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

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:

// 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

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

// 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

// 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

// 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

// 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

// 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

// 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

// 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

// 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

// 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

// 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

// 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

// 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

// 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

// 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
// Personless processing (good for system errors)
client.captureException(error); // No distinctId

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