UNPKG

@nowarajs/logger

Version:

Type-safe logging library for Bun with advanced TypeScript body intersection, modular strategy pattern, transform streams, and immutable API design.

135 lines (132 loc) 3.98 kB
// @bun // source/logger.ts import { BaseError } from "@nowarajs/error"; import { TypedEventEmitter } from "@nowarajs/typed-event-emitter"; import { once } from "events"; import { Transform } from "stream"; // source/enums/loggerErrorKeys.ts var LOGGER_ERROR_KEYS = { STRATEGY_ALREADY_ADDED: "logger.error.strategy_already_added", STRATEGY_NOT_FOUND: "logger.error.strategy_not_found", NO_STRATEGY_ADDED: "logger.error.no_strategy_added", STRATEGY_ERROR: "logger.error.strategy_error" }; // source/logger.ts class Logger extends TypedEventEmitter { _strategies; _logStream; _pendingLogs = []; _maxPendingLogs; _isWriting = false; constructor(strategies = {}, maxPendingLogs = 1e4) { super(); this._strategies = strategies; this._maxPendingLogs = maxPendingLogs; this._logStream = new Transform({ objectMode: true, transform: (chunk, _, callback) => { this._executeStrategies(chunk.level, new Date(chunk.date), chunk.object, chunk.strategiesNames).then(() => callback()).catch((error) => { this.emit("error", error); callback(); }); } }); } registerStrategy(name, strategy) { if (this._strategies[name]) throw new BaseError({ message: LOGGER_ERROR_KEYS.STRATEGY_ALREADY_ADDED, cause: { strategyName: name } }); return new Logger({ ...this._strategies, [name]: strategy }, this._maxPendingLogs); } unregisterStrategy(name) { if (!(name in this._strategies)) throw new BaseError({ message: LOGGER_ERROR_KEYS.STRATEGY_NOT_FOUND, cause: { strategyName: name } }); const { [name]: _, ...rest } = this._strategies; return new Logger(rest, this._maxPendingLogs); } registerStrategies(strategies) { return strategies.reduce((logger, [name, strategy]) => logger.registerStrategy(name, strategy), this); } unregisterStrategies(names) { let logger = this; for (const name of names) logger = logger.unregisterStrategy(name); return logger; } clearStrategies() { return new Logger({}, this._maxPendingLogs); } error(object, strategiesNames) { this._out("ERROR", object, strategiesNames); } warn(object, strategiesNames) { this._out("WARN", object, strategiesNames); } info(object, strategiesNames) { this._out("INFO", object, strategiesNames); } debug(object, strategiesNames) { this._out("DEBUG", object, strategiesNames); } log(object, strategiesNames) { this._out("LOG", object, strategiesNames); } async _executeStrategies(level, date, object, strategiesNames) { await Promise.all(strategiesNames.map(async (name) => { try { await this._strategies[name]?.log(level, date, object); } catch (error) { throw new BaseError({ message: LOGGER_ERROR_KEYS.STRATEGY_ERROR, cause: { strategyName: name, object, error } }); } })); } _out(level, object, strategiesNames) { const strategyKeys = Object.keys(this._strategies); if (strategyKeys.length === 0) throw new BaseError({ message: LOGGER_ERROR_KEYS.NO_STRATEGY_ADDED, cause: { level, object } }); if (this._pendingLogs.length >= this._maxPendingLogs) return; const log = { date: new Date().toISOString(), level, object, strategiesNames: strategiesNames ? strategiesNames : strategyKeys }; this._pendingLogs.push(log); if (!this._isWriting) { this._isWriting = true; setImmediate(() => { this._writeLog(); }); } } async _writeLog() { while (this._pendingLogs.length > 0) { const pendingLog = this._pendingLogs.shift(); if (!pendingLog) continue; const canWrite = this._logStream.write(pendingLog); if (!canWrite) await once(this._logStream, "drain"); } this._isWriting = false; this.emit("end"); } } export { Logger };