UNPKG

consola

Version:

Elegant Console Logger for Node.js and Browser

693 lines (556 loc) 16.3 kB
'use strict'; Object.defineProperty(exports, '__esModule', { value: true }); function _interopDefault (ex) { return (ex && (typeof ex === 'object') && 'default' in ex) ? ex['default'] : ex; } var util = _interopDefault(require('util')); var path = require('path'); var fs = require('fs'); var dayjs = _interopDefault(require('dayjs')); var stringWidth = _interopDefault(require('string-width')); var figures = _interopDefault(require('figures')); var chalk = _interopDefault(require('chalk')); var Types = { // Level 0 fatal: { level: 0 }, error: { level: 0 }, // Level 1 warn: { level: 1 }, // Level 2 log: { level: 2 }, // Level 3 info: { level: 3 }, success: { level: 3 }, // Level 4 debug: { level: 4 }, // Level 5 trace: { level: 5 }, // Silent silent: { level: Infinity }, // Legacy ready: { level: 3 }, start: { level: 3 } }; function isPlainObject(obj) { return Object.prototype.toString.call(obj) === '[object Object]'; } // TODO: remove for consola@3 function isLogObj(arg) { // Should be plain object if (!isPlainObject(arg)) { return false; } // Should contains either 'message' or 'args' field if (!arg.message && !arg.args) { return false; } // Handle non-standard error objects if (arg.stack) { return false; } return true; } let paused = false; const queue = []; class Consola { constructor(options = {}) { this._reporters = options.reporters || []; this._types = options.types || Types; this._level = options.level != null ? options.level : 3; this._defaults = options.defaults || {}; this._async = typeof options.async !== 'undefined' ? options.async : null; this._stdout = options.stdout; this._stderr = options.stdout; this._mockFn = options.mockFn; // Create logger functions for current instance for (const type in this._types) { this[type] = this._wrapLogFn(Object.assign({ type }, this._types[type], this._defaults)); } // Use _mockFn if is set if (this._mockFn) { this.mockTypes(); } } get level() { return this._level; } set level(newLevel) { // Ensure that newLevel does not exceeds type level boundaries let min = 0; let max = 0; for (const typeName in this._types) { const type = this._types[typeName]; if (type.level > max) { max = type.level; } else if (type.level < min) { min = type.level; } } // Set level this._level = Math.min(max, Math.max(min, newLevel)); } get stdout() { return this._stdout || console._stdout; // eslint-disable-line no-console } get stderr() { return this._stderr || console._stderr; // eslint-disable-line no-console } create(options) { return new Consola(Object.assign({ reporters: this._reporters, level: this._level, types: this._types, defaults: this._defaults, stdout: this._stdout, stderr: this._stderr, mockFn: this._mockFn }, options)); } withDefaults(defaults) { return this.create({ defaults: Object.assign({}, this._defaults, defaults) }); } withTag(tag) { return this.withDefaults({ tag: this._defaults.tag ? this._defaults.tag + ':' + tag : tag }); } addReporter(reporter) { this._reporters.push(reporter); return this; } removeReporter(reporter) { if (reporter) { const i = this._reporters.indexOf(reporter); if (i >= 0) { return this._reporters.splice(i, 1); } } else { this._reporters.splice(0); } return this; } setReporters(reporters) { this._reporters = Array.isArray(reporters) ? reporters : [reporters]; return this; } wrapAll() { this.wrapConsole(); this.wrapStd(); } restoreAll() { this.restoreConsole(); this.restoreStd(); } wrapConsole() { for (const type in this._types) { // Backup original value if (!console['__' + type]) { // eslint-disable-line no-console console['__' + type] = console[type]; // eslint-disable-line no-console } // Override console[type] = this[type]; // eslint-disable-line no-console } } restoreConsole() { for (const type in this._types) { // Restore if backup is available if (console['__' + type]) { // eslint-disable-line no-console console[type] = console['__' + type]; // eslint-disable-line no-console delete console['__' + type]; // eslint-disable-line no-console } } } wrapStd() { this._wrapStream(this.stdout, 'log'); this._wrapStream(this.stderr, 'log'); } _wrapStream(stream, type) { if (!stream) { return; } // Backup original value if (!stream.__write) { stream.__write = stream.write; } // Override stream.write = data => { this[type](String(data).trim()); }; } restoreStd() { this._restoreStream(this.stdout); this._restoreStream(this.stderr); } _restoreStream(stream) { if (!stream) { return; } if (stream.__write) { stream.write = stream.__write; delete stream.__write; } } pauseLogs() { paused = true; } resumeLogs() { paused = false; // Process queue const _queue = queue.splice(0); for (const item of _queue) { item[0]._logFn(item[1], item[2]); } } mockTypes(mockFn) { this._mockFn = mockFn || this._mockFn; if (typeof this._mockFn !== 'function') { return; } for (const type in this._types) { this[type] = this._mockFn(type, this._types[type]) || this[type]; } } _wrapLogFn(defaults) { function logFn() { if (paused) { queue.push([this, defaults, arguments]); return; } return this._logFn(defaults, arguments); } return logFn.bind(this); } _logFn(defaults, args) { if (defaults.level > this._level) { return this._async ? Promise.resolve(false) : false; } // Construct a new log object const logObj = Object.assign({ date: new Date(), args: [] }, defaults); // Consume arguments if (args.length === 1 && isLogObj(args[0])) { Object.assign(logObj, args[0]); } else { logObj.args = Array.from(args); } // Aliases if (logObj.message) { logObj.args.unshift(logObj.message); delete logObj.message; } if (logObj.additional) { if (!Array.isArray(logObj.additional)) { logObj.additional = logObj.additional.split('\n'); } logObj.args.push('\n' + logObj.additional.join('\n')); delete logObj.additional; } // Log if (this._async) { return this._logAsync(logObj); } else { this._log(logObj); } } _log(logObj) { for (const reporter of this._reporters) { reporter.log(logObj, { async: false, stdout: this.stdout, stderr: this.stderr }); } } _logAsync(logObj) { return Promise.all(this._reporters.map(reporter => reporter.log(logObj, { async: true, stdout: this.stdout, stderr: this.stderr }))); } } // Legacy support Consola.prototype.add = Consola.prototype.addReporter; Consola.prototype.remove = Consola.prototype.removeReporter; Consola.prototype.clear = Consola.prototype.removeReporter; Consola.prototype.withScope = Consola.prototype.withTag; Consola.prototype.mock = Consola.prototype.mockTypes; Consola.prototype.pause = Consola.prototype.pauseLogs; Consola.prototype.resume = Consola.prototype.resumeLogs; // Export class function assignGlobalReference(newInstance, referenceKey) { if (!newInstance.constructor || global[referenceKey] && !global[referenceKey].constructor) { throw new Error('Assigning to global reference is only supported for class instances'); } else if (newInstance.constructor && !global[referenceKey]) { global[referenceKey] = newInstance; } else if (!(newInstance instanceof global[referenceKey].constructor || global[referenceKey] instanceof newInstance.constructor)) { throw new Error(`Not a ${global[referenceKey].constructor.name} instance`); } const oldInstance = Object.create(global[referenceKey]); for (let prop in global[referenceKey]) { oldInstance[prop] = global[referenceKey][prop]; delete global[referenceKey][prop]; } for (let prop of Object.getOwnPropertySymbols(global[referenceKey])) { oldInstance[prop] = global[referenceKey][prop]; delete global[referenceKey][prop]; } for (let prop in newInstance) { global[referenceKey][prop] = newInstance[prop]; } for (let prop of Object.getOwnPropertySymbols(newInstance)) { global[referenceKey][prop] = newInstance[prop]; } return oldInstance; } function assignGlobalConsola(newConsola) { return assignGlobalReference(newConsola, 'consola'); } function parseStack(stack) { const cwd = process.cwd() + path.sep; let lines = stack.split('\n').splice(1).map(l => l.trim().replace('file://', '').replace(cwd, '')); return lines; } function writeStream(data, stream, mode = 'default') { const write = stream.__write || stream.write; switch (mode) { case 'async': return new Promise(resolve => { if (write.call(stream, data) === true) { resolve(); } else { stream.once('drain', () => { resolve(); }); } }); case 'sync': return fs.writeSync(stream.fd, data); default: return write.call(stream, data); } } function formatDate(timeFormat, date) { return dayjs(date).format(timeFormat); } const DEFAULTS = { dateFormat: 'HH:mm:ss', formatOptions: { colors: false, compact: true } }; const bracket = x => x ? `[${x}]` : ''; class BasicReporter { constructor(options) { this.options = Object.assign({}, DEFAULTS, options); } formatStack(stack) { return ' ' + parseStack(stack).join('\n '); } formatArgs(args) { const _args = args.map(arg => { if (arg && typeof arg.stack === 'string') { return arg.message + '\n' + this.formatStack(arg.stack); } return arg; }); // Only supported with Node >= 10 // https://nodejs.org/api/util.html#util_util_inspect_object_options if (typeof util.formatWithOptions === 'function') { return util.formatWithOptions(this.options.formatOptions, ..._args); } else { return util.format(..._args); } } formatDate(date) { return formatDate(this.options.dateFormat, date); } filterAndJoin(arr) { return arr.filter(x => x).join(' '); } formatLogObj(logObj) { const message = this.formatArgs(logObj.args); const date = this.formatDate(logObj.date); const type = logObj.type.toUpperCase(); return this.filterAndJoin([bracket(date), bracket(logObj.tag), bracket(type), message]); } log(logObj, { async, stdout, stderr } = {}) { const line = this.formatLogObj(logObj, { width: stdout.columns ? stdout.columns - 1 : 80 }); return writeStream(line + '\n', logObj.level < 2 ? stderr : stdout, async ? 'async' : 'default'); } } const TYPE_COLOR_MAP = { 'info': 'cyan' }; const LEVEL_COLOR_MAP = { 0: 'red', 1: 'yellow', 2: 'white', 3: 'green' }; class BrowserReporter { constructor(options) { this.options = Object.assign({}, options); } log(logObj) { const consoleLogFn = logObj.level < 1 // eslint-disable-next-line no-console ? console.__error || console.error : console.__log || console.log; // Type const type = logObj.type !== 'log' ? `[${logObj.type.toUpperCase()}]` : ''; // Tag const tag = logObj.tag ? `[${logObj.tag.toUpperCase()}]` : ''; // Date const date = `[${new Date(logObj.date).toLocaleTimeString()}]`; // Styles const color = TYPE_COLOR_MAP[logObj.type] || LEVEL_COLOR_MAP[logObj.level]; const styleColor = `color: ${color}; background-color: inherit;`; const styleGrey = `color: ${logObj.additionalColor || 'grey'}; background-color: inherit;`; // Log to the console consoleLogFn('%c' + date + tag + '%c' + type, styleGrey, styleColor, ...logObj.args); } } const _colorCache = {}; function chalkColor(name) { let color = _colorCache[name]; if (color) { return color; } if (name[0] === '#') { color = chalk.hex(name); } else { color = chalk[name] || chalk.keyword(name); } _colorCache[name] = color; return color; } const _bgColorCache = {}; function chalkBgColor(name) { let color = _bgColorCache[name]; if (color) { return color; } if (name[0] === '#') { color = chalk.bgHex(name); } else { color = chalk['bg' + name[0].toUpperCase() + name.slice(1)] || chalk.bgKeyword(name); } _bgColorCache[name] = color; return color; } const DEFAULTS$1 = { secondaryColor: 'grey', formatOptions: { colors: true, compact: false } }; const TYPE_ICONS = { info: figures('ℹ'), success: figures('✔'), debug: figures('›'), trace: figures('›'), log: '' }; class FancyReporter extends BasicReporter { constructor(options) { super(Object.assign({}, DEFAULTS$1, options)); } formatStack(stack) { const grey = chalkColor('grey'); const cyan = chalkColor('cyan'); return '\n' + parseStack(stack).map(line => ' ' + line.replace(/^at +/, m => grey(m)).replace(/\((.+)\)/, (_, m) => `(${cyan(m)})`)).join('\n'); } formatType(logObj, isBadge) { const typeColor = TYPE_COLOR_MAP[logObj.type] || LEVEL_COLOR_MAP[logObj.level] || this.options.secondaryColor; if (isBadge) { return chalkBgColor(typeColor).black(` ${logObj.type.toUpperCase()} `); } const _type = typeof TYPE_ICONS[logObj.type] === 'string' ? TYPE_ICONS[logObj.type] : logObj.icon || logObj.type; return _type ? chalkColor(typeColor)(_type) : ''; } formatLogObj(logObj, { width }) { const [message, ...additional] = this.formatArgs(logObj.args).split('\n'); const isBadge = typeof logObj.badge !== 'undefined' ? Boolean(logObj.badge) : logObj.level < 2; const secondaryColor = chalkColor(this.options.secondaryColor); const date = secondaryColor(this.formatDate(logObj.date)); const type = this.formatType(logObj, isBadge); const tag = logObj.tag ? secondaryColor(logObj.tag) : ''; let left = this.filterAndJoin([type, message]); let right = this.filterAndJoin([tag, date]); const space = width - stringWidth(left) - stringWidth(right) - 2; let line = space > 0 ? left + ' '.repeat(space) + right : left; line += additional.length ? '\n' + additional.join('\n') : ''; return isBadge ? '\n' + line + '\n' : line; } } class JSONReporter { constructor({ stream } = {}) { this.stream = stream || process.stdout; } log(logObj) { this.stream.write(JSON.stringify(logObj) + '\n'); } } // This reporter is compatible with Winston 3 // https://github.com/winstonjs/winston // eslint-disable-next-line const _require = typeof __non_webpack_require__ !== 'undefined' ? __non_webpack_require__ : require; // bypass webpack class WinstonReporter { constructor(logger) { if (logger && logger.log) { this.logger = logger; } else { const winston = _require('winston'); this.logger = winston.createLogger(Object.assign({ level: 'info', format: winston.format.simple(), transports: [new winston.transports.Console()] }, logger)); } } log(logObj) { const args = [].concat(logObj.args); const arg0 = args.shift(); this.logger.log({ level: levels[logObj.level] || 'info', label: logObj.tag, message: arg0, args: args, timestamp: logObj.date.getTime() / 1000 }); } } const levels = { 0: 'error', 1: 'warn', 2: 'info', 3: 'verbose', 4: 'debug', 5: 'silly' }; exports.Consola = Consola; exports.Types = Types; exports.isLogObj = isLogObj; exports.assignGlobalConsola = assignGlobalConsola; exports.BasicReporter = BasicReporter; exports.BrowserReporter = BrowserReporter; exports.FancyReporter = FancyReporter; exports.JSONReporter = JSONReporter; exports.WinstonReporter = WinstonReporter;