UNPKG

komi-logger

Version:

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

179 lines (173 loc) 5.1 kB
// @bun import { KomiError } from "./chunk-506035av.js"; // source/logger.ts import { once } from "events"; import { Transform } from "stream"; // source/enums/loggerErrorKeys.ts var loggerErrorKeys = { strategyAlreadyAdded: "komi-logger.error.strategy_already_added", strategyNotFound: "komi-logger.error.strategy_not_found", noStrategyAdded: "komi-logger.error.no_strategy_added", loggerStrategyError: "komi-logger.error.strategy_error" }; // source/utils/typedEventEmitter.ts import { EventEmitter } from "events"; class TypedEventEmitter extends EventEmitter { emit(event, ...args) { return super.emit(event, ...args); } on(event, listener) { return super.on(event, listener); } once(event, listener) { return super.once(event, listener); } addListener(event, listener) { return super.addListener(event, listener); } removeListener(event, listener) { return super.removeListener(event, listener); } off(event, listener) { return super.off(event, listener); } listenerCount(event) { return super.listenerCount(event); } listeners(event) { return super.listeners(event); } rawListeners(event) { return super.rawListeners(event); } prependListener(event, listener) { return super.prependListener(event, listener); } prependOnceListener(event, listener) { return super.prependOnceListener(event, listener); } } // 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 KomiError({ key: loggerErrorKeys.strategyAlreadyAdded, message: `The strategy "${name}" is already added.`, cause: { strategyName: name } }); return new Logger({ ...this._strategies, [name]: strategy }, this._maxPendingLogs); } unregisterStrategy(name) { if (!(name in this._strategies)) throw new KomiError({ key: loggerErrorKeys.strategyNotFound, message: `The strategy "${String(name)}" is 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 KomiError({ key: loggerErrorKeys.loggerStrategyError, message: `An error occurred while executing the strategy "${String(name)}".`, cause: { strategyName: name, object, error } }); } })); } _out(level, object, strategiesNames) { const strategyKeys = Object.keys(this._strategies); if (strategyKeys.length === 0) throw new KomiError({ message: "No strategy is added.", key: loggerErrorKeys.noStrategyAdded }); 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 };