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