Skip to content

Themaxkin/MaxError

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

2 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

MaxError

Enterprise-grade error handling for Node.js

Structured · Sanitized · Rate-limited · Observable

English | 中文

node version license tests coverage version ESM zero deps


Why MaxError?

In production environments, try/catch alone is never enough. You need:

  • Structured errors — not just strings, but objects with category, level, status code, context, and timestamps
  • Sensitive data protection — passwords, tokens, and card numbers must never leak into logs
  • Log storm prevention — one failing upstream service can generate millions of identical errors per minute
  • Observability — error statistics by category, status code, level, and time dimension
  • Standardized error codes — consistent error contracts across microservices
  • Express integration — structured JSON error responses with traceId propagation

MaxError solves all of these in a single, zero-external-dependency class (core library). It extends the native Error class, so it works everywhere a regular Error does.

Architecture Overview

┌─────────────────────────────────────────────────────────────────┐
│                        MaxError Instance                        │
├─────────────┬──────────────┬──────────────┬────────────────────┤
│  Constructor │   handle()   │  Recovery    │   Error Chain      │
│  ─ sanitize  │   ─ rate     │  ─ retry     │   ─ setCause()     │
│  ─ aggregate │     limit    │  ─ max       │   ─ getErrorChain()│
│  ─ stats     │   ─ log      │    attempts  │                    │
│  ─ fingerprint│  ─ respond  │              │                    │
├──────────────┴──────────────┴──────────────┴────────────────────┤
│                     Static Services                             │
├──────────┬───────────┬────────────┬──────────┬─────────────────┤
│ Error    │ Rate      │ Sanitizer  │ Aggreg-  │ Express         │
│ Codes    │ Limiter   │ (deep,     │ ation    │ Middleware      │
│ Registry │ (sliding  │  recursive)│ (LRU,    │ (error handler, │
│          │  window)  │            │  dedup)  │  traceId)       │
├──────────┴───────────┴────────────┴──────────┴─────────────────┤
│                   Statistics & Persistence                      │
│   ─ byCategory / byStatusCode / byLevel / byTime              │
│   ─ File or Database storage with debounce                     │
├────────────────────────────────────────────────────────────────┤
│                   Multi-channel Logging                         │
│   ─ Console (colored) / File (rotation) / Remote (HTTP)        │
└────────────────────────────────────────────────────────────────┘

Features

Feature Description
🏷️ Error Code System Register business error codes (AUTH_001, DB_002), create errors with MaxError.fromCode()
🔒 Auto Sanitization Deep recursive redaction of passwords, tokens, emails, phone numbers, card numbers
🚦 Rate Limiting Sliding window rate limiter per error fingerprint, prevents log storms
📊 Error Aggregation Fingerprint-based deduplication with LRU eviction, tracks occurrence count
🔌 Express Middleware One-line error handler + traceId injection middleware
📈 Statistics Multi-dimensional stats (category/status/level/time), file or DB persistence
🔄 Recovery Configurable retry strategies with max attempt limits
⛓️ Error Chaining Preserve root cause with setCause(), trace with getErrorChain()
🎯 Filtering Filter errors by category, level, or status code
📝 Multi-channel Logging Console (colored), file (with rotation), remote HTTP
⏱️ Performance Metrics Collect operation duration and timing data
🌐 Network Responses Auto-respond with structured JSON for HTTP errors

Installation

git clone https://github.com/user/max-error.git
cd max-error
pnpm install

Quick Start

import { MaxError, ErrorCategory, ErrorLevel } from './js/MaxError.js';

// Create a structured error
const error = new MaxError(
    'User query failed',          // custom message
    'User not found: u_123',      // original message
    404,                          // status code
    { userId: 'u_123' },         // details (auto-sanitized)
    {
        category: ErrorCategory.DATABASE,
        level: ErrorLevel.ERROR,
        captureTimestamp: true,
        captureStackTrace: true,
        captureContext: true,
        initialContext: { sessionId: 'sess_abc' },
    }
);

