magiclogger
v0.1.0
Published
Lightweight magical terminal and browser-compatible logger with color support, file logging, and numerous formatting options
Readme
MagicLogger
🎬 See MagicLogger in Action
🚀 Universal Color Logging Standard
MagicLogger is a TypeScript logger 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
- Quick Start
- Styling APIs
- Key Features
- MAGIC Schema
- Transports
- Advanced Features
- Theming & Custom Colors
- Context & Tags
- Validation & Schema Enforcement
- Performance
- Examples
- API Reference
- Contributing
Installation
npm install magiclogger
# or
yarn add magiclogger
# or
pnpm add magicloggerSupports both ESM and CommonJS:
import { Logger } from 'magiclogger'; // ESM/TypeScript
const { Logger } = require('magiclogger'); // CommonJSQuick 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>[email protected]</> 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: '[email protected]' });🎯 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-styledAsync 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:
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" }Preserve Style Information
- Extract styles from markup (e.g.,
<red>text</>) - Store as
[startIndex, endIndex, style]tuples - Keep plain text in
messagefield
- Extract styles from markup (e.g.,
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 styleCustom 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/transportsAdvanced 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: '[email protected]',
password: 'secret123', // Will be redacted
creditCard: '4111111111111111' // Will be redacted
};
const sanitized = contextManager.sanitize(userContext);
// Result: {
// userId: '123',
// email: '[email protected]',
// 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' styleTag-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, underlineUsing 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
Use appropriate validation modes:
throwfor development and testingwarnfor staging environmentssilentfor production (with event listeners)
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({ /* ... */ }));Monitor validation failures:
contextManager.on('schemaValidationFailed', ({ result }) => { monitoring.recordValidationFailure(result.errors); });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 } } };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: truefor 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 metricsDocumentation
📚 View Documentation | Getting Started | API Reference
npm run docs # Start docs dev server with live reload
npm run docs:build # Build production docsFor 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
📦 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.
| 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.
