UNPKG

@nowarajs/logger

Version:

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

117 lines (115 loc) 3.33 kB
// @bun import { LOGGER_ERROR_KEYS } from "./chunk-av6g3vv2.js"; // source/logger.ts import { BaseError } from "@nowarajs/error"; import { TypedEventEmitter } from "@nowarajs/typed-event-emitter"; import { once } from "events"; import { Transform } from "stream"; class Logger extends TypedEventEmitter { _sinks; _logStream; _pendingLogs = []; _maxPendingLogs; _isWriting = false; constructor(sinks = {}, maxPendingLogs = 1e4) { super(); this._sinks = sinks; this._maxPendingLogs = maxPendingLogs; this._logStream = new Transform({ objectMode: true, transform: (chunk, _, callback) => { this._executeStrategies(chunk.level, new Date(chunk.date), chunk.object, chunk.sinksNames).then(() => callback()).catch((error) => { this.emit("error", error); callback(); }); } }); } registerSink(name, sink) { if (this._sinks[name]) throw new BaseError(LOGGER_ERROR_KEYS.SINK_ALREADY_ADDED, { sinkName: name }); return new Logger({ ...this._sinks, [name]: sink }, this._maxPendingLogs); } unregisterSink(name) { if (!(name in this._sinks)) throw new BaseError(LOGGER_ERROR_KEYS.SINK_NOT_FOUND, { sinkName: name }); const { [name]: _, ...rest } = this._sinks; return new Logger(rest, this._maxPendingLogs); } registerSinks(sinks) { return sinks.reduce((logger, [name, sink]) => logger.registerSink(name, sink), this); } unregisterSinks(names) { let logger = this; for (const name of names) logger = logger.unregisterSink(name); return logger; } clearSinks() { return new Logger({}, this._maxPendingLogs); } error(object, sinksNames) { this._out("ERROR", object, sinksNames); } warn(object, sinksNames) { this._out("WARN", object, sinksNames); } info(object, sinksNames) { this._out("INFO", object, sinksNames); } debug(object, sinksNames) { this._out("DEBUG", object, sinksNames); } log(object, sinksNames) { this._out("LOG", object, sinksNames); } async _executeStrategies(level, date, object, sinksNames) { await Promise.all(sinksNames.map(async (name) => { try { await this._sinks[name]?.log(level, date, object); } catch (error) { throw new BaseError(LOGGER_ERROR_KEYS.SINK_ERROR, { sinkName: name, object, error }); } })); } _out(level, object, sinksNames) { const sinkKeys = Object.keys(this._sinks); if (sinkKeys.length === 0) throw new BaseError(LOGGER_ERROR_KEYS.NO_SINK_ADDED, { level, object }); if (this._pendingLogs.length >= this._maxPendingLogs) return; const log = { date: new Date().toISOString(), level, object, sinksNames: sinksNames ? sinksNames : sinkKeys }; 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 };