Skip to main content
Version: 1.10.3

PrettyHandler

The PrettyHandler formats log output with proper indentation and structure, making nested objects and complex data much more readable. It works as a wrapper around other handlers and can be combined with ColorHandler for beautiful, colorized pretty output.

Features​

  • 🎨 Beautiful Formatting: Deep indentation for nested objects
  • 🔧 Handler Wrapper: Works with any underlying handler (TextHandler, JSONHandler, ColorHandler)
  • 📊 Smart Arrays: Optional compact mode for primitive arrays
  • 🎯 Depth Control: Configurable maximum depth to prevent excessive nesting
  • 🌈 Composable: Combine with ColorHandler for colorized pretty output
  • ⚡ Multi-handler Support: Use in MultiHandler for different outputs

Basic Usage​

With TextHandler​

import { Logger, PrettyHandler, TextHandler, Any } from '@omdxp/jslog';

const logger = new Logger(
new PrettyHandler({
handler: new TextHandler()
})
);

logger.info('User data', Any('user', {
id: 123,
name: 'Alice',
profile: {
bio: 'Developer',
settings: {
theme: 'dark',
notifications: true
}
}
}));

With JSONHandler​

import { Logger, PrettyHandler, JSONHandler, Any } from '@omdxp/jslog';

const logger = new Logger(
new PrettyHandler({
handler: new JSONHandler()
})
);

logger.info('API Response', Any('data', {
status: 200,
body: {
users: [
{ id: 1, name: 'Alice' },
{ id: 2, name: 'Bob' }
]
}
}));

Combining with ColorHandler​

For the best development experience, combine PrettyHandler with ColorHandler:

import { Logger, PrettyHandler, ColorHandler, Any } from '@omdxp/jslog';

const logger = new Logger(
new PrettyHandler({
handler: new ColorHandler(),
indent: 4
})
);

logger.info('Beautiful colorized output', Any('config', {
server: {
host: 'localhost',
port: 3000,
ssl: {
enabled: true,
cert: '/path/to/cert.pem'
}
},
database: {
host: 'db.example.com',
port: 5432
}
}));

Configuration Options​

indent​

Number of spaces for each indentation level (default: 2).

const logger = new Logger(
new PrettyHandler({
handler: new TextHandler(),
indent: 4 // Use 4 spaces instead of 2
})
);

maxDepth​

Maximum depth for nested objects (default: 10). Objects deeper than this will show [Max depth reached].

const logger = new Logger(
new PrettyHandler({
handler: new TextHandler(),
maxDepth: 5
})
);

logger.info('Deep nesting', Any('data', {
l1: { l2: { l3: { l4: { l5: { l6: 'too deep' } } } } }
}));
// l6 will show as [Max depth reached]

compactArrays​

Display primitive arrays in compact mode (default: false).

const logger = new Logger(
new PrettyHandler({
handler: new ColorHandler(),
compactArrays: true
})
);

logger.info('Tags', Any('tags', ['typescript', 'logging', 'nodejs']));
// Output: tags=[typescript, logging, nodejs]

logger.info('Numbers', Any('numbers', [1, 2, 3, 4, 5]));
// Output: numbers=[1, 2, 3, 4, 5]

// Complex arrays are still prettified
logger.info('Users', Any('users', [
{ id: 1, name: 'Alice' },
{ id: 2, name: 'Bob' }
]));
// Each user object is formatted with indentation

Advanced Examples​

MultiHandler: Pretty Console + Compact File​

Log pretty output to console while keeping compact JSON for file processing:

import {
Logger,
MultiHandler,
PrettyHandler,
ColorHandler,
JSONHandler
} from '@omdxp/jslog';
import * as fs from 'fs';

const logger = new Logger(
new MultiHandler([
// Pretty colorized console output
new PrettyHandler({
handler: new ColorHandler(),
indent: 2
}),
// Compact JSON for file/processing
new JSONHandler({
writer: fs.createWriteStream('./app.log')
})
])
);

