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