Skip to main content
Proliferate automatically groups similar errors into Issues, making it easy to understand the scope and frequency of problems in your application.

How Grouping Works

When an error is captured, Proliferate generates a fingerprint—a unique identifier based on the error’s characteristics. Errors with the same fingerprint are grouped into the same Issue.

The Fingerprinting Algorithm

Proliferate uses a deterministic algorithm to generate fingerprints:
1

Extract error type

The exception class name (e.g., TypeError, ValueError)
2

Parse stack frames

Extract the first 3 frames from the stack trace
3

Build fingerprint source

Combine type and frames as ErrorType|func1@file1|func2@file2|func3@file3
4

Hash

Generate a SHA256 hash and take the first 16 characters
Input:
  Type: TypeError
  Stack:
    at onClick (app.js:42:15)
    at handleEvent (react-dom.js:1234:5)
    at dispatchEvent (react-dom.js:567:12)

Fingerprint source: "TypeError|[email protected]|[email protected]|[email protected]"

Result: "a3f2e1d4c5b6a7f8"

Why This Approach?

This means:
  • Refactoring code that doesn’t change the error won’t create a new Issue
  • Adding/removing unrelated code in the same file won’t split Issues
  • The same logical error stays grouped together across releases
This means:
  • Deep call stacks with the same root cause stay grouped
  • Framework-level differences in the call stack don’t split Issues

Viewing Grouped Errors

In the dashboard, you’ll see:
  • Issues: Unique error types grouped by fingerprint
  • Events: Individual occurrences of each Issue
Each Issue shows:
  • First and last seen timestamps
  • Total event count
  • Affected users and accounts
  • Trend over time

Stack Trace Parsing

Proliferate parses stack traces from both JavaScript and Python:
Error: Connection failed
    at fetchData (src/api.js:42:15)
    at onClick (src/components/Button.tsx:23:8)
    at HTMLButtonElement.dispatchEvent (<anonymous>)
Extracted frames:

Handling Different Error Scenarios

Same Error, Different Locations

Errors of the same type thrown from different code locations will be separate Issues:
// Issue A: TypeError|[email protected]|...
function validateEmail(email) {
  if (!email) throw new TypeError('Email required');
}

// Issue B: TypeError|[email protected]|...
function validatePhone(phone) {
  if (!phone) throw new TypeError('Phone required');  // Different Issue
}

Third-Party Library Errors

Errors from third-party libraries are grouped by where they’re called from your code:
// Issue A: AxiosError|[email protected]|...
async function fetchUsers() {
  return axios.get('/users');  // Network error here
}

// Issue B: AxiosError|[email protected]|...
async function fetchOrders() {
  return axios.get('/orders');  // Same error type, different Issue
}

Issue Management

Merging Issues

If you determine that two Issues are actually the same problem, you can merge them in the dashboard. The merged Issue will combine:
  • All events from both Issues
  • Affected users and accounts
  • Timeline data

Resolving Issues

Mark an Issue as resolved when you’ve fixed the underlying problem. If new events occur:
  • The Issue automatically reopens
  • You receive a notification (if configured)

Ignoring Issues

For known issues you can’t or don’t want to fix:
  • Mark the Issue as ignored
  • New events are still collected but won’t trigger alerts
  • You can unignore at any time

Best Practices

Custom error classes lead to better grouping:
class ValidationError extends Error {
  constructor(field, message) {
    super(message);
    this.name = 'ValidationError';
    this.field = field;
  }
}

throw new ValidationError('email', 'Invalid email format');
The error type matters more than the message:
// Good - same error type for all validation failures
throw new ValidationError(`${field} is invalid`);

// Also fine - message doesn't affect grouping
throw new ValidationError(`Email "${email}" is invalid`);
Preserve the original error type:
// Bad - loses original error type
try {
  await api.call();
} catch (e) {
  throw new Error('API call failed');
}

// Good - preserves error type
try {
  await api.call();
} catch (e) {
  throw e;  // Or wrap with original as cause
}
Full module paths help with grouping:
# Error type: myapp.errors.PaymentError
class PaymentError(Exception):
    pass