@plugjs/plug
Version:
PlugJS Build System ===================
161 lines • 5.73 kB
JavaScript
import { formatWithOptions } from 'node:util';
import { BuildFailure } from "../asserts.js";
import { currentContext } from "../async.js";
import { stripAnsi } from "../utils/ansi.js";
import { $gry } from "./colors.js";
import { emit } from "./emit.js";
import { DEBUG, ERROR, INFO, NOTICE, TRACE, WARN } from "./levels.js";
import { logOptions } from "./options.js";
import { ReportImpl } from "./report.js";
/* ========================================================================== */
/* Initial value of log colors, and subscribe to changes */
let _level = logOptions.level;
logOptions.on('changed', ({ level }) => {
_level = level;
});
/** Return a {@link Logger} associated with the specified task name. */
export function getLogger(task, indent) {
const context = currentContext();
const taskName = task === undefined ? (context?.taskName || '') : task;
const indentLevel = indent === undefined ? (context?.log.indent || 0) : 0;
return new LoggerImpl(taskName, emit, indentLevel);
}
/* ========================================================================== */
/** Weak set of already logged build failures */
const _loggedFailures = new WeakSet();
/** Default implementation of the {@link Logger} interface. */
class LoggerImpl {
_task;
_emitter;
indent;
_stack = [];
level = _level;
constructor(_task, _emitter, indent) {
this._task = _task;
this._emitter = _emitter;
this.indent = indent;
}
_emit(level, args, taskName = this._task) {
if (this.level > level)
return;
// The `BuildFailure` is a bit special case
const params = args.filter((arg) => {
if (arg instanceof BuildFailure) {
// Filter out any previously logged build failure and mark
if (_loggedFailures.has(arg))
return false;
_loggedFailures.add(arg);
// If the build failure has any root cause, log those
arg.errors?.forEach((error) => this._emit(level, [error]));
// Log this only if it has a message
if (!arg.message)
return false;
// Log the full error (with stack) if the _default_ level is DEBUG
if (_level < INFO)
return true;
// Log only the message in other cases
this._emit(level, [arg.message]);
return false;
}
else {
return true;
}
});
// If there's nothing left to log, then we're done
if (params.length === 0)
return;
// Prepare our options for logging
const options = { level, taskName, indent: this.indent };
// Dump any existing stack entry
if (this._stack.length) {
for (const { message, ...extras } of this._stack) {
this._emitter({ ...options, ...extras }, [message]);
}
this._stack.splice(0);
}
// Emit our log lines and return
this._emitter(options, params);
}
trace(...args) {
this._emit(TRACE, args);
}
debug(...args) {
this._emit(DEBUG, args);
}
info(...args) {
this._emit(INFO, args);
}
notice(...args) {
this._emit(NOTICE, args);
}
warn(...args) {
this._emit(WARN, args);
}
error(...args) {
this._emit(ERROR, args);
}
fail(...args) {
this._emit(ERROR, args);
throw BuildFailure.fail();
}
enter(...args) {
if (args.length) {
const [level, message] = args;
this._stack.push({ level, message, indent: this.indent });
}
this.indent++;
}
leave(...args) {
this._stack.pop();
this.indent--;
if (this.indent < 0)
this.indent = 0;
if (args.length) {
const [level, message] = args;
this._emit(level, [message]);
}
}
report(title) {
const emitter = (options, args) => {
if (this._stack.length) {
for (const { message, ...extras } of this._stack) {
this._emitter({ ...options, ...extras }, [message]);
}
this._stack.splice(0);
}
let { indent = 0, prefix = '' } = options;
prefix = this.indent ? $gry('| ') + prefix : prefix;
indent += this.indent;
this._emitter({ ...options, indent, prefix }, args);
};
return new ReportImpl(title, this._task, emitter);
}
}
/* ========================================================================== */
/** A test logger, writing to a buffer always _without_ colors/indent */
export class TestLogger extends LoggerImpl {
_lines = [];
constructor() {
super('', (options, args) => {
const { prefix = '', indent = 0 } = options;
const linePrefix = ''.padStart(indent * 2) + prefix;
/* Now for the normal logging of all our parameters */
formatWithOptions({ colors: false, breakLength: 120 }, ...args)
.split('\n').forEach((line) => {
const stripped = stripAnsi(line);
this._lines.push(`${linePrefix}${stripped}`);
});
}, 0);
}
/** Return the _current_ buffer for this instance */
get buffer() {
return this._lines.join('\n');
}
/** Reset the buffer and return any previously buffered text */
reset() {
const buffer = this.buffer;
this._lines = [];
return buffer;
}
}
//# sourceMappingURL=logger.js.map