logger.info('Request processed', Any('request', {
method: 'POST',
path: '/api/users',
body: { name: 'Alice', email: 'alice@example.com' }
}));

Error Logging with Pretty Format​

try {
throw new Error('Database connection failed');
} catch (err) {
logger.error('Operation failed', Err(err), Any('context', {
operation: 'query',
database: 'users_db',
attempts: 3,
lastAttempt: new Date()
}));
}

// Output will show:
// - Error message, name, and stack (prettified)
// - Context object with proper indentation

Groups with PrettyHandler​

PrettyHandler properly tracks and displays group prefixes:

const appLogger = logger.withGroup('app');
appLogger.info('Application event', { event: 'startup' });

const dbLogger = appLogger.withGroup('database');
dbLogger.info('Database query', Any('query', {
sql: 'SELECT * FROM users WHERE active = true',
duration: 45,
params: []
}));

Output:

2025-11-05T18:56:11.489Z INFO  Application event
app.event="startup"
2025-11-05T18:56:11.489Z INFO Database query
app.database.query:
{
duration: 45,
params: [],
sql: "SELECT * FROM users WHERE active = true"
}

Notice how groups are properly prefixed to attribute keys (app.event, app.database.query), making it easy to see the attribute hierarchy.

Development vs Production​

Use PrettyHandler in development and compact handlers in production:

const isDev = process.env.NODE_ENV === 'development';

const logger = new Logger(
isDev
? new PrettyHandler({
handler: new ColorHandler(),
indent: 2
})
: new JSONHandler()
);

Child Loggers with Attributes​

const childLogger = logger.with(
String('service', 'api'),
String('version', '1.0.0')
);

childLogger.info('Request processed', Any('request', {
id: 'req_123',
method: 'GET',
path: '/users/123',
response: {
status: 200,
body: {
id: 123,
name: 'Alice',
roles: ['admin', 'user']
}
}
}));

// All logs will include service and version attributes
// with proper formatting

Performance Considerations​

  • Development Only: PrettyHandler adds formatting overhead. Use it in development but consider compact handlers for production.
  • Depth Limit: Set maxDepth appropriately to avoid excessive processing of deeply nested structures.
  • MultiHandler: When using MultiHandler, put PrettyHandler first for console output and compact handlers for file/network logging.

How It Works​

PrettyHandler is a wrapper handler that:

  1. Intercepts log records before they reach the underlying handler
  2. Prettifies all attribute values (recursively formatting objects and arrays)
  3. Passes the modified record to the wrapped handler
  4. Does NOT handle streams or output itself - that's the wrapped handler's job

This design makes it composable with any other handler:

// Works with TextHandler
new PrettyHandler({ handler: new TextHandler() })

// Works with JSONHandler
new PrettyHandler({ handler: new JSONHandler() })

// Works with ColorHandler
new PrettyHandler({ handler: new ColorHandler() })

// Works with FileHandler (via advanced handlers)
new PrettyHandler({ handler: new FileHandler({ filepath: './app.log' }) })

// Works in chains
new PrettyHandler({
handler: new BufferedHandler({
handler: new FileHandler({ filepath: './app.log' })
})
})

API Reference​

Constructor​

constructor(options: PrettyHandlerOptions)

Options​

interface PrettyHandlerOptions {
handler: Handler; // Required: The underlying handler
indent?: number; // Optional: Spaces per indent (default: 2)
maxDepth?: number; // Optional: Max nesting depth (default: 10)
compactArrays?: boolean; // Optional: Compact primitive arrays (default: false)
}

Methods​

  • enabled(level: Level): boolean - Delegates to wrapped handler
  • needsSource(): boolean - Delegates to wrapped handler
  • handle(record: Record): void - Prettifies and delegates to wrapped handler
  • withAttrs(attrs: Attr[]): Handler - Returns new PrettyHandler with attributes
  • withGroup(name: string): Handler - Returns new PrettyHandler with group
  • close(): Promise<void> - Closes wrapped handler if it supports closing

See Also​