Skip to content

The most colorful TS / JS logging library

License

manicinc/magiclogger

Folders and files

NameName
Last commit message
Last commit date

Latest commit

Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 

Repository files navigation

MagicLogger

MagicLog

🌐 Documentation & Website β€’ πŸ“š API Reference

core_gzip core_console_gzip core_transports_gzip

CI Release Docs
GitHub Stars npm version codecov
TypeScript Node.js License

🎬 See MagicLogger in Action

MagicLogger Terminal Demo

πŸš€ Universal Color Logging Standard

MagicLogger is a TypeScript logger that works in Node and browser built on a universal color logging standard that preserves styled text across any language, transport, or platform.

Traditional prod environments suppress / strip styling / pretty print in logs, dropping presumed unnecessary bundling and load.

Using this library generally means you're okay with these assumptions:

  • Storage is cheap, some extra kb in many web apps makes little difference (if you don't care about an image being 1.1 vs 1.0 mb this likely applies)
  • Some logs sent in production will require human review consistently
  • When you analyze logs at a high-level you want to have a visually appealing experience

Table of Contents

Installation

npm install magiclogger
# or
yarn add magiclogger
# or
pnpm add magiclogger

Supports both ESM and CommonJS:

import { Logger } from 'magiclogger';          // ESM/TypeScript
const { Logger } = require('magiclogger');     // CommonJS

Quick Start

import { Logger, SyncLogger, createLogger, createSyncLogger } from 'magiclogger';

// AsyncLogger (default) - non-blocking, faster for styled output
const logger = new Logger();  // Uses AsyncLogger by default
logger.info('Application started');

// Logger with automatic console transport (enabled by default)
const logger = new Logger({
  useConsole: true,   // Console transport is enabled by default (can be disabled with false)
  useColors: true,    // Enable colored output (default: true)
  verbose: false      // Show debug messages (default: false)
});

// Or simply use with defaults - console is automatically enabled
const logger = new Logger();  // Console transport created automatically

// SyncLogger - only for audit logs that MUST never be dropped
// Use ONLY when you need absolute guarantee of delivery under extreme load
// AsyncLogger is recommended for 99.9% of use cases
const auditLogger = new SyncLogger({
  file: './audit.log',     // Blocks until written to disk
  forceFlush: true         // For regulatory compliance
});

Styling APIs

Choose the styling approach that fits your code style:

import { Logger } from 'magiclogger';
const logger = new Logger();

// 1. Inline angle brackets - simplest, works everywhere
logger.info('<green.bold>βœ… Success:</> User <cyan>john@example.com</> authenticated');

// 2. Template literals - clean interpolation
logger.info(logger.fmt`@red.bold{ERROR:} Failed to connect to @yellow{${database}}`);

// 3. Chainable API - programmatic styling
logger.info(logger.s.blue.bold('INFO:') + ' Processing ' + logger.s.cyan(filename));

Key Features

Structured Logging with NDJSON

MagicLogger supports NDJSON (Newline Delimited JSON) format: each log entry is a complete JSON object on its own line.

// Configure NDJSON output
const logger = new Logger({
  transports: [
    new FileTransport({
      filepath: './logs/app.log',
      format: 'json'  // NDJSON format
    })
  ]
});

// Output (each line is a complete JSON object):
// {"id":"abc123","timestamp":"2024-01-20T10:30:00Z","level":"info","message":"Server started","context":{"port":3000}}
// {"id":"def456","timestamp":"2024-01-20T10:30:01Z","level":"error","message":"Database error","error":{"message":"Connection refused"}}

Structured JSON with Optional Validation

Every log outputs structured JSON following the MAGIC Schema:

// With optional schema validation (lazy-loaded)
const logger = new Logger({
  schemas: {
    user: z.object({
      userId: z.string().uuid(),
      email: z.string().email()
    })
  },
  onSchemaViolation: 'warn'  // 'warn' | 'error' | 'throw'
});

logger.info('User login', { tag: 'user', userId: '550e8400...', email: 'john@example.com' });

🎯 Tagging & Theming

Auto-style logs based on semantic tags:

const logger = new Logger({
  theme: {
    tags: {
      api: ['cyan', 'bold'],
      database: ['yellow'],
      critical: ['white', 'bgRed', 'bold']
    }
  }
});

logger.info('Request received', { tags: ['api'] });  // Auto-styled

Async vs Sync Loggers

AsyncLogger (default) - Non-blocking, 120K+ ops/sec with styles. Use for 99.9% of cases.

SyncLogger - Blocks until written to disk. Only for regulatory/audit requirements.

// For critical logs that must never be lost
const auditLogger = new SyncLogger({ file: './audit.log' });

// Or ensure graceful shutdown
process.on('SIGTERM', async () => {
  await logger.close();  // Flushes all pending logs
  process.exit(0);
});

Note: See Performance Design for architecture details and Advanced Usage for production configurations.

MAGIC Schema - Universal Styled Logging Standard

The MAGIC Schema is a universal JSON format that preserves text styling across any language, transport, or platform. Any language can produce MAGIC-compliant logs that MagicLogger (or any MAGIC-compatible system) can ingest and display with full color preservation.

The MAGIC Schema provides:

  • Style Preservation: Colors and formatting survive serialization as structured data
  • Language Agnostic: Any language can implement the MAGIC producer specification
  • Ingestion Ready: MagicLogger can consume logs from any MAGIC-compliant source
  • Consistent Structure: Same JSON format across all languages and transports
  • OpenTelemetry Ready: Direct compatibility with OTLP (OpenTelemetry Protocol)
  • Distributed Tracing: Built-in W3C Trace Context support
  • Rich Metadata: Automatic capture of system, process, and environment info

Schema Structure

Every log entry outputs this JSON structure:

logger.info('User authenticated', { 
  userId: 'u_123', 
  method: 'OAuth',
  provider: 'google'
});

Produces:

{
  "id": "1733938475123-abc123xyz",
  "timestamp": "2025-08-14T12:34:35.123Z",
  "timestampMs": 1765769675123,
  "level": "info",
  "message": "User authenticated",      // Plain text message
  "styles": [[0, 4, "green.bold"]],     // Preserved styling info
  "service": "auth-api",
  "environment": "production",
  "loggerId": "api-service",
  "context": { 
    "userId": "u_123",
    "method": "OAuth",
    "provider": "google"
  },
  "metadata": {
    "hostname": "api-server-01",
    "pid": 12345,
    "platform": "linux",
    "nodeVersion": "v20.10.0"
  },
  "trace": {
    "traceId": "0af7651916cd43dd8448eb211c80319c",
    "spanId": "b7ad6b7169203331"
  }
}

OpenTelemetry Compatibility

The MAGIC Schema maps directly to OpenTelemetry's log data model:

// MAGIC fields β†’ OTLP mapping
{
  "id"           β†’ attributes["log.id"]
  "timestamp"    β†’ timeUnixNano  
  "level"        β†’ severityNumber
  "message"      β†’ body
  "trace.traceId" β†’ traceId (root level)
  "trace.spanId"  β†’ spanId (root level)
  "service"      β†’ resource.attributes["service.name"]
}

Full Documentation: See the MAGIC Schema Specification for complete field definitions, examples, and integration guides.

🌍 Cross-Language SDK Compatibility

The MAGIC Format Specification

The MAGIC Schema is an open specification that any language can implement:

{
  "id": "unique-identifier",
  "timestamp": "2024-01-15T10:30:00.000Z",
  "timestampMs": 1705316400000,
  "level": "info|warn|error|debug|trace|fatal",
  "message": "Plain text without formatting",
  "styles": [
    [0, 6, "red.bold"],      // Apply red.bold to characters 0-6
    [12, 28, "cyan"]         // Apply cyan to characters 12-28
  ]
}
import { Logger } from 'magiclogger';

const logger = new Logger();

// Input with styles
logger.error('<red.bold>Error:</> Database <yellow>timeout</>');

// Outputs MAGIC JSON
{
  "message": "Error: Database timeout",
  "styles": [[0, 6, "red.bold"], [16, 23, "yellow"]],
  // ... other fields
}

// Can reconstruct styled output
import { applyStyles } from 'magiclogger';
const styled = applyStyles(entry.message, entry.styles);
console.log(styled); // Shows with colors!

MAGIC Compliance Requirements

For a logger to be MAGIC-compliant, it must:

  1. Output Valid MAGIC Schema JSON

    {
      "message": "Error: Database connection failed",
      "styles": [[0, 6, "red.bold"], [7, 35, "yellow"]],
      "level": "error",
      "timestamp": "2024-01-15T10:30:00.000Z"
    }
  2. Preserve Style Information

    • Extract styles from markup (e.g., <red>text</>)
    • Store as [startIndex, endIndex, style] tuples
    • Keep plain text in message field
  3. Include Required Fields

    • id, timestamp, level, message
    • Optional but recommended: service, environment, trace

Transports

Core Transports

import { 
  ConsoleTransport,
  FileTransport,       // High-performance sonic-boom (default)
  WorkerFileTransport, // Worker thread isolation
  SyncFileTransport,   // Synchronous with buffering
  HTTPTransport,
  WebSocketTransport
} from 'magiclogger/transports';

// Logger automatically uses high-performance file transport
const logger = new Logger({
  file: './logs/app.log'  // Uses FileTransport (sonic-boom) automatically
});

// Or explicitly configure transports
const logger = new Logger({
  transports: [
    // Console with colors (optional - added by default if no transports specified)
    new ConsoleTransport({ useColors: true }),
    
    // FileTransport - recommended default (sonic-boom)
    new FileTransport({ 
      filepath: './logs/app.log',
      minLength: 4096,  // Buffer size before auto-flush
      maxWrite: 16384   // Max bytes per write
    }),
    
    // HTTP with batching
    new HTTPTransport({ 
      url: 'https://logs.example.com',
      batch: { size: 100, timeout: 5000 }
    })
  ]
});

Advanced Transports

These are optional dependencies.

// Database transports
import { PostgreSQLTransport, MongoDBTransport } from 'magiclogger/transports';

// Cloud storage
import { S3Transport } from 'magiclogger/transports';

// Messaging systems
import { KafkaTransport, SyslogTransport } from 'magiclogger/transports';

// Observability platforms
import { OTLPTransport } from 'magiclogger/transports/otlp';

const otlpTransport = new OTLPTransport({
  endpoint: 'http://localhost:4318',
  serviceName: 'my-service',
  includeTraceContext: true  // W3C Trace Context support
});

Console Transport Behavior

By default, MagicLogger automatically creates a console transport unless explicitly disabled:

// Default behavior - console transport is automatically created
const logger = new Logger();  // Console output enabled

// Explicitly disable console for file-only logging (better performance)
const fileOnlyLogger = new Logger({
  useConsole: false,  // Disable automatic console transport
  transports: [
    new FileTransport({ 
      filepath: './app.log',
      buffer: { size: 1000 }  // Buffer for better write performance
    })
  ]
});

// Production setup - disable console
const prodLogger = new Logger({
  useConsole: false,  // No console overhead in production
  transports: [
    new HTTPTransport({ 
      url: process.env.LOG_ENDPOINT,
      batch: { size: 1000, timeout: 10000 }
    }),
    new S3Transport({ 
      bucket: 'logs',
      compress: true
    })
  ]
});

Advanced Features

Visual Elements

// Headers and separators
logger.header('πŸš€ DEPLOYMENT PROCESS');
logger.separator('=', 50);

// Progress bars
for (let i = 0; i <= 100; i += 10) {
  logger.progressBar(i);
  await delay(100);
}

// Tables
logger.table([
  { name: 'API', status: 'healthy', cpu: '12%' },
  { name: 'Database', status: 'healthy', cpu: '45%' }
]);

// Object diffs
logger.diff('State change', oldState, newState);

🎨 Theming & Custom Colors

Theme System

MagicLogger's theme system provides consistent, semantic styling across your application.

Built-in Themes

const logger = new Logger({ theme: 'ocean' });
// Available themes: ocean, forest, sunset, minimal, cyberpunk, dark, default

// Each theme provides consistent colors for semantic log types
logger.info('Information');     // Themed as info style
logger.success('Completed');    // Themed as success style
logger.warning('Caution');      // Themed as warning style
logger.error('Failed');         // Themed as error style

Custom Theme Definition

const logger = new Logger({
  theme: {
    // Log level styles
    info: ['cyan'],
    success: ['green', 'bold'],
    warning: ['yellow'],
    error: ['red', 'bold'],
    debug: ['gray', 'dim'],
    
    // UI element styles
    header: ['brightWhite', 'bold', 'underline'],
    footer: ['gray', 'dim'],
    separator: ['blue'],
    highlight: ['brightYellow', 'bold'],
    muted: ['gray', 'dim'],
    
    // Custom semantic styles
    api: ['cyan', 'bold'],
    database: ['yellow'],
    cache: ['magenta'],
    network: ['blue'],
    security: ['red', 'bold', 'underline']
  }
});

Tag-Based Theming

Combine themes with tags for automatic styling based on log categories:

const logger = new Logger({
  theme: {
    tags: {
      'api': ['cyan', 'bold'],
      'api.request': ['cyan'],
      'api.response': ['brightCyan'],
      'database': ['yellow'],
      'database.query': ['yellow', 'dim'],
      'database.error': ['red', 'bold'],
      'security': ['red', 'bold', 'bgYellow'],
      'performance': ['magenta', 'bold']
    }
  }
});

// Tags automatically apply themed styles
logger.info('Request received', { tags: ['api', 'api.request'] });
logger.error('Query timeout', { tags: ['database', 'database.error'] });
logger.warn('Unauthorized access attempt', { tags: ['security'] });

Custom Colors (Advanced)

MagicLogger supports custom color registration for brand-specific palettes and advanced terminal features.

Registering Custom Colors

// Register individual custom color
logger.registerCustomColor('brandPrimary', {
  hex: '#FF5733',        // 24-bit RGB color (for modern terminals)
  fallback: 'orange'     // Required fallback for limited terminals
});

// Register multiple custom colors
logger.registerCustomColors({
  // Using RGB values
  brandBlue: { 
    rgb: [51, 102, 255],
    fallback: 'blue',
    description: 'Primary brand blue'
  },
  
  // Using 256-color palette
  darkOlive: { 
    code256: 58,        // 256-color palette code
    fallback: 'green',
    description: 'Secondary accent color'
  },
  
  // Direct ANSI sequence (advanced)
  brandGradient: {
    ansi: '\x1b[38;2;255;87;51m', // Direct ANSI escape
    fallback: 'red'
  }
});

Using Custom Colors

// In themes
logger.setTheme({
  header: ['brandPrimary', 'bold'],
  success: ['brandBlue'],
  accent: ['darkOlive', 'italic']
});

// With style factories
const brand = logger.color('brandPrimary', 'bold');
const accent = logger.color('darkOlive');

logger.info(`Welcome to ${brand('Our Product')} - ${accent('v2.0')}`);

// In styled messages
logger.info('<brandPrimary.bold>Important:</> Check the <brandBlue>dashboard</>');

πŸ“Š Context & Tags

MagicLogger provides powerful context and tagging features for structured logging, enabling better log organization, filtering, and analysis.

Context - Structured Metadata

Context allows you to attach structured data to log entries, providing rich metadata for debugging and monitoring.

Global vs Per-Log Context

// Global context - applied to all logs from this logger
const logger = new Logger({
  id: 'payment-service',
  context: {
    service: 'payment-api',
    version: '2.1.0',
    environment: process.env.NODE_ENV,
    region: 'us-east-1',
    instanceId: process.env.INSTANCE_ID
  }
});

// Per-log context - specific to individual log entries
logger.info('Payment processed', {
  orderId: 'ORD-12345',
  customerId: 'CUST-67890',
  amount: 99.99,
  currency: 'USD',
  processingTime: 145,
  paymentMethod: 'credit_card'
});

// Context merging - per-log overrides global
logger.info('Special payment', {
  amount: 199.99,
  version: '2.2.0',  // Overrides global version
  promotional: true   // Adds new field
});

Using the meta() Helper

When using console-like variadic arguments, wrap context to prevent it from being printed:

import { meta } from 'magiclogger';

// Without meta() - context gets printed to console
logger.info('User logged in', { userId: '123' });
// Output: User logged in { userId: '123' }

// With meta() - context is attached but not printed
logger.info('User logged in', meta({ userId: '123' }));
// Output: User logged in
// Context still available in structured output/transports

Advanced Context Management

import { ContextManager } from 'magiclogger';

const contextManager = new ContextManager({
  // Auto-redact sensitive fields
  sensitiveKeys: ['password', 'token', 'ssn', 'creditCard'],
  
  // Transform nested keys to flat structure
  transformRules: {
    'user.id': 'userId',
    'request.id': 'requestId',
    'response.time': 'responseTime'
  },
  
  // Validation rules
  maxDepth: 3,
  maxSize: 1000, // bytes
  forbidden: ['__proto__', 'constructor']
});

// Sanitize sensitive data automatically
const userContext = {
  userId: '123',
  email: 'user@example.com',
  password: 'secret123',  // Will be redacted
  creditCard: '4111111111111111'  // Will be redacted
};

const sanitized = contextManager.sanitize(userContext);
// Result: { 
//   userId: '123', 
//   email: 'user@example.com', 
//   password: '***', 
//   creditCard: '***' 
// }

Tags - Categorical Labels

Tags are simple string labels for categorizing and filtering logs, enabling powerful log organization and styling.

Basic Tag Usage

// Global tags - applied to all logs
const logger = new Logger({
  tags: ['api', 'production', 'v2']
});

// All logs include these tags
logger.info('Server started');  // Tags: ['api', 'production', 'v2']
logger.error('Database error'); // Tags: ['api', 'production', 'v2']

// Per-log tags - additional categorization
logger.info('User authenticated', { 
  tags: ['auth', 'oauth', 'google'] 
});
// Combined tags: ['api', 'production', 'v2', 'auth', 'oauth', 'google']

Hierarchical Tags

MagicLogger supports hierarchical tag organization using both dot notation and explicit parent-child relationships:

const logger = new Logger({
  tags: ['api.v2']
});

// Dot notation - automatic hierarchy
logger.info('Database query', {
  tags: ['database.query.select', 'performance.slow']
});

// Results in tags that can be filtered at any level:
// - 'api.v2' (matches: api, api.v2)
// - 'database.query.select' (matches: database, database.query, database.query.select)
// - 'performance.slow' (matches: performance, performance.slow)

// Explicit parent-child relationships
logger.info('User action', {
  tags: [
    { name: 'user', children: ['auth', 'profile'] },
    { name: 'api', children: ['request', 'response'] }
  ]
});
// Generates: ['user', 'user.auth', 'user.profile', 'api', 'api.request', 'api.response']

// Path-based tag generation
import { TagManager } from 'magiclogger';
const tagManager = new TagManager();

// Generate from file paths
const tags = tagManager.fromPath('src/services/payment/stripe.ts');
// Result: ['src', 'src.services', 'src.services.payment', 'src.services.payment.stripe']

// Generate from class/method names
const methodTags = tagManager.fromMethod('PaymentService', 'processRefund');
// Result: ['PaymentService', 'PaymentService.processRefund']
Hierarchical Transport Filtering

Filter logs at transport level based on tag hierarchy:

const logger = new Logger({
  transports: [
    {
      type: 'file',
      path: './app.log',
      filter: (entry) => {
        // Include all API-related logs
        return entry.tags?.some(tag => 
          tag.startsWith('api.') || tag === 'api'
        );
      }
    },
    {
      type: 'file', 
      path: './errors.log',
      filter: (entry) => {
        // Only error and security tags
        return entry.tags?.some(tag =>
          tag.includes('error') || tag.startsWith('security.')
        );
      }
    }
  ]
});
Hierarchical Theme Selection

Apply styles based on tag hierarchy with cascading rules:

const logger = new Logger({
  theme: {
    tags: {
      // Base styles
      'api': ['cyan'],
      'database': ['yellow'],
      'security': ['red', 'bold'],
      
      // More specific styles override base
      'api.error': ['red', 'bold'],
      'api.success': ['green'],
      'database.slow': ['yellow', 'bold', 'bgRed'],
      'security.breach': ['red', 'bold', 'underline', 'bgYellow'],
      
      // Wildcards for pattern matching
      '*.error': ['red'],
      'performance.*': ['magenta'],
      '*.slow': ['bold', 'bgYellow']
    }
  }
});

// Theme selection follows specificity
logger.error('Auth failed', { tags: ['api.error'] });        // Uses 'api.error' style
logger.warn('Slow query', { tags: ['database.slow'] });      // Uses 'database.slow' style
logger.info('Request', { tags: ['api.request'] });           // Falls back to 'api' style

Tag-Based Styling

Combine tags with themes for automatic visual categorization:

const logger = new Logger({
  theme: {
    tags: {
      // Exact match
      'error': ['red', 'bold'],
      'warning': ['yellow'],
      'success': ['green', 'bold'],
      
      // Hierarchical matching
      'api': ['cyan', 'bold'],
      'api.request': ['cyan'],
      'api.response': ['brightCyan'],
      'api.error': ['red', 'bold'],
      
      // Category styling
      'database': ['yellow'],
      'database.slow': ['yellow', 'bold', 'bgRed'],
      'cache': ['magenta'],
      'security': ['red', 'bold', 'underline']
    }
  }
});

// Automatic styling based on tags
logger.info('Request received', { tags: ['api.request'] });    // Cyan
logger.warn('Slow query', { tags: ['database.slow'] });        // Yellow on red
logger.error('Auth failed', { tags: ['security', 'api.error'] }); // Red, bold, underline

Using TagManager

import { TagManager } from 'magiclogger';

const tagManager = new TagManager();

// Generate tags from file paths
const tags = tagManager.fromPath('src/api/v2/users/create.ts');
// Result: ['src', 'api', 'v2', 'users', 'create']

// Normalize and validate tags
const normalized = tagManager.normalize(['API', 'User-Auth', 'OAuth/2.0']);
// Result: ['api', 'user-auth', 'oauth-2-0']

// Filter logs by tags
const logs = [/* array of log entries */];
const apiLogs = logs.filter(log => 
  tagManager.matches(log.tags, 'api.*')
);

Define context types for consistency.

interface RequestContext {
  requestId: string;
  userId?: string;
  method: string;
  path: string;
  duration?: number;
}

const logger = new Logger<RequestContext>();
logger.info('Request completed', {
  requestId: 'req-123',
  method: 'GET',
  path: '/api/users',
  duration: 45
});

Pretty Printing Objects

MagicLogger supports console-like variadic arguments while maintaining structured output:

import { Logger, meta, err } from 'magiclogger';
const logger = new Logger();

// Print like console.log
logger.info('Data:', { a: 1, b: 2 });

// Attach metadata for transports (not printed)
logger.info('Saved user', user, meta({ requestId, userId }));

// Structured error handling
logger.error('Failed to save', err(new Error('boom')), meta({ requestId }));

πŸ›‘οΈ Validation & Schema Enforcement

MagicLogger provides comprehensive validation for both context and tags, ensuring data quality and preventing malformed logs from polluting your logging infrastructure.

Schema Validation

Define schemas to enforce structure and types for your log data:

import { Logger, ContextManager, TagManager } from 'magiclogger';
import type { ObjectSchema } from 'magiclogger/validation';

// Define a schema for context validation
const contextSchema: ObjectSchema = {
  type: 'object',
  properties: {
    userId: { 
      type: 'string', 
      format: 'uuid',
      optional: false 
    },
    email: { 
      type: 'string', 
      format: 'email',
      transform: (v) => v.toLowerCase() 
    },
    age: { 
      type: 'number', 
      min: 0, 
      max: 150 
    },
    roles: {
      type: 'array',
      items: { type: 'string' },
      minItems: 1
    },
    metadata: {
      type: 'object',
      additionalProperties: true
    }
  },
  required: ['userId', 'email']
};

// Configure validation behavior
const contextManager = new ContextManager({
  schema: contextSchema,
  schemaValidationMode: 'warn',  // 'throw' | 'warn' | 'silent'
  enableValidation: true
});

Validation Modes

Control how validation failures are handled:

// Strict mode - throws errors on validation failure
const strictLogger = new Logger({
  contextManager: new ContextManager({
    schema: userSchema,
    schemaValidationMode: 'throw'  // Fails fast
  })
});

// Warning mode - logs warnings but continues
const warnLogger = new Logger({
  contextManager: new ContextManager({
    schema: userSchema,
    schemaValidationMode: 'warn'   // Logs warnings to console
  })
});

// Silent mode - silently drops invalid data
const silentLogger = new Logger({
  contextManager: new ContextManager({
    schema: userSchema,
    schemaValidationMode: 'silent'  // No errors or warnings
  })
});

Validation Events

Listen to validation events for custom handling:

const contextManager = new ContextManager({
  schema: contextSchema,
  schemaValidationMode: 'silent'
});

// Listen for validation failures
contextManager.on('schemaValidationFailed', ({ result, context }) => {
  // Custom handling - send to error tracking
  errorTracker.report('Invalid log context', {
    errors: result.errors,
    context: context
  });
  
  // Or increment metrics
  metrics.increment('logs.validation.failed', {
    errorCount: result.errors.length
  });
});

// Listen for successful validations
contextManager.on('validated', (context) => {
  metrics.increment('logs.validation.success');
});

Built-in Validation Rules

Context Validation

const contextManager = new ContextManager({
  // Structure limits
  maxDepth: 5,           // Maximum nesting depth
  maxProperties: 50,     // Maximum properties per object
  
  // Security
  sanitizeMode: 'strict', // Remove sensitive data
  freezeContext: true,    // Prevent mutations
  
  // Custom validation rules
  enableValidation: true
});

// Set validation rules programmatically
contextManager.setValidationRules({
  required: ['requestId', 'userId'],
  types: {
    requestId: 'string',
    userId: 'string',
    timestamp: 'number',
    success: 'boolean'
  },
  custom: (context) => {
    // Custom validation logic
    if (context.userId && context.userId === 'admin') {
      return context.adminToken !== undefined;
    }
    return true;
  }
});

Tag Validation

const tagManager = new TagManager({
  maxTags: 10,           // Maximum number of tags
  maxTagLength: 50,      // Maximum length per tag
  allowedPattern: /^[a-z0-9.-]+$/,  // Regex pattern
  
  // Schema for structured tags
  schema: {
    type: 'array',
    items: {
      type: 'string',
      pattern: /^[a-z]+(\.[a-z]+)*$/,  // Hierarchical pattern
      maxLength: 50
    },
    maxItems: 10,
    uniqueItems: true
  },
  schemaValidationMode: 'warn'
});

Schema Types

MagicLogger supports comprehensive schema types:

// String validation
const stringSchema = {
  type: 'string',
  minLength: 3,
  maxLength: 100,
  pattern: /^[A-Z]/,        // Must start with capital
  format: 'email',          // Predefined formats
  enum: ['admin', 'user'],  // Allowed values
  trim: true,               // Auto-trim whitespace
  toLowerCase: true         // Auto-lowercase
};

// Number validation
const numberSchema = {
  type: 'number',
  min: 0,
  max: 100,
  integer: true,           // Must be integer
  positive: true,          // Must be positive
  multipleOf: 5            // Must be multiple of 5
};

// Array validation
const arraySchema = {
  type: 'array',
  items: { type: 'string' },
  minItems: 1,
  maxItems: 10,
  uniqueItems: true        // No duplicates
};

// Object validation
const objectSchema = {
  type: 'object',
  properties: {
    name: { type: 'string' },
    age: { type: 'number' }
  },
  required: ['name'],
  additionalProperties: false,  // No extra props
  minProperties: 1,
  maxProperties: 10
};

// Union types
const unionSchema = {
  type: 'union',
  schemas: [
    { type: 'string' },
    { type: 'number' }
  ]
};

Sanitization

Automatic sanitization of sensitive data:

const contextManager = new ContextManager({
  sanitizeMode: 'strict',  // 'none' | 'basic' | 'strict' | 'custom'
  
  // Custom sanitization function
  sanitize: (value) => {
    if (typeof value === 'string') {
      // Redact credit card numbers
      return value.replace(/\b\d{4}[\s-]?\d{4}[\s-]?\d{4}[\s-]?\d{4}\b/g, '****-****-****-****');
    }
    return value;
  }
});

// Automatic sensitive key detection
const context = {
  userId: '123',
  password: 'secret123',      // Automatically redacted
  creditCard: '4111-1111-1111-1111',  // Automatically redacted
  apiToken: 'xyz789',          // Automatically redacted
  data: 'safe-data'
};

// Result after sanitization:
// {
//   userId: '123',
//   password: '***',
//   creditCard: '***',
//   apiToken: '***',
//   data: 'safe-data'
// }

Validation is designed to be efficient and tree-shakeable, its components loaded only when schemas are defined.

Best Practices

  1. Use appropriate validation modes:

    • throw for development and testing
    • warn for staging environments
    • silent for production (with event listeners)
  2. Define schemas at initialization:

    // Good - schema defined once
    const schema = { /* ... */ };
    const logger = new Logger({ contextManager: new ContextManager({ schema }) });
    
    // Avoid - schema defined per log
    logger.info('message', validateSchema({ /* ... */ }));
  3. Monitor validation failures:

    contextManager.on('schemaValidationFailed', ({ result }) => {
      monitoring.recordValidationFailure(result.errors);
    });
  4. Use transforms for data normalization:

    const schema = {
      type: 'object',
      properties: {
        email: {
          type: 'string',
          transform: (v) => v.toLowerCase().trim()
        },
        timestamp: {
          type: 'number',
          transform: (v) => Math.floor(v)  // Remove decimals
        }
      }
    };
  5. Combine with TypeScript for compile-time safety:

    interface UserContext {
      userId: string;
      email: string;
      roles: string[];
    }
    
    const logger = new Logger<UserContext>();
    // TypeScript ensures compile-time type safety
    // Schema ensures runtime validation

Extensions (Optional)

Extensions are opt-in for specialized needs:

import { Redactor, Sampler, RateLimiter } from 'magiclogger/extensions';

const logger = new Logger({
  // PII Redaction
  redactor: new Redactor({ preset: 'strict' }),
  
  // Statistical sampling (10% of logs)
  sampler: new Sampler({ rate: 0.1 }),
  
  // Rate limiting (1000/minute)
  rateLimiter: new RateLimiter({ max: 1000, window: 60000 })
});

Performance

Why MagicLogger?

MagicLogger delivers 170K ops/sec with styled output and works in both Node.js and browsers. While ~50% slower than Pino (374K ops/sec), this is by design - every log includes:

  • 180K ops/sec plain text, 170K ops/sec styled output
  • Full MAGIC schema conformity - Structured logging by default
  • OpenTelemetry compatible out of the box - No plugins needed
  • Browser + Node.js - Same API everywhere (unlike Pino/Winston which are Node-only)
  • Similar size to Winston (~42KB vs ~44KB)

The performance trade-off is purposeful: complete observability data in every log. Choose MagicLogger when you need structured logging with visual debugging that works everywhere.

Note: Future versions may offer a "performance mode" without default structured logging.

Performance Comparison (20K iterations, real file I/O)

πŸ“ Plain Text Performance
Logger Ops/sec Avg (ms) P50 P95 P99 Max
Pino 560,285 0.002 0.001 0.003 0.004 4.808
Winston (Plain) 306,954 0.003 0.001 0.003 0.049 0.633
MagicLogger (Sync) 269,587 0.003 0.001 0.003 0.008 4.547
MagicLogger (Async) 165,694 0.006 0.003 0.007 0.032 5.305
Bunyan 84,515 0.012 0.008 0.020 0.045 8.435

Performance Notes:

  • MagicLogger achieves 250K+ ops/sec for synchronous plain text logging
  • AsyncLogger delivers 120K+ ops/sec with styled output (only 11.8% overhead)
  • Pre-compiled style patterns cache for common log formats
🎨 Styled Output Performance
Logger Ops/sec Avg (ms) P50 P95 P99 Max
Winston (Sync + Styled) 446,027 0.002 0.001 0.002 0.035 4.662
Pino (Pretty) 274,431 0.004 0.003 0.004 0.008 0.097
MagicLogger (Async + Styles) 116,404 0.008 0.005 0.010 0.034 8.074
Bunyan (Styled) 99,468 0.010 0.007 0.017 0.029 6.036
MagicLogger (Sync + Styles) 80,502 0.012 0.005 0.016 0.031 8.340

Performance benchmarks are run manually via npm run perf:update when performance improvements are made - see scripts/performance/

How Styling Works

Default Mode (Logger/SyncLogger):

  • Style extraction happens in the main thread before sending to transports
  • Uses efficient regex-based parsing to extract <style>text</> markup
  • Produces plain text + style ranges array for the MAGIC schema
  • LRU cache reduces repeated style generation overhead
  • Deep dive into our style optimization techniques β†’

AsyncLogger with Worker Threads (optional):

  • Workers are OFF by default for better latency
  • Enable with worker.enabled: true for heavy styling workloads
  • 4x faster for complex styles but adds IPC overhead for simple logs
  • Recommended only for >10K styled logs/sec

Architecture Choices:

  • sonic-boom: High-performance async file I/O with internal buffering
  • Fast Path Detection: Unstyled text bypasses style processing entirely
  • Worker Pool Pattern: Reusable worker threads when needed (avoids spawn overhead)

Why AsyncLogger is Recommended:

  • Non-blocking: Keeps your app responsive even under heavy logging
  • Optimized for styled output: 151K ops/sec with advanced style caching
  • Smart batching: Automatically optimizes for network transports (100-1000 entries)
  • Production-ready: Graceful shutdown, backpressure handling, automatic retries

When to use SyncLogger (rare):

  • Critical audit logs that must NEVER be lost even under extreme load
  • Regulatory compliance requiring synchronous disk writes
  • Trade-off: Can make your app unresponsive under load

See benchmark methodology and architecture docs.

API Reference

Logger Options

interface LoggerOptions {
  // Basic configuration
  id?: string;
  tags?: string[];
  context?: Record<string, unknown>;
  verbose?: boolean;
  useColors?: boolean;     // Enable colored output (default: true)
  useConsole?: boolean;     // Add console transport automatically (default: true, set to false to disable)
  
  // Styling & themes
  theme?: string | ThemeDefinition;
  
  // Performance features
  buffer?: BufferOptions;
  sampling?: SamplingOptions;
  rateLimit?: RateLimitOptions;
  
  // Security
  redaction?: RedactionOptions;
  
  // Transports
  transports?: Transport[];
}

Key Methods

// Logging methods
logger.debug(message, meta?)
logger.info(message, meta?)
logger.warn(message, meta?)
logger.error(message, meta?)
logger.success(message, meta?)

// Styling
logger.s       // Chainable style API
logger.fmt     // Template literal API

// Visual elements
logger.header(text, styles?)
logger.separator(char, length)
logger.progressBar(percent, width?)
logger.table(data)
logger.diff(label, oldObj, newObj)

// Management
logger.flush()          // Force flush buffers
logger.close()          // Graceful shutdown
logger.getStats()       // Performance metrics

Documentation

πŸ“š View Documentation | Getting Started | API Reference

npm run docs        # Start docs dev server with live reload
npm run docs:build  # Build production docs

For development setup and build instructions, see Development Guide and Build Instructions.

Contributing

We welcome contributions! Please see CONTRIBUTING.md for guidelines.

License

MIT Β© Manic.agency


Developed and sponsored by Manic.agency

πŸ“¦ Build Output Sizes

File Format Raw Size Gzip
index.cjs CJS 10.7 kB 2.35 kB
index.js ESM 6.49 kB 1.94 kB
index.d.ts Types 180 kB 38.2 kB

Core Bundle Sizes (gzipped)

Scenario Size
Core (bare minimum) 47.1 kB
Core + Console Transport 47.1 kB
Core + File Transport 47.1 kB
Core + HTTP Transport 49.7 kB
Core + All Basic Transports 51.2 kB

Generated via scripts/analyze-build.js.

File Format Raw Size Gzip
index.cjs CJS 10.7 kB 2.35 kB
index.js ESM 6.49 kB 1.94 kB
index.d.ts Types 180 kB 38.2 kB

Core Bundle Sizes (gzipped)

Scenario Size
Core (bare minimum) 47.1 kB
Core + Console Transport 47.1 kB
Core + File Transport 47.1 kB
Core + HTTP Transport 49.7 kB
Core + All Basic Transports 51.2 kB

Generated via scripts/analyze-build.js.

About

The most colorful TS / JS logging library

Topics

Resources

License

Contributing

Security policy

Stars

Watchers

Forks

Packages

No packages published

Contributors 3

  •  
  •  
  •