@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
JavaScript
// @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
};