UNPKG

@plugjs/plug

Version:
161 lines 5.73 kB
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