Skip to main content

Logger

The SyntropyLog logger is the core component that provides structured logging with automatic context propagation and correlation.

🎯 Overview

SyntropyLog's logger is designed to be:

  • Context-aware - Automatically includes correlation IDs and context
  • Framework-agnostic - Works with any Node.js application
  • Performance-optimized - Minimal overhead with intelligent log scoping
  • Production-ready - Multiple transport support and structured output

🚀 Basic Usage

Initialize once and wait for the ready event before using the logger. Use getLogger(name) to get a logger instance.

import { syntropyLog, ClassicConsoleTransport } from 'syntropylog';

syntropyLog.on('ready', () => {});
syntropyLog.on('error', (err) => console.error(err));
await syntropyLog.init({
logger: {
serviceName: 'my-app',
level: 'info',
transports: [new ClassicConsoleTransport()],
},
});

// Get logger instance (use only after ready)
const logger = syntropyLog.getLogger('main');

// Basic logging
logger.info('Application started');
logger.warn('Configuration not found, using defaults');
logger.error('Database connection failed', {
error: 'Connection timeout',
retryCount: 3
});

Multiple loggers (N loggers, same correlation ID)

You can create several loggers with different names (e.g. one for the service, one for Axios, one for Kafka, one for DB). Each identifies a subsystem; they all share the same correlation ID when used inside the same contextManager.run(), so you can trace the full journey of a request across systems. You can call getLogger(name) from anywhere in your app — the logger keeps the same config; a small logger config module and an enum for names keeps things ordered and explicit. Configure different transports per logger name or use a transport pool with .override() / .add() / .remove() per call.

See Multiple loggers and transports for patterns (centralized loggers + enum, named loggers + shared transports, transport pool + per-call routing, different transport sets per logger name) and how the correlation ID travels.

🏗️ Singleton pattern per name

SyntropyLog caches logger instances by name (and bindings) to avoid creating new instances on every call:

// First call - creates and returns new instance
const logger1 = syntropyLog.getLogger('user-service');

// Second call - returns the SAME instance (cached by name)
const logger2 = syntropyLog.getLogger('user-service');

// logger1 === logger2 ✅

📊 Log Levels

SyntropyLog supports standard log levels with intelligent context inclusion:

logger.trace('Detailed debugging information');
logger.debug('Debug information for development');
logger.info('General information about application flow');
logger.warn('Warning messages for potentially harmful situations');
logger.error('Error events that might still allow the application to continue');
logger.fatal('Severe error events that will prevent the application from running');

🎨 Transports

Configure one or more transports in logger.transports. Use a console transport for development (e.g. PrettyConsoleTransport) or production (e.g. ClassicConsoleTransport), or both.

import { syntropyLog, ClassicConsoleTransport } from 'syntropylog';

await syntropyLog.init({
logger: {
serviceName: 'my-app',
level: 'info',
transports: [new ClassicConsoleTransport()],
},
});

See the package exports for other built-in transports and the Configuration guide.

🧠 Smart Context Logging with LoggingMatrix

The loggingMatrix is an intermediary layer that acts as a filter between your context data and the logger. It determines which context fields are included in each log level, providing fine-grained control over what gets logged without affecting security or performance.

How LoggingMatrix Works

await syntropyLog.init({
loggingMatrix: {
default: ['correlationId'], // Minimal context for success logs
trace: ['*'], // Full context for debugging
debug: ['correlationId', 'userId', 'operation'],
info: ['correlationId', 'serviceName'],
warn: ['correlationId', 'userId', 'errorCode'],
error: ['*'], // Full context when things go wrong
fatal: ['*'], // Everything for critical failures
},
});

Key Benefits

  • Selective Context: Only include relevant context fields per log level
  • Wildcard Support: Use '*' to include ALL context fields when needed
  • Hot Reload: Modify the matrix at runtime without restarting
  • Security Preserved: All masking and sanitization rules still apply
  • Performance Optimized: Only serializes what's needed for each level