// Handle: log + optional HTTP response
error.handle(null, 'quote', { type: 'iso' });

Console output:

 2025-01-15T08:30:00.000Z|MaxError  {
  time: '2025-01-15T08:30:00.000Z',
  message: 'User query failed',
  originalMessage: 'User not found: u_123',
  statusCode: 404,
  details: { userId: 'u_123' },
  category: 'database',
  level: 3,
  timestamp: 1736930400000,
  context: { sessionId: 'sess_abc', timestamp: 1736930400000 },
  errorFile: { fileName: 'app.js', lineNumber: 12, columnNumber: 15 }
}

API Reference

Constructor

new MaxError(message, originalMessage, statusCode, details, options)
Parameter Type Default Description
message string null Custom display message
originalMessage string Original error message (used as Error.message)
statusCode number HTTP status code
details object {} Additional error details (auto-sanitized if enabled)
options object {} Configuration options (see below)

Options

Option Type Default Description
category string 'system' Error category (from ErrorCategory)
level number 3 Error level (from ErrorLevel)
captureStackTrace boolean config Capture file name, line, column
captureTimestamp boolean config Attach Date.now() timestamp
captureEnvironment boolean config Capture NODE_ENV
captureContext boolean config Enable context object
captureMetrics boolean config Enable metrics collection
initialContext object {} Initial context key-value pairs

Static Methods

Method Returns Description
MaxError.fromCode(code, details?, options?) MaxError Create error from registered error code
MaxError.registerErrorCode(code, definition) void Register a single error code
MaxError.registerErrorCodes(registry) void Batch register error codes
MaxError.getAllErrorCodes() Map Get all registered codes (runtime + config)
MaxError.getErrorCodeDefinition(code) object|null Get definition for a specific code
MaxError.loadConfig(config) void Merge new configuration
MaxError.sanitize(data) * Manually sanitize any data
MaxError.isRateLimited(fingerprint) boolean Check if fingerprint is rate-limited
MaxError.generateFingerprint(error) string Generate error fingerprint
MaxError.aggregate(error, fingerprint) object Record error in aggregation window
MaxError.getAggregation(fingerprint?) object|Map Get aggregation stats
MaxError.expressMiddleware(options?) function Express error handler middleware
MaxError.expressRequestContext() function Express traceId injection middleware
MaxError.resetStats() void Clear all stats, rate limit, and aggregation data
MaxError.saveStats() Promise Persist stats to file or database
MaxError.initializeStats(forceRefresh?) Promise Load stats from storage
MaxError.cleanup() Promise Graceful shutdown (save + stop timers)

Instance Methods

Method Returns Description
handle(res?, typeTime, eventTime, options?) void Log error, optionally send HTTP response
addContext(key, value) this Add context key-value pair
getContext(key) * Get context value
setCause(error) this Set error cause (chaining)
getErrorChain() Array Get full cause chain
collectMetrics(operation, startTime, endTime) this Record performance metrics
registerRecovery(fn, maxAttempts?) this Register retry strategy
attemptRecovery() Promise<boolean> Execute one recovery attempt
matchesFilter(filter) boolean Check if error matches filter criteria
getDate(type, eventTime) string|number Format current time

Instance Properties

Property Type Description
customMessage string Custom display message
originalMessage string Original error message
statusCode number HTTP status code
details object Error details (sanitized)
category string Error category
level number Error level
fingerprint string Error fingerprint for dedup
rateLimited boolean Whether this error was rate-limited
aggregationCount number How many times this error has occurred
isFirstOccurrence boolean Whether this is the first occurrence
errorCode string Business error code (if created via fromCode)
timestamp number Creation timestamp (if captured)
environment string NODE_ENV value (if captured)
context object Context object (if captured)
metrics object Performance metrics (if captured)
fileName string Source file (if stack captured)
lineNumber number Source line (if stack captured)
columnNumber number Source column (if stack captured)

