Skip to content

metarhia/metalog

Repository files navigation

Meta Logger for Metarhia

ci status snyk npm version npm downloads/month npm downloads license

Output example

Usage

const logger = await Logger.create({
  path: './log', // absolute or relative path
  workerId: 7, // mark for process or thread
  flushInterval: 3000, // flush log to disk interval (default: 3s)
  writeBuffer: 64 * 1024, // buffer size (default: 64kb)
  keepDays: 5, // delete after N days, 0 - disable (default: 1)
  home: process.cwd(), // remove substring from paths
  json: false, // print logs in JSON format (default: false)
  toFile: ['log', 'info', 'warn', 'error'], // tags to write to file (default: all)
  toStdout: ['log', 'info', 'warn', 'error'], // tags to write to stdout (default: all)
  createStream: () => fs.createWriteStream, // custom stream factory (optional)
  crash: 'flush', // crash handling: 'flush' to flush buffer on exit (optional)
});

const { console } = logger;

console.log('Test message');
console.info('Info message');
console.warn('Warning message');
console.error('Error message');
console.debug('Debug message');

console.assert(true, 'Assertion passed');
console.assert(false, 'Assertion failed');
console.count('counter');
console.count('counter');
console.countReset('counter');

console.time('operation');
// ... some operation ...
console.timeEnd('operation');
console.timeLog('operation', 'Checkpoint');

console.group('Group 1');
console.log('Nested message');
console.groupCollapsed('Group 2');
console.log('Collapsed group message');
console.groupEnd();
console.groupEnd();

console.dir({ key: 'value' });
console.dirxml('<div>HTML content</div>');
console.table([
  { name: 'John', age: 30 },
  { name: 'Jane', age: 25 },
]);

console.trace('Trace message');

await logger.close();

Console API Compatibility

Metalog provides a fully compatible console implementation that supports all Node.js console methods:

  • console.log([data][, ...args]) - General logging
  • console.info([data][, ...args]) - Informational messages
  • console.warn([data][, ...args]) - Warning messages
  • console.error([data][, ...args]) - Error messages
  • console.debug([data][, ...args]) - Debug messages
  • console.assert(value[, ...message]) - Assertion testing
  • console.clear() - Clear the console
  • console.count([label]) - Count occurrences
  • console.countReset([label]) - Reset counter
  • console.dir(obj[, options]) - Object inspection
  • console.dirxml(...data) - XML/HTML inspection
  • console.group([...label]) - Start group
  • console.groupCollapsed() - Start collapsed group
  • console.groupEnd() - End group
  • console.table(tabularData[, properties]) - Table display
  • console.time([label]) - Start timer
  • console.timeEnd([label]) - End timer
  • console.timeLog([label][, ...data]) - Log timer value
  • console.trace([message][, ...args]) - Stack trace

All methods maintain the same behavior as Node.js native console, with output routed through the metalog system for consistent formatting and file logging.

Configuration Options

LoggerOptions

Option Type Default Description
path string required Directory path for log files (absolute or relative)
home string required Base path to remove from stack traces
workerId number undefined Worker/process identifier (appears as W0, W1, etc.)
flushInterval number 3000 Flush buffer to disk interval in milliseconds
writeBuffer number 65536 Buffer size threshold before flushing (64KB)
keepDays number 1 Days to keep log files (0 = disable rotation)
json boolean false Output logs in JSON format
toFile string[] ['log', 'info', 'warn', 'debug', 'error'] Log tags to write to file
toStdout string[] ['log', 'info', 'warn', 'debug', 'error'] Log tags to write to stdout
createStream function fs.createWriteStream Custom stream factory function
crash string undefined Crash handling mode ('flush' to flush on exit)

Log Tags

Metalog supports five log tags that can be filtered independently for file and console output:

  • log - General logging
  • info - Informational messages
  • warn - Warning messages
  • debug - Debug messages
  • error - Error messages

Advanced Usage

Custom Stream Factory

const logger = await Logger.create({
  path: './log',
  home: process.cwd(),
  createStream: (filePath) => {
    // Custom compression stream
    const fs = require('fs');
    const zlib = require('zlib');
    const gzip = zlib.createGzip();
    const writeStream = fs.createWriteStream(filePath + '.gz');
    return gzip.pipe(writeStream);
  },
});

Selective Logging

// Only log errors to file, all tags to console
const logger = await Logger.create({
  path: './log',
  home: process.cwd(),
  toFile: ['error'],
  toStdout: ['log', 'info', 'warn', 'debug', 'error'],
});