Practical Examples

// Set various context data
const contextManager = syntropyLog.getContextManager();
contextManager.set('userId', 123);
contextManager.set('serviceName', 'user-service');
contextManager.set('operation', 'create-user');
contextManager.set('tenantId', 'tenant-1');
contextManager.set('sessionId', 'sess-789');
contextManager.set('requestId', 'req-456');

// With loggingMatrix: { info: ['correlationId', 'serviceName'] }
logger.info('User created successfully');
// Output: { correlationId: 'abc-123', serviceName: 'user-service', message: 'User created successfully' }
// Only correlationId and serviceName are included

// With loggingMatrix: { error: ['*'] }
logger.error('Database connection failed');
// Output: { correlationId: 'abc-123', userId: 123, serviceName: 'user-service', operation: 'create-user', tenantId: 'tenant-1', sessionId: 'sess-789', requestId: 'req-456', message: 'Database connection failed' }
// ALL context fields are included for debugging

Runtime Configuration

You can modify the logging matrix at runtime with reconfigureLoggingMatrix():

// Add more context to info logs during debugging
syntropyLog.reconfigureLoggingMatrix({
info: ['correlationId', 'serviceName', 'userId', 'operation'],
});

// Reduce context in error logs for production
syntropyLog.reconfigureLoggingMatrix({
error: ['correlationId', 'userId', 'errorCode'],
});

// Changes take effect immediately
logger.info('Operation completed'); // Now includes userId and operation

🔒 Data Masking

Configure masking in init() so sensitive fields are redacted before any transport:

await syntropyLog.init({
logger: { serviceName: 'my-app', level: 'info', transports: [new ClassicConsoleTransport()] },
masking: {
enableDefaultRules: true,
rules: [
{ pattern: /password|token|secret/i, strategy: 'password' },
{ pattern: /creditCard|cardNumber/i, strategy: 'card' },
],
},
});

// Sensitive data is automatically masked in log output
logger.info('User created', {
userId: 123,
email: 'user@example.com',
password: 'secret123', // Masked in output
creditCard: '1234-5678-9012-3456', // Masked in output
});

🎯 Advanced Features

Fluent API

logger
.withSource('auth-controller')
.withTransactionId('tx-12345')
.info('User authentication completed', {
userId: 123,
method: 'oauth2',
provider: 'google'
});

Error Logging

try {
await someOperation();
} catch (error) {
logger.error({ err: error }, 'Operation failed', {
operation: 'database_query',
duration: 1500,
});
}

Structured Data

logger.info('API Request', {
method: 'GET',
url: '/api/users',
statusCode: 200,
duration: 45,
userAgent: 'Mozilla/5.0...',
});

⚡ Performance Considerations

  • Minimal overhead - Less than 1ms per log operation
  • Intelligent serialization - Only serialize what's needed
  • Async operations - Non-blocking logging
  • Memory efficient - Singleton pattern prevents leaks

🔧 Configuration Options

await syntropyLog.init({
logger: {
level: 'info', // 'fatal' | 'error' | 'warn' | 'info' | 'debug' | 'trace' | 'silent'
serviceName: 'my-enterprise-app',
transports: [new ClassicConsoleTransport()],
serializerTimeoutMs: 50, // Prevent slow serializers from blocking
},
});

See Configuration for the full config shape.

🎯 Best Practices

  1. Use appropriate log levels - Don't log everything at the same level
  2. Include relevant context - Add useful information for debugging
  3. Structure your data - Use objects for structured logging
  4. Mask sensitive data - Always configure data masking
  5. Use correlation IDs - Enable automatic correlation for distributed tracing
  6. Choose the right transports — Different transports for different environments
  7. Use multiple loggers — One per subsystem (service, axios, kafka, etc.) with the same correlation ID; see Multiple loggers and transports