Error Code System

Define and reuse business error codes across your application:

// Register error codes
MaxError.registerErrorCodes({
    AUTH_001: { message: 'Not authenticated', statusCode: 401, category: 'authentication', level: 2 },
    AUTH_002: { message: 'Token expired',     statusCode: 401, category: 'authentication', level: 2 },
    DB_001:  { message: 'Connection failed',  statusCode: 500, category: 'database',       level: 4 },
    BIZ_001: { message: 'Insufficient funds', statusCode: 400, category: 'business',       level: 2 },
});

// Create errors from codes
const error = MaxError.fromCode('AUTH_001', { endpoint: '/api/orders' });
console.log(error.statusCode);    // 401
console.log(error.category);      // 'authentication'
console.log(error.errorCode);     // 'AUTH_001'

// You can also define codes in MaxError.config.js → errorCodes.registry
// They are auto-registered when config loads

Sensitive Data Sanitization

Automatically redacts sensitive fields and patterns before logging:

const error = new MaxError('Payment failed', 'err', 500, {
    username: 'john',
    password: 'secret123',           // → '******'
    card: '6222021234567890123',     // → '***CARD***'
    contact: 'john@example.com',    // → '***EMAIL***'
    phone: '13812345678',           // → '***PHONE***'
    nested: {
        token: 'abc123',            // → '******' (deep recursive)
        data: {
            ssn: '123-45-6789',     // → '******'
        }
    }
});

// Manual sanitization
const clean = MaxError.sanitize({ password: 'x', email: 'a@b.com' });

Sanitizer configuration in MaxError.config.js:

Option Type Description
enabled boolean Enable/disable auto-sanitization
fields string[] Field names to redact (case-insensitive)
patterns Array<{regex, replacement}> Regex patterns for value-level redaction
replacement string Default replacement text ('******')
customSanitizer function|null Custom sanitizer function (data) => sanitizedData

Rate Limiting

Sliding window rate limiter prevents log storms from repeated errors:

MaxError.loadConfig({
    rateLimiting: {
        enabled: true,
        windowMs: 60000,       // 1 minute window
        maxPerWindow: 10,      // max 10 per fingerprint per window
        onRateLimited: (fingerprint, count) => {
            console.warn(`Rate limited: ${fingerprint} (${count} in window)`);
        },
    },
});

// Errors beyond the limit are automatically skipped in handle()
for (let i = 0; i < 20; i++) {
    const err = new MaxError('Same error', 'msg', 500, {});
    err.handle(null, 'quote', { type: 'iso' });
    // Only the first 10 will actually log
}
Option Type Default Description
enabled boolean false Enable rate limiting
windowMs number 60000 Sliding window size in ms
maxPerWindow number 100 Max errors per fingerprint per window
onRateLimited function|null null Callback (fingerprint, count) => {}

Error Aggregation

Deduplicates identical errors and tracks occurrence count:

MaxError.loadConfig({
    aggregation: {
        enabled: true,
        windowMs: 60000,
        maxFingerprints: 1000,    // LRU eviction when exceeded
        onAggregated: (fingerprint, count, firstError) => {
            if (count % 100 === 0) {
                alert(`Error ${fingerprint} occurred ${count} times!`);
            }
        },
    },
});

const err1 = new MaxError('DB timeout', 'timeout', 500, {});
const err2 = new MaxError('DB timeout', 'timeout', 500, {});
console.log(err2.aggregationCount);  // 2
console.log(err2.isFirstOccurrence); // false

// Query aggregation data
const agg = MaxError.getAggregation(err1.fingerprint);
console.log(agg.count);  // 2

Express Middleware

One-line integration for Express applications:

import express from 'express';
import { MaxError } from './js/MaxError.js';

const app = express();

// 1. Inject traceId into every request
app.use(MaxError.expressRequestContext());

// 2. Your routes
app.get('/api/users/:id', (req, res) => {
    throw MaxError.fromCode('USER_NOT_FOUND', { userId: req.params.id });
});

