/** * @license * Copyright 2016 Google LLC * SPDX-License-Identifier: Apache-2.0 */ import process from 'process'; import {EventEmitter} from 'events'; import debug from 'debug'; import * as marky from 'marky'; const isWindows = process.platform === 'win32'; // @ts-expect-error: process.browser is set via Rollup. const isBrowser = process.browser; const colors = { red: isBrowser ? 'crimson' : 1, yellow: isBrowser ? 'gold' : 3, cyan: isBrowser ? 'darkturquoise' : 6, green: isBrowser ? 'forestgreen' : 2, blue: isBrowser ? 'steelblue' : 4, magenta: isBrowser ? 'palevioletred' : 5, }; // allow non-red/yellow colors for debug() debug.colors = [colors.cyan, colors.green, colors.blue, colors.magenta]; class Emitter extends EventEmitter { // yarn build-types fails without this! // https://github.com/microsoft/TypeScript/issues/41672#issuecomment-2303803072 constructor(options) { super(options); } /** * Fires off all status updates. Listen with * `require('lib/log').events.addListener('status', callback)` * @param {string} title * @param {!Array<*>} argsArray */ issueStatus(title, argsArray) { if (title === 'status' || title === 'statusEnd') { this.emit(title, [title, ...argsArray]); } } /** * Fires off all warnings. Listen with * `require('lib/log').events.addListener('warning', callback)` * @param {string} title * @param {!Array<*>} argsArray */ issueWarning(title, argsArray) { this.emit('warning', [title, ...argsArray]); } } const loggersByTitle = {}; const loggingBufferColumns = 25; let level_; class Log { static _logToStdErr(title, argsArray) { const log = Log.loggerfn(title); log(...argsArray); } /** * @param {string} title */ static loggerfn(title) { title = `LH:${title}`; let log = loggersByTitle[title]; if (!log) { log = debug(title); loggersByTitle[title] = log; // errors with red, warnings with yellow. if (title.endsWith('error')) { log.color = colors.red; } else if (title.endsWith('warn')) { log.color = colors.yellow; } } return log; } /** * @param {string} level */ static setLevel(level) { level_ = level; switch (level) { case 'silent': debug.enable('-LH:*'); break; case 'verbose': debug.enable('LH:*'); break; case 'warn': debug.enable('-LH:*, LH:*:warn, LH:*:error'); break; case 'error': debug.enable('-LH:*, LH:*:error'); break; default: debug.enable('LH:*, -LH:*:verbose'); } } /** * A simple formatting utility for event logging. * @param {string} prefix * @param {!Object} data A JSON-serializable object of event data to log. * @param {string=} level Optional logging level. Defaults to 'log'. */ static formatProtocol(prefix, data, level) { const columns = (!process || process.browser) ? Infinity : process.stdout.columns; const method = data.method || '?????'; const maxLength = columns - method.length - prefix.length - loggingBufferColumns; // IO.read ignored here to avoid logging megabytes of trace data const snippet = (data.params && method !== 'IO.read') ? JSON.stringify(data.params).substr(0, maxLength) : ''; Log._logToStdErr(`${prefix}:${level || ''}`, [method, snippet]); } /** * @return {boolean} */ static isVerbose() { return level_ === 'verbose'; } /** * @param {{msg: string, id: string, args?: any[]}} status * @param {string} level */ static time({msg, id, args = []}, level = 'log') { marky.mark(id); Log[level]('status', msg, ...args); } /** * @param {{msg: string, id: string, args?: any[]}} status * @param {string} level */ static timeEnd({msg, id, args = []}, level = 'verbose') { Log[level]('statusEnd', msg, ...args); marky.stop(id); } /** * @param {string} title * @param {...any} args */ static log(title, ...args) { Log.events.issueStatus(title, args); return Log._logToStdErr(title, args); } /** * @param {string} title * @param {...any} args */ static warn(title, ...args) { Log.events.issueWarning(title, args); return Log._logToStdErr(`${title}:warn`, args); } /** * @param {string} title * @param {...any} args */ static error(title, ...args) { return Log._logToStdErr(`${title}:error`, args); } /** * @param {string} title * @param {...any} args */ static verbose(title, ...args) { Log.events.issueStatus(title, args); return Log._logToStdErr(`${title}:verbose`, args); } /** * Add surrounding escape sequences to turn a string green when logged. * @param {string} str * @return {string} */ static greenify(str) { return `${Log.green}${str}${Log.reset}`; } /** * Add surrounding escape sequences to turn a string red when logged. * @param {string} str * @return {string} */ static redify(str) { return `${Log.red}${str}${Log.reset}`; } static get green() { return '\x1B[32m'; } static get red() { return '\x1B[31m'; } static get yellow() { return '\x1b[33m'; } static get purple() { return '\x1b[95m'; } static get reset() { return '\x1B[0m'; } static get bold() { return '\x1b[1m'; } static get dim() { return '\x1b[2m'; } static get tick() { return isWindows ? '\u221A' : '✓'; } static get cross() { return isWindows ? '\u00D7' : '✘'; } static get whiteSmallSquare() { return isWindows ? '\u0387' : '▫'; } static get heavyHorizontal() { return isWindows ? '\u2500' : '━'; } static get heavyVertical() { return isWindows ? '\u2502 ' : '┃ '; } static get heavyUpAndRight() { return isWindows ? '\u2514' : '┗'; } static get heavyVerticalAndRight() { return isWindows ? '\u251C' : '┣'; } static get heavyDownAndHorizontal() { return isWindows ? '\u252C' : '┳'; } static get doubleLightHorizontal() { return '──'; } } Log.events = new Emitter(); /** * @return {PerformanceEntry[]} */ Log.takeTimeEntries = () => { const entries = marky.getEntries(); marky.clear(); return entries; }; /** * @return {PerformanceEntry[]} */ Log.getTimeEntries = () => marky.getEntries(); export default Log;