DEV Community

Cover image for Introducing StableError: A TypeScript Library for Consistent Error Tracking
Maxime Sahroui
Maxime Sahroui

Posted on

Introducing StableError: A TypeScript Library for Consistent Error Tracking

How a simple library can revolutionize your error monitoring and debugging workflow


The Problem with Traditional Error Tracking

Picture this: You're debugging a production issue where users are reporting "User not found" errors. You check your error tracking dashboard and see hundreds of similar errors, but they all have different IDs:

  • Error ID: a1b2c3d4 - "User 123 not found"
  • Error ID: e5f6g7h8 - "User 456 not found"
  • Error ID: i9j0k1l2 - "User 789 not found"

Even though these are essentially the same error (a user lookup failure), your monitoring system treats them as completely different issues. This makes it nearly impossible to:

  • Group related errors for effective analysis
  • Track error frequency accurately
  • Identify patterns in your application failures
  • Prioritize fixes based on real impact

Enter StableError: The Solution

StableError is a TypeScript library that solves this exact problem by generating stable, consistent error IDs based on the semantic content of your errors, not their variable parts.

The Magic: Same Error = Same ID

import { createStableError } from 'stable-error';

// These all generate the SAME error ID
const error1 = createStableError('User 123 not found', { 
  category: 'validation',
  metadata: { field: 'email' }
});

const error2 = createStableError('User 456 not found', { 
  category: 'validation',
  metadata: { field: 'email' }
});

const error3 = createStableError('USER 789 NOT FOUND', { 
  category: 'validation',
  metadata: { field: 'email' }
});

console.log(error1.id === error2.id); // true
console.log(error1.id === error3.id); // true
Enter fullscreen mode Exit fullscreen mode

How It Works: Intelligent Message Normalization

StableError uses sophisticated message normalization to ensure that semantically identical errors produce the same ID:

1. Number Normalization

// All of these become "user NUMBER not found"
"User 123 not found"
"User 456 not found" 
"User 999999 not found"
Enter fullscreen mode Exit fullscreen mode

2. UUID Normalization

// All of these become "user UUID not found"
"User 550e8400-e29b-41d4-a716-446655440000 not found"
"User 6ba7b810-9dad-11d1-80b4-00c04fd430c8 not found"
Enter fullscreen mode Exit fullscreen mode

3. Timestamp Normalization

// All of these become "error at TIMESTAMP"
"Error at 2023-01-01T10:00:00Z"
"Error at 2023-12-31T23:59:59Z"
"Error at 1672531200000" // Unix timestamp
Enter fullscreen mode Exit fullscreen mode

4. Case and Whitespace Normalization

// All of these become "user not found"
"User not found"
"USER NOT FOUND"
"  user   not   found  "
Enter fullscreen mode Exit fullscreen mode

Smart Metadata Filtering

Not all metadata should affect error ID generation. StableError only considers "stable" metadata keys that represent the error's semantic meaning:

Included in ID generation:

  • type, code, field, operation, service, component

Ignored (variable data):

  • userId, timestamp, sessionId, requestId
// These generate the SAME ID (userId and timestamp are ignored)
createStableError('Error', { 
  metadata: { 
    field: 'email', 
    userId: 123,           // Ignored
    timestamp: '2023-01-01' // Ignored
  }
});

createStableError('Error', { 
  metadata: { 
    field: 'email', 
    userId: 456,           // Ignored  
    timestamp: '2023-12-31' // Ignored
  }
});
Enter fullscreen mode Exit fullscreen mode

Real-World Usage Examples

1. API Error Handling

import { createStableError } from 'stable-error';

async function getUserById(id: string) {
  try {
    const user = await database.findUser(id);
    if (!user) {
      throw createStableError('User not found', {
        category: 'validation',
        statusCode: 404,
        severity: 'medium',
        metadata: { 
          field: 'userId',
          operation: 'user_lookup'
        }
      });
    }
    return user;
  } catch (error) {
    // Convert any unexpected errors to stable errors
    if (error instanceof Error && !error.id) {
      throw createStableError(error, {
        category: 'database',
        severity: 'high',
        metadata: { operation: 'user_lookup' }
      });
    }
    throw error;
  }
}
Enter fullscreen mode Exit fullscreen mode

2. Error Monitoring Integration

// With your favorite error tracking service
import { createStableError } from 'stable-error';

function trackError(error: Error, context: any) {
  const stableError = createStableError(error, {
    category: 'api',
    severity: 'high',
    metadata: {
      service: 'user-service',
      component: 'auth-middleware'
    }
  });

  // Send to your monitoring service
  errorTracker.captureException(stableError, {
    tags: {
      errorId: stableError.id,
      category: stableError.category,
      severity: stableError.severity
    },
    extra: stableError.metadata
  });
}
Enter fullscreen mode Exit fullscreen mode

3. Error Aggregation Dashboard

// Group errors by stable ID for analytics
const errorGroups = errors.reduce((groups, error) => {
  const stableError = createStableError(error);
  const id = stableError.id;

  if (!groups[id]) {
    groups[id] = {
      id,
      message: stableError.message,
      category: stableError.category,
      count: 0,
      firstSeen: stableError.timestamp,
      lastSeen: stableError.timestamp,
      severity: stableError.severity
    };
  }

  groups[id].count++;
  groups[id].lastSeen = stableError.timestamp;

  return groups;
}, {});
Enter fullscreen mode Exit fullscreen mode

Key Features

Stable Error IDs

  • Same error message + category + metadata = Same ID
  • 8-character hexadecimal IDs for easy reference

Full TypeScript Support

  • Complete type safety with comprehensive interfaces
  • IntelliSense support for all options and methods

Flexible Input

  • Works with string messages or existing Error objects
  • Preserves original stack traces when converting

Rich Error Information

  • Category classification
  • Severity levels (low, medium, high, critical)
  • HTTP status codes
  • Timestamps
  • Custom metadata

JSON Serialization

const error = createStableError('Test error', {
  category: 'validation',
  severity: 'high'
});

const json = error.toJSON();
// {
//   id: "a1b2c3d4",
//   message: "Test error", 
//   category: "validation",
//   severity: "high",
//   timestamp: "2023-01-01T10:00:00Z",
//   statusCode: 500,
//   metadata: {},
//   stack: "Error: Test error\n    at ..."
// }
Enter fullscreen mode Exit fullscreen mode

Installation and Quick Start

npm install stable-error
# or
yarn add stable-error
# or  
bun add stable-error
Enter fullscreen mode Exit fullscreen mode
import { createStableError } from 'stable-error';

// Create your first stable error
const error = createStableError('Something went wrong', {
  category: 'api',
  severity: 'high',
  metadata: { endpoint: '/users', method: 'GET' }
});

console.log(`Error ID: ${error.id}`); // Always the same for this error type
Enter fullscreen mode Exit fullscreen mode

Browser and Runtime Support

  • Modern browsers (ES2018+)
  • Node.js 14+
  • TypeScript 4.5+
  • Bun (fully tested)

Conclusion

StableError transforms error tracking from a chaotic mess into an organized, actionable system. By generating consistent IDs for semantically identical errors, it enables:

  • Better error analytics - See which errors actually matter
  • Faster debugging - Group related issues together
  • Improved prioritization - Focus on high-frequency problems
  • Cleaner dashboards - No more noise from variable data

Whether you're building a small application or managing a large-scale system, StableError provides the foundation for effective error monitoring and debugging. Give it a try and see how it can revolutionize your error tracking workflow.


Ready to get started? Check out the GitHub repository for full documentation, examples, and the latest updates.

StableError is open source and available under the MIT license.

Top comments (0)