// 3. Error handler (must be last)
app.use(MaxError.expressMiddleware({
    exposeDetails: process.env.NODE_ENV !== 'production',
    onError: null,  // or custom handler: (error, req, res) => {}
}));

app.listen(3000);

Response format:

{
    "success": false,
    "error": {
        "message": "User not found",
        "code": "USER_NOT_FOUND",
        "category": "database"
    }
}

With exposeDetails: true (development):

{
    "success": false,
    "error": {
        "message": "User not found",
        "code": "USER_NOT_FOUND",
        "category": "database",
        "details": { "userId": "123" },
        "stack": "MaxError: User not found\n    at ..."
    }
}

Recovery Strategies

Register automatic retry logic for recoverable errors:

const error = new MaxError('Service unavailable', 'timeout', 503, {});

error.registerRecovery(async () => {
    const response = await fetch('https://api.example.com/health');
    if (!response.ok) throw new Error('Still down');
}, 3); // max 3 attempts

let recovered = false;
while (!recovered && error.recovery.attempts < error.recovery.maxAttempts) {
    recovered = await error.attemptRecovery();
    if (!recovered) {
        await new Promise(r => setTimeout(r, 1000)); // wait 1s between retries
    }
}
console.log(recovered ? 'Service recovered' : 'Service still down');

Error Chaining

Preserve the full error cause chain for debugging:

try {
    await db.query('SELECT ...');
} catch (dbError) {
    const error = new MaxError('Query failed', dbError.message, 500, {
        query: 'SELECT ...',
    });
    error.setCause(dbError);

    const chain = error.getErrorChain();
    // chain = [MaxError('Query failed'), Error('ECONNREFUSED')]

    chain.forEach((err, i) => {
        console.log(`${i === 0 ? '→' : '  └─'} ${err.message}`);
    });
    // → Query failed
    //   └─ connect ECONNREFUSED 127.0.0.1:3306
}

Error Filtering

Filter errors by category, level, or status code:

const error = new MaxError('Not found', 'msg', 404, {}, {
    category: 'database',
    level: 3,
});

error.matchesFilter({ categories: ['database', 'network'] }); // true
error.matchesFilter({ levels: [4] });                          // false
error.matchesFilter({ statusCodes: [404, 500] });              // true
error.matchesFilter({ categories: ['system'], levels: [3] });  // false

Statistics & Persistence

Error stats are updated automatically on every new MaxError():

console.log(MaxError.errorStats);
// {
//   total: 42,
//   byCategory: { system: 10, network: 20, database: 12 },
//   byStatusCode: { '500': 22, '404': 15, '403': 5 },
//   byLevel: { '2': 10, '3': 25, '4': 7 },
//   byTime: {
//     byMinute: { '2025-01-15 08:30': 3, ... },
//     byHour:   { '2025-01-15 08': 15, ... },
//     byDay:    { '2025-01-15': 42 }
//   }
// }

// Manual save / load
await MaxError.saveStats();
await MaxError.initializeStats(true);

// Reset everything
MaxError.resetStats();

// Graceful shutdown
process.on('SIGTERM', async () => {
    await MaxError.cleanup();
    process.exit(0);
});

Persistence configuration:

Option Type Default Description
enabled boolean true Enable persistence
storageType string 'file' 'file' or 'database'
filePath string './data/error-stats.json' File path for file storage
interval number 300000 Auto-save interval (ms)
skipUnchanged boolean true Skip save if stats unchanged
immediateSave boolean true Save on every error (debounced)
dbConnection object|null null MySQL connection object
tableName string 'error_stats' Database table name

Multi-channel Logging

// Console (default) — colored output
error.handle(null, 'quote', { type: 'iso' });

// File — with automatic rotation
error.handle(null, 'quote', { type: 'iso' }, {
    outputType: 'file',
    filePath: './logs/error.log',
    formatJson: true,
});

