UNPKG

syntropylog

Version:

An instance manager with observability for Node.js applications

169 lines 7.78 kB
import { Logger } from './Logger'; import { MaskingEngine } from '../masking/MaskingEngine'; import { SerializerRegistry } from '../serialization/SerializerRegistry'; import { ConsoleTransport } from './transports/ConsoleTransport'; import { SanitizationEngine } from '../sanitization/SanitizationEngine'; /** * @class LoggerFactory * @description Manages the lifecycle and configuration of all logging components. * An instance of this factory is created by `syntropyLog.init()` and acts as the central * orchestrator for creating and managing logger instances and their dependencies. */ export class LoggerFactory { /** @private The manager for handling asynchronous contexts. */ contextManager; /** @private The main framework instance, used as a mediator. */ syntropyLogInstance; /** @private The array of transports to which logs will be dispatched. */ transports; /** @private The global minimum log level for all created loggers. */ globalLogLevel; /** @private The global service name, used as a default for loggers. */ serviceName; /** @private The engine responsible for serializing complex objects. */ serializerRegistry; /** @private The engine responsible for masking sensitive data. */ maskingEngine; /** @private A pool to cache logger instances by name for performance. */ loggerPool = new Map(); /** * @constructor * @param {SyntropyLogConfig} config - The global configuration object. * @param {IContextManager} contextManager - The shared context manager instance. * @param {SyntropyLog} syntropyLogInstance - The main framework instance for mediation. * @description Initializes all core logging engines and orchestrates transport setup. * It follows a key principle for transport configuration: * - **If `config.logger.transports` is provided:** The factory trusts the user's * configuration completely and uses the provided transports as-is. It is the user's * responsibility to configure them correctly (e.g., adding sanitization). * - **If no transports are provided:** The factory creates a single, production-safe * `ConsoleTransport` by default, which includes a built-in `SanitizationEngine`. */ constructor(config, contextManager, syntropyLogInstance) { this.contextManager = contextManager; this.syntropyLogInstance = syntropyLogInstance; // Configure the context manager by passing the entire context config object. if (config.context) { this.contextManager.configure(config.context); } // Configure the HTTP manager if http instances are provided if (config.http?.instances) { // This block is not provided in the original file, so it's commented out. // If it were uncommented, it would likely involve configuring the HTTP manager // with the provided instances. } // If the user provides a specific list of transports, we use them directly. // We trust the user to have configured them correctly (e.g., providing a // SanitizationEngine to their production transports). if (config.logger?.transports) { this.transports = config.logger.transports; } else { // If no transports are provided, we create a safe, default production transport. // This transport includes a default sanitization engine. const sanitizationEngine = new SanitizationEngine(); this.transports = [new ConsoleTransport({ sanitizationEngine })]; } this.globalLogLevel = config.logger?.level ?? 'info'; this.serviceName = config.logger?.serviceName ?? 'unknown-service'; this.serializerRegistry = new SerializerRegistry({ serializers: config.logger?.serializers, timeoutMs: config.logger?.serializerTimeoutMs, }); this.maskingEngine = new MaskingEngine({ rules: config.masking?.rules, maskChar: config.masking?.maskChar, preserveLength: config.masking?.preserveLength, enableDefaultRules: config.masking?.enableDefaultRules !== false, }); } /** * Retrieves a logger instance by name. If the logger does not exist, it is created * and cached for subsequent calls. * @param {string} [name='default'] - The name of the logger to retrieve. * @param {Record<string, JsonValue>} [bindings] - Optional bindings to apply to the logger. * @returns {ILogger} The logger instance. */ getLogger(name = 'default', bindings) { // Create a stable cache key that doesn't depend on object reference const cacheKey = this.createCacheKey(name, bindings); if (this.loggerPool.has(cacheKey)) { return this.loggerPool.get(cacheKey); } const loggerName = name === 'default' ? this.serviceName : name; const dependencies = { contextManager: this.contextManager, serializerRegistry: this.serializerRegistry, maskingEngine: this.maskingEngine, syntropyLogInstance: this.syntropyLogInstance, }; const logger = new Logger(loggerName, this.transports, dependencies, { bindings, }); logger.level = this.globalLogLevel; this.loggerPool.set(cacheKey, logger); return logger; } /** * Creates a stable cache key for logger instances. * @private */ createCacheKey(name, bindings) { if (!bindings || Object.keys(bindings).length === 0) { return name; } // Sort keys to ensure consistent cache keys regardless of property order const sortedBindings = Object.keys(bindings) .sort() .reduce((result, key) => { result[key] = bindings[key]; return result; }, {}); try { return `${name}:${JSON.stringify(sortedBindings)}`; } catch { // Fallback for non-serializable objects return `${name}:${Object.keys(sortedBindings).sort().join(',')}`; } } /** * Calls the `flush` method on all configured transports to ensure buffered * logs are written before the application exits. */ async flushAllTransports() { const flushPromises = this.transports.map((transport) => transport.flush().catch((err) => { console.error(`Error flushing transport ${transport.constructor.name}:`, err); })); await Promise.allSettled(flushPromises); } /** * Shuts down the logger factory and all its transports. * This ensures that all buffered logs are written and resources are cleaned up. */ async shutdown() { try { // Flush all transports first await this.flushAllTransports(); // Clear the logger pool this.loggerPool.clear(); // Shutdown all transports if they have a shutdown method const shutdownPromises = this.transports.map((transport) => { if (typeof transport.shutdown === 'function') { return transport .shutdown() .catch((err) => { console.error(`Error shutting down transport ${transport.constructor.name}:`, err); }); } return Promise.resolve(); }); await Promise.allSettled(shutdownPromises); } catch (error) { console.error('Error during LoggerFactory shutdown:', error); } } } //# sourceMappingURL=LoggerFactory.js.map