// Only log info and above tags to both file and console
const logger = await Logger.create({
  path: './log',
  home: process.cwd(),
  toFile: ['info', 'warn', 'error'],
  toStdout: ['info', 'warn', 'error'],
});

JSON Logging

const logger = await Logger.create({
  path: './log',
  home: process.cwd(),
  json: true,
});

logger.console.info('User action', { userId: 123, action: 'login' });
// Output: {"timestamp":"2025-01-07T10:30:00.000Z","worker":"W0","tag":"info","message":"User action","userId":123,"action":"login"}

Log Rotation and Cleanup

const logger = await Logger.create({
  path: './log',
  home: process.cwd(),
  keepDays: 7, // Keep logs for 7 days
  workerId: 1,
});

// Manual rotation
await logger.rotate();

// Log files are automatically rotated daily
// Old files are cleaned up based on keepDays setting

Error Handling

const logger = await Logger.create({
  path: './log',
  home: process.cwd(),
  crash: 'flush', // Flush buffer on process exit
});

logger.on('error', (error) => {
  console.error('Logger error:', error.message);
});

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

API Reference

Logger Class

Constructor

new Logger(options: LoggerOptions): Promise<Logger>

Static Methods

  • Logger.create(options: LoggerOptions): Promise<Logger> - Create and open logger

Instance Methods

  • open(): Promise<Logger> - Open log file and start logging
  • close(): Promise<void> - Close logger and flush remaining data
  • rotate(): Promise<void> - Manually trigger log rotation
  • write(tag: string, indent: number, args: unknown[]): void - Low-level write method
  • flush(callback?: (error?: Error) => void): void - Flush buffer to disk

Properties

  • active: boolean - Whether logger is currently active
  • path: string - Log directory path
  • home: string - Home directory for path normalization
  • console: Console - Console instance for logging

BufferedStream Class

const stream = new BufferedStream({
  stream: fs.createWriteStream('output.log'),
  writeBuffer: 32 * 1024, // 32KB buffer
  flushInterval: 5000, // 5 second flush interval
});

stream.write(Buffer.from('data'));
stream.flush();
await stream.close();

Formatter Class

const formatter = new Formatter({
  worker: 'W1',
  home: '/app',
  json: false,
});

const formatted = formatter.formatPretty('info', 0, ['Message']);
const jsonOutput = formatter.formatJson('error', 0, [error]);

Best Practices

Performance Optimization

  1. Buffer Size: Adjust writeBuffer based on your log volume

    • High volume: 128KB or larger
    • Low volume: 16KB or smaller
  2. Flush Interval: Balance between performance and data safety

    • Production: 3-10 seconds
    • Development: 1-3 seconds
  3. Selective Logging: Use toFile and toStdout to reduce I/O

    // Production: Only errors to file, warnings+ to console
    toFile: ['error'],
    toStdout: ['warn', 'error']

Log Management

  1. Rotation Strategy: Set appropriate keepDays based on storage

  2. Path Organization: Use structured paths for multi-service deployments

    path: `/var/log/app/${process.env.NODE_ENV}/${process.env.SERVICE_NAME}`;
  3. Error Handling: Always handle logger errors

    logger.on('error', (error) => {
      // Fallback logging or alerting
    });

Development vs Production

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

const logger = await Logger.create({
  path: './log',
  home: process.cwd(),
  json: !isDevelopment, // JSON in production, pretty in dev
  toFile: isDevelopment ? ['log', 'info', 'warn', 'error'] : ['error'],
  toStdout: isDevelopment
    ? ['log', 'info', 'warn', 'debug', 'error']
    : ['warn', 'error'],
  flushInterval: isDevelopment ? 1000 : 5000,
  keepDays: isDevelopment ? 1 : 30,
});

Troubleshooting

Common Issues

Logs not appearing in files:

  • Check path directory exists and is writable
  • Verify toFile array includes desired log tags
  • Ensure logger is properly opened with await logger.open()

High memory usage:

  • Reduce writeBuffer size
  • Increase flushInterval frequency
  • Use selective logging with toFile/toStdout

Missing logs on crash:

  • Set crash: 'flush' option
  • Handle process signals properly
  • Use try/catch around critical operations

Performance issues:

  • Use JSON format for high-volume logging
  • Disable file logging for debug tags in production
  • Consider using separate loggers for different components

Debug Mode

// Enable debug logging
const logger = await Logger.create({
  path: './log',
  home: process.cwd(),
  toStdout: ['debug', 'info', 'warn', 'error'],
});

logger.console.debug('Debug information', { data: 'value' });

License & Contributors

Copyright (c) 2017-2025 Metarhia contributors. Metalog is MIT licensed.
Metalog is a part of Metarhia technology stack.