// Remote HTTP — with timeout and failure callback
error.handle(null, 'quote', { type: 'iso' }, {
    outputType: 'remote',
});

Log rotation config:

Option Type Default Description
maxFileSize number 10MB Max file size before rotation
backupCount number 5 Number of backup files to keep
onError function null Custom error callback

Configuration Reference

All features are configurable via js/MaxError.config.js or MaxError.loadConfig().

// Runtime config override
MaxError.loadConfig({
    rateLimiting: { enabled: true, maxPerWindow: 50 },
    sanitizer: { enabled: true },
    aggregation: { enabled: true },
});

Top-level config sections:

Section Description
categories Error category enum definitions
levels Error level enum definitions
defaultCaptureOptions Default capture flags for constructor
defaultLogConfig Default logging output type and format
networkErrorConfig Network error response format
performance Metrics collection and max recovery attempts
logRetention File rotation settings
remoteLogging Remote HTTP logging endpoint and timeout
defaultContext Default context fields
rateLimiting Rate limiter configuration
sanitizer Sensitive data sanitization rules
aggregation Error aggregation settings
errorCodes Error code registry
middleware Express middleware settings
statsPersistence Statistics persistence settings

Project Structure

max-error/
├── js/
│   ├── MaxError.js           # Core library (1650 lines)
│   ├── MaxError.config.js    # Full configuration with comments
│   └── MaxError.test.js      # 135 unit tests (vitest)
├── examples/
│   ├── 01-basic-usage.js              # Constructor, context, metrics
│   ├── 02-error-codes.js             # Error code registration & usage
│   ├── 03-sanitizer.js               # Sanitization demos
│   ├── 04-rate-limit-and-aggregation.js  # Rate limiting & aggregation
│   ├── 05-express-middleware.js       # Express integration
│   └── 06-recovery-and-chain.js       # Recovery & error chaining
├── data/
│   └── error-stats.json      # Persisted error statistics
├── package.json
└── vitest.config.js

Running Examples

node examples/01-basic-usage.js
node examples/02-error-codes.js
node examples/03-sanitizer.js
node examples/04-rate-limit-and-aggregation.js
node examples/06-recovery-and-chain.js

# Express example (requires: pnpm add express)
node examples/05-express-middleware.js

Testing

pnpm test           # Run all 135 tests
pnpm test:watch     # Watch mode

Test coverage includes: exports, constructor, config validation, statistics, time keys, context/error chain, metrics collection, recovery strategies, error filtering, date formatting, logging, handle method, stack parsing, persistence, rate limiting, sanitization, aggregation, error codes, Express middleware.

Comparison with Other Libraries

Feature MaxError http-errors boom verror
Error categorization
Error codes
Auto sanitization
Rate limiting
Error aggregation
Statistics
Express middleware
Error chaining
Recovery strategies
Multi-channel logging
Context tracking
Performance metrics

Contributing

  1. Fork the repository
  2. Create your feature branch (git checkout -b feature/amazing-feature)
  3. Write tests for your changes
  4. Ensure all tests pass (pnpm test)
  5. Commit your changes (git commit -m 'feat: add amazing feature')
  6. Push to the branch (git push origin feature/amazing-feature)
  7. Open a Pull Request

Changelog

v1.0.0

  • Initial release
  • Core error handling with category, level, status code
  • Error code system with registerErrorCode / fromCode
  • Sensitive data sanitization (field + regex pattern)
  • Sliding window rate limiting
  • Fingerprint-based error aggregation with LRU eviction
  • Express error handler and traceId middleware
  • Multi-channel logging (console, file with rotation, remote HTTP)
  • Statistics with multi-dimensional tracking and persistence
  • Recovery strategies with configurable retry
  • Error chaining with setCause / getErrorChain
  • 135 unit tests

License

MIT © MaxKin

About

企业级 Node.js 错误处理方案

Resources

License

Stars

Watchers

Forks

Releases

No releases published

Packages

 
 
 

Contributors