const chalk = require('chalk');
const isWin = /^win/.test(process.platform);
const logLevels = [
'all',
'silly',
'debug',
'verbose',
'timing',
'http',
'notice',
'info',
'success',
'warn',
'quiet',
'error',
'silent'
];
const textPrefixes = {
error: '[ERR]',
warn: '[WRN]',
warning: '[WRN]',
http: '[NET]',
info: '[INF]',
success: '[OK ]',
notice: '[NOT]',
timing: '[TIM]',
verbose: '[VRB]',
debug: '[DBG]',
silly: '[LOL]',
log: ''
};
const asciiPrefixes = {
error: isWin ? '►' : '✖',
warn: isWin ? '‼' : '⚠',
warning: isWin ? '‼' : '⚠',
http: isWin ? '≡' : '☷',
info: isWin ? 'i' : 'ℹ',
success: isWin ? '√' : '✔',
notice: isWin ? 'i' : 'ℵ',
timing: isWin ? '+' : '◷',
verbose: isWin ? 'i' : 'ℹ',
debug: isWin ? 'i': 'ℹ',
silly: isWin ? '☺' : '☺',
log: ''
};
const emojiPrefixes = {
error: '❌ ',
warn: '〽️',
warning: '〽️',
http: '🌐 ',
info: '➡️ ',
success: '✅ ',
notice: '❕ ',
timing: '🕒 ',
verbose: '🎤 ',
debug: '🔬 ',
silly: '🙃 ',
log: ''
};
const colorStyles = {
error: chalk.red.bold,
warn: chalk.yellow.bold,
warning: chalk.yellow.bold,
http: chalk.cyan.bold,
info: chalk.green.bold,
success: chalk.green.bold,
notice: chalk.blue.bold,
timing: chalk.blue,
verbose: chalk.blue.bold,
debug: chalk.gray.bold,
silly: chalk.white.bold,
/** @private */
log: t => t
};
const npmPrefixes = {
error: 'ERR',
warn: 'WRN',
warning: 'WRN',
http: 'NET',
info: 'INF',
success: 'OK ',
notice: 'NOT',
timing: 'TIM',
verbose: 'VRB',
debug: 'DBG',
silly: 'LOL',
log: '>'
};
/**
* This function serves as the loog object and a function to reconfigure it.
* A quick example:
*
* // Use its methods directly, with the default configuration
* const loog = require('loog');
* loog.info('Hi!');
*
* // Use it as a function to reconfigure the instance
* const loog = require('loog')({
* prefixStyle: 'emoji'
* });
* loog.info('Hi!');
*
* @function loog
* @param {Object} [config] - The initial config
* @param {string} [config.prefixStyle=text] - The prefix style, one of ['text', 'emoji', 'ascii', 'none'].
* @param {string} [config.logLevel=info] - The log level, one of ['silly', 'debug', 'info', 'warn', 'error', 'silent'].
*/
function wrap () {
let ex = function Loog (cfg) {
_instance = new Log(Object.assign({}, defaultCfg, cfg));
return wrap();
}
Object.getOwnPropertyNames(Log.prototype).forEach((method)=> {
if (method !== "constructor" && method.charAt(0) !== "_") {
ex[method] = _instance[method].bind(_instance);
}
});
ex.$levels = logLevels;
ex.$methods = logLevels.filter(l => !~['all','quiet','silent'].indexOf(l)).concat(['log']);
ex.$prefixes = {
text: textPrefixes,
ascii: asciiPrefixes,
emoji: emojiPrefixes,
npm: npmPrefixes
};
ex.$colors = colorStyles;
return ex;
}
function getLogFn(me, level) {
return function _ () {
if (_.enable && !me._mute) {
let args = Array.prototype.slice.call(arguments);
if (me.cfg.prefixes[level]) {
if (me.cfg._noColors) {
args.unshift(me.cfg.prefixes[level]);
} else {
args.unshift(me.cfg.colors[level](me.cfg.prefixes[level]));
}
}
if (me._indentation > 0) {
args.unshift(" ".repeat(me._indentation));
}
if (me.cfg.process) {
args.unshift(me.cfg.process);
}
if (me.cfg._noPrefix) {
if (me.cfg._noColors) {
console.log(args.join(' '));
} else {
console.log(me.cfg.colors[level](args.join(' ')));
}
} else {
console.log(args.join(' '));
}
}
return me;
}
}
/**
* A simple log with an extra character.
*
* The result of importing this module serves as the `loog` object and a function to reconfigure it.
* A quick example:
*
* // Use its methods directly, with the default configuration
* const loog = require('loog');
* loog.info('Hi!');
*
* // Use it as a function to reconfigure the instance
* const loog = require('loog')({
* prefixStyle: 'emoji'
* });
* loog.info('Hi!');
*
* See the docs for the global {@link loog} function for more documentation on how to reconfigure loog.
* @module loog
*/
class Log {
constructor (config) {
this._indentation = 0;
this._counters = {};
this._trackers = {};
this.cfg = Object.assign({}, config);
if (!('prefixes' in this.cfg)) {
switch (this.cfg.prefixStyle) {
case "text":
this.cfg.prefixes = textPrefixes;
break;
case "emoji":
this.cfg.prefixes = emojiPrefixes;
break;
case "ascii":
this.cfg.prefixes = asciiPrefixes;
break;
case "npm":
this.cfg.prefixes = npmPrefixes;
break;
default:
this.cfg.prefixes = {};
this.cfg._noPrefix = true;
break;
}
} else if (Object.keys(this.cfg.prefixes).length == 0) {
this.cfg._noPrefix = true;
}
if (!('colors' in this.cfg)) {
this.cfg.colors = colorStyles;
} else if (Object.keys(this.cfg.colors).length == 0) {
this.cfg._noColors = true;
}
this.setLogLevel(config.logLevel);
}
/****** Instance methods ******/
/**
* Clears the console, does nothing if muted
* @function
* @name module:loog#clear
* @see {@link module:loog#clearLine}
* @returns {loog}
*/
clear () {
if (!this._mute) {
process.stdout.write('\x1Bc');
}
return this;
}
/**
* Clears a counter
* @function
* @name module:loog#clearCount
* @param {string} [message=''] - The message or label to clear the counter for
* @see {@link module:loog#count}
* @returns {loog}
*/
clearCount (label) {
delete this._counters[label||'_'];
}
/**
* Clears the last line from the console, does nothing if muted
* @function
* @name module:loog#clearLine
* @see {@link module:loog#clear}
* @returns {loog}
*/
clearLine () {
if (!this._mute) {
process.stdout.write('\u001B[A\u001B[K');
}
return this;
}
/**
* Count a given message or label and show a message (optional)
* @function
* @name module:loog#count
* @param {string} [message=null] - The message or label
* @param {string} [type=log] - The type of log to use, pass `null` to skip logging the current count
* @see {@link module:loog#clearCount}
* @returns {loog}
*/
count (label, type='log') {
let m = '';
if (label) {
m = `${label}: `;
} else {
label = '_';
}
this._counters[label] = (this._counters[label] || 0) + 1;
if (type!==null) {
this[type](`${m}${this._counters[label]}`);
}
return this;
}
/**
* Indent subsequent log statements one level deeper.
* @function
* @name module:loog#indent
* @see {@link module:loog#outdent}
* @see {@link module:loog#pauseIndentation}
* @see {@link module:loog#resumeIndentation}
* @returns {loog}
*/
indent () {
this._indentation++;
return this;
}
/**
* Logs a JSON object using JSON.stringify
* @function
* @name module:loog$json
* @param {Object} json - The JSON object to log
* @param {number} [indent=4] - The number of spaces on each indent level
* @param {string} [type=log] - The logging method to use
*/
json (json, indent = 4, type='log') {
JSON.stringify(json, null, indent).split('\n').map(l => {this[type](l)});
}
/**
* Mutes all subsequent log statements
* @function
* @name module:loog#mute
* @see {@link module:loog#unmute}
* @returns {loog}
*/
mute () {
this._mute = true;
return this;
}
/**
* Outdent subsequent log statements one level.
* @function
* @name module:loog#outdent
* @see {@link module:loog#indent}
* @see {@link module:loog#pauseIndentation}
* @see {@link module:loog#resumeIndentation}
* @see {@link module:loog#resetIndentation}
* @returns {loog}
*/
outdent () {
if (this._indentation > 0) {
this._indentation--;
}
return this;
}
/**
* Temporarily pause indentation, subsequent statements will be logged at the root level.
* Use {@link module:loog#resumeIndentation} to recover the indent level.
* @function
* @name module:loog#pauseIndentation
* @see {@link module:loog#resumeIndentation}
* @see {@link module:loog#indent}
* @see {@link module:loog#outdent}
* @see {@link module:loog#resetIndentation}
* @returns {loog}
*/
pauseIndentation () {
this._indentWas = this._indentation;
this._indentation = 0;
return this;
}
/**
* Reports existing trackers, does nothing if muted
* @function
* @name module:loog#report
* @see {@link module:loog#mute}
* @returns {loog}
*/
report (label, type = 'log') {
let me = this;
if (label && label in this._trackers) {
me[type](`${label}: ${this._trackers[label]}`);
} else {
let msg = Object.keys(this._trackers).sort().map((k) => {
return `${k}: ${this._trackers[k]}`;
});
if (msg.length) {
me[type](msg.join(', '));
}
}
return this;
}
/**
* Resets the indent level to 0.
* @function
* @name module:loog#resetIndentation
* @see {@link module:loog#indent}
* @see {@link module:loog#outdent}
* @see {@link module:loog#pauseIndentation}
* @see {@link module:loog#resumeIndentation}
* @returns {loog}
*/
resetIndentation () {
this._indentation = 0;
delete this._indentWas;
return this;
}
/**
* Resumes the previously paused indentation.
* @function
* @name module:loog#resumeIndentation
* @see {@link module:loog#pauseIndentation}
* @see {@link module:loog#indent}
* @see {@link module:loog#outdent}
* @see {@link module:loog#resetIndentation}
* @returns {loog}
*/
resumeIndentation () {
this._indentation = this._indentWas;
delete this._indentWas;
return this;
}
/**
* Changes the log level for subsequent statements.
*
* Possible levels are:
*
* - all
* - silly
* - debug
* - verbose
* - timing
* - http
* - notice
* - info
* - success
* - warn
* - quiet
* - error
* - silent
*
* Log levels are aggregative, so they enable/or disable log functions like this:
*
* - Level: **all**, **silly**
* - `loog.error`
* - `loog.warn`
* - `loog.warning`
* - `loog.info`
* - `loog.success`
* - `loog.notice`
* - `loog.http`
* - `loog.timing`
* - `loog.verbose`
* - `loog.debug`
* - `loog.silly`
* - `loog.log`
* - Level: **debug**, **verbose**
* - `loog.error`
* - `loog.warn`
* - `loog.warning`
* - `loog.info`
* - `loog.success`
* - `loog.notice`
* - `loog.http`
* - `loog.timing`
* - `loog.verbose`
* - `loog.debug`
* - `loog.log`
* - Level: **timing**
* - `loog.error`
* - `loog.warn`
* - `loog.warning`
* - `loog.info`
* - `loog.success`
* - `loog.notice`
* - `loog.http`
* - `loog.timing`
* - `loog.log`
* - Level: **http**
* - `loog.error`
* - `loog.warn`
* - `loog.warning`
* - `loog.info`
* - `loog.success`
* - `loog.notice`
* - `loog.http`
* - `loog.log`
* - Level: **notice**
* - `loog.error`
* - `loog.warn`
* - `loog.warning`
* - `loog.info`
* - `loog.success`
* - `loog.notice`
* - `loog.log`
* - Level: **info**
* - `loog.error`
* - `loog.warn`
* - `loog.warning`
* - `loog.info`
* - `loog.success`
* - `loog.log`
* - Level: **warn**
* - `loog.error`
* - `loog.warn`
* - `loog.warning`
* - `loog.log`
* - Level: **error**, **quiet**
* - `loog.error`
* - `loog.log`
* - Level: **silent**
* - *(none)*
*
* @function
* @name module:loog#setLogLevel
* @param {string} [newLevel=quiet] - The log level to set, must be one of <br> - all<br> - silly<br> - debug<br> - verbose<br> - timing<br> - http<br> - notice<br> - info<br> - warn<br> - quiet<br> - error<br> - silent
* @returns {loog}
*/
setLogLevel(newLevel) {
let me = this;
if (!newLevel || logLevels.indexOf(newLevel) === -1) {
newLevel = 'info';
}
switch (newLevel) {
case "all":
case "silly":
me.silly.enable = true;
case "debug":
case "verbose":
me.debug.enable = true;
me.verbose.enable = true;
case "timing":
me.timing.enable = true;
case "http":
me.http.enable = true;
case "notice":
me.notice.enable = true;
case "info":
me.info.enable = true;
me.success.enable = true;
case "warn":
me.warn.enable = true;
me.warning.enable = true;
case "quiet":
case "error":
me.error.enable = true;
me.log.enable = true;
break;
case "silent":
me.mute();
break;
}
return this;
}
/**
* Tracks a label or message so that it can be later retrieved using `report`.
* @function
* @name module:loog#track
* @param {string} label - The label or message to track
* @see {@link module:loog#report}
* @see {@link module:loog#untrack}
* @returns {loog}
*/
track (label) {
if (label) {
this._trackers[label] = (this._trackers[label] || 0) + 1;
}
return this;
}
/**
* Unmutes all subsequent log statements
* @function
* @name module:loog#unmute
* @see {@link module:loog#mute}
* @returns {loog}
*/
unmute () {
this._mute = false;
return this;
}
/**
* Stops tracking a label
* @function
* @name module:loog#untrack
* @param {string} label - The label or message to stop tracking
* @see {@link module:loog#track}
* @returns {loog}
*/
untrack (label) {
if (label && label in this._trackers) {
delete this._trackers[label];
}
return this;
}
/****** Private methods ******/
/**
* Logs `message` as **error**.
*
* Visible when `logLevel` is set to: `all`, `silly`, `debug`, `verbose`, `timing`, `http`, `notice`, `info`, `warn`, `quiet` or `error`.
*
* @function
* @name module:loog#error
* @param {string} message - The message to log
* @returns {loog}
*/
/**
* Logs `message` as **warn**.
*
* Visible when `logLevel` is set to: `all`, `silly`, `debug`, `verbose`, `timing`, `http`, `notice`, `info` or `warn`
*
* @function
* @name module:loog#warn
* @alias module:loog#warning
* @param {string} message - The message to log
* @returns {loog}
*/
/**
* Logs `message` as **info**.
*
* Visible when `logLevel` is set to: `all`, `silly`, `debug`, `verbose`, `timing`, `http`, `notice` or `info`
*
* @function
* @name module:loog#info
* @param {string} message - The message to log
* @returns {loog}
*/
/**
* Logs `message` as **success**.
*
* Visible when `logLevel` is set to: `all`, `silly`, `debug`, `verbose`, `timing`, `http`, `notice` or `info`
*
* @function
* @name module:loog#success
* @param {string} message - The message to log
* @returns {loog}
*/
/**
* Logs `message` as **notice**.
*
* Visible when `logLevel` is set to: `all`, `silly`, `debug`, `verbose`, `timing`, `http` or `notice`
*
* @function
* @name module:loog#notice
* @param {string} message - The message to log
* @returns {loog}
*/
/**
* Logs `message` as **http**.
*
* Visible when `logLevel` is set to: `all`, `silly`, `debug`, `verbose`, `timing` or `http`
*
* @function
* @name module:loog#http
* @param {string} message - The message to log
* @returns {loog}
*/
/**
* Logs `message` as **timing**.
*
* Visible when `logLevel` is set to: `all`, `silly`, `debug`, `verbose` or `timing`
*
* @function
* @name module:loog#timing
* @param {string} message - The message to log
* @returns {loog}
*/
/**
* Logs `message` as **verbose**.
*
* Visible when `logLevel` is set to: `all`, `silly`, `debug` or `verbose`
*
* @function
* @name module:loog#verbose
* @param {string} message - The message to log
* @returns {loog}
*/
/**
* Logs `message` as **debug**.
*
* Visible when `logLevel` is set to: `all`, `silly` or `debug`
*
* @function
* @name module:loog#debug
* @param {string} message - The message to log
* @returns {loog}
*/
/**
* Logs `message` as **silly**.
*
* Visible when `logLevel` is set to: `all` or `silly`
*
* @function
* @name module:loog#silly
* @param {string} message - The message to log
* @returns {loog}
*/
/**
* Issues a log statement marked as 'log'
* @function
* @name module:loog#log
* @param {string} message - The message to log
* @returns {loog}
*/
_ () {} // This is mock function helps the docs above to be included
}
Object.keys(textPrefixes).forEach(level => {
Object.defineProperty(Log.prototype, level, {
/** @private */
get: function () {
if (!this[`_${level}`]) {
this[`_${level}`] = getLogFn(this, level);
}
return this[`_${level}`];
}
});
});
const defaultCfg = {
prefixStyle: 'text',
logLevel: process.env.npm_config_loglevel || 'info'
};
let _instance = new Log(defaultCfg);
module.exports = wrap();