UNPKG

semlog

Version:

A semantic logger module, that colors and formats automatically depending on the content

457 lines (405 loc) 11.8 kB
/** * semlog * A semantic logger that colors and formats messages automatically according to the content * * @author Simon Heimler */ 'use strict'; ////////////////////////////////////////// // Requirements // ////////////////////////////////////////// const chalk = require('chalk'); const prettyjson = require('prettyjson'); ////////////////////////////////////////// // Variables // ////////////////////////////////////////// if (!global.githubFannonSemlog) { global.githubFannonSemlog = { history: [], config: { silent: false, colorize: true, printYaml: false, logDateTime: true, printTime: true, printDateTime: false, printDebug: true, printVerbose: true, historySize: 2048, // 0 for none }, statistics: { debug: 0, warning: 0, error: 0, total: 0, }, }; } /** Publicly export the chalk color library */ exports.chalk = chalk; ////////////////////////////////////////// // LOGGING FUNCTIONS // ////////////////////////////////////////// /** * Custom Logging function * * Writes Logs to console, stringifies objects first * * @param {string|object} msg Message String or Object * @param {boolean} [silent] Dot not print message to the console, but stores it to the log history. */ exports.log = function (obj, silent) { if (obj && obj instanceof Error) { exports.error(obj, silent); } else if (obj && typeof obj === 'object') { exports.debug(obj, silent); } else { exports.message(obj, silent); } }; exports.message = function (msg, silent) { global.githubFannonSemlog.statistics.total += 1; if (msg && msg.indexOf && (msg.indexOf('[V]') >= 0 || msg.indexOf('[D]') >= 0)) { global.githubFannonSemlog.statistics.debug += 1; } else if (msg && msg.indexOf && msg.indexOf('[W]') >= 0) { global.githubFannonSemlog.statistics.warning += 1; } else if (msg && msg.indexOf && msg.indexOf('[E]') >= 0) { global.githubFannonSemlog.statistics.error += 1; } const config = global.githubFannonSemlog.config; silent = silent || config.silent; if (typeof msg !== 'string') { try { msg = '' + JSON.stringify(msg); } catch { msg = '[E] [SEMLOG] Could not stringify given parameter'; } } exports.addToHistory(msg); if (!silent) { if (config.colorize) { msg = exports.colorize(msg); } if ((config.printTime || config.printDateTime) && msg.trim().length > 0) { if (config.printDateTime) { msg = chalk.gray('[' + exports.humanDate() + '] ') + msg; } else { msg = chalk.gray('[' + exports.humanTime() + '] ') + msg; } } const suppress = (!config.printVerbose && msg.indexOf('[V]') >= 0) || (!config.printDebug && msg.indexOf('[D]') >= 0); if (!suppress) { console.log(msg); } } }; /** * Prints out debugging information for the current model object * * @param {object} obj Object */ exports.debug = function (obj, silent) { global.githubFannonSemlog.statistics.total += 1; const config = global.githubFannonSemlog.config; silent = silent || config.silent; exports.addToHistory(obj); if (!silent) { if (global.githubFannonSemlog.config.printYaml) { // Print YAML const options = { keysColor: 'white', dashColor: 'white', stringColor: 'yellow', numberColor: 'blue', }; if (!global.githubFannonSemlog.config.colorize) { options.noColor = true; } console.log(chalk.gray('---\n') + prettyjson.render(obj, options)); } else { // Print indented JSON const msg = JSON.stringify(obj, false, 4); console.log(chalk.gray(msg)); } } }; /** * Prints errors * * @param {object} obj Object */ exports.error = function (obj, silent) { global.githubFannonSemlog.statistics.total += 1; global.githubFannonSemlog.statistics.error += 1; const config = global.githubFannonSemlog.config; silent = silent || config.silent; exports.addToHistory(obj); if (!silent) { console.error(chalk.red('[E] ' + obj.message)); console.log(chalk.gray(JSON.stringify(obj, null, 4))); if (obj.stack) { console.log(chalk.gray(obj.stack)); } } }; exports.addToHistory = function (obj) { const config = global.githubFannonSemlog.config; let msg; try { msg = JSON.stringify(obj, null, 4); // Check that the message history size doesn't get to big if (config.historySize && global.githubFannonSemlog.history.length >= config.historySize) { global.githubFannonSemlog.history.shift(); } if (config.logDateTime) { msg = '[' + exports.humanDate() + '] ' + msg; } } catch { msg = '[W] Internal semlog error: Could not push circular/invalid object into the history'; } global.githubFannonSemlog.history.push(msg); }; /** * Colors the messages by searching for specific indicator strings * TODO: Allow to add to the colorMap * * @param {string} msg * @returns {string} */ exports.colorize = function (msg) { const colorMap = { '[E]': 'red', // ERROR '[W]': 'yellow', // WARNING '[?]': 'yellow', // MISSING '[S]': 'green', // SUCCESS '[i]': 'blue', // INFO '[+]': 'green', // ADDED '[-]': 'red', // REMOVED '[C]': 'cyan', // CHANGED '[U]': 'grey', // UNCHANGED '[=]': 'grey', // EQUAL '[/]': 'grey', // SKIPPED '[V]': 'magenta', // VERBOSE '[D]': 'magenta', // DEBUG '[T]': 'magenta', // TO-DO '[TODO]': 'magenta', // TO-DO }; for (const [searchString, color] of Object.entries(colorMap)) { if (msg && msg.indexOf && msg.indexOf(searchString) > -1) { return chalk[color](msg); } } return msg; }; ////////////////////////////////////////// // LOGGER FUNCTIONS // ////////////////////////////////////////// /** * Gets the current config */ exports.getConfig = function () { return global.githubFannonSemlog.config; }; /** * Gets the current config */ exports.getStatistics = function () { return global.githubFannonSemlog.statistics; }; /** * Updates the config. * Only those parameters that have been given will be updated * * @param {object} config */ exports.updateConfig = function (config) { Object.assign(global.githubFannonSemlog.config, config); return global.githubFannonSemlog.config; }; /** * Clears (empties) the log object */ exports.clearLogHistory = function () { global.githubFannonSemlog.history = []; global.githubFannonSemlog.statistics = { debug: 0, warning: 0, error: 0, total: 0, }; }; /** * Returns the global.moboLogObject * * @returns {Array} */ exports.getLogHistory = function () { return global.githubFannonSemlog.history; }; ////////////////////////////////////////// // HELPER UTILITIES // ////////////////////////////////////////// /** * Pad a number with n digits * * @param {number} number number to pad * @param {number} digits number of total digits * @returns {string} */ exports.pad = function (number, digits) { return new Array(Math.max(digits - String(number).length + 1, 0)).join(0) + number; }; /** * Adds dots as thousand separators to numbers * * http://stackoverflow.com/a/2901298 * * @param number * @returns {string} */ exports.prettyNumber = function (number) { return number.toString().replace(/\B(?=(\d{3})+(?!\d))/g, '.'); }; /** * Replace all (/.../g) leading slash (^\/) or (|) trailing slash (\/$) with an empty string. * * @see http://stackoverflow.com/a/3840645 * * @param {String} url URL / Path to cleanup * @returns {String} */ exports.cleanUrl = function (url) { url = url.trim(); return url.replace(/^\/|\/$/g, ''); }; /** * Strips trailing slashes from URL * * @see http://stackoverflow.com/a/6680858/776425 * * @param {String} url URL to cleanup * @returns {String} */ exports.stripTrailingSlash = function (url) { if (url.substr(-1) === '/') { url = url.substr(0, url.length - 1); } return url; }; /** * Returns the byte length of an utf8 string or an object (when parsed to JSON) * * @see http://stackoverflow.com/a/23329386 */ exports.byteSize = function (obj) { let str; if (typeof obj === 'object') { str = JSON.stringify(obj); } else { str = obj.toString(); } let s = str.length; for (let i = str.length - 1; i >= 0; i--) { const code = str.charCodeAt(i); if (code > 0x7f && code <= 0x7ff) { s++; } else if (code > 0x7ff && code <= 0xffff) { s += 2; } if (code >= 0xdc00 && code <= 0xdfff) { i--; } //trail surrogate } return s; }; /** * * @param bytes * @param [si] * @returns {string} * * @see http://stackoverflow.com/a/14919494 */ exports.prettyBytes = function (bytes, si) { const thresh = si ? 1000 : 1024; if (bytes < thresh) { return bytes + ' B'; } const units = si ? ['kB', 'MB', 'GB', 'TB', 'PB', 'EB', 'ZB', 'YB'] : ['KiB', 'MiB', 'GiB', 'TiB', 'PiB', 'EiB', 'ZiB', 'YiB']; let u = -1; do { bytes /= thresh; ++u; } while (bytes >= thresh); return bytes.toFixed(1) + ' ' + units[u]; }; /** * Returns an array with date / time information * Starts with year at index 0 up to index 6 for milliseconds * * @param {Date=} date Optional date object. If falsy, will take current time. * @returns {Array} */ exports.getDateArray = function (date) { date = date || new Date(); return [ date.getFullYear(), exports.pad(date.getMonth() + 1, 2), exports.pad(date.getDate(), 2), exports.pad(date.getHours(), 2), exports.pad(date.getMinutes(), 2), exports.pad(date.getSeconds(), 2), exports.pad(date.getMilliseconds(), 3), ]; }; /** * Returns nicely formatted date-time * @example 2015-02-10 16:01:12 * * @param {object} [date] * @returns {string} */ exports.humanDate = function (date) { date = date || new Date(); const d = exports.getDateArray(date); return d[0] + '-' + d[1] + '-' + d[2] + ' ' + d[3] + ':' + d[4] + ':' + d[5]; }; /** * Returns a formatted date-time, optimized for machines * @example 2015-02-10_16-00-08 * * @param {object} [date] * @returns {string} */ exports.roboDate = function (date) { date = date || new Date(); const d = exports.getDateArray(date); return d[0] + '-' + d[1] + '-' + d[2] + '_' + d[3] + '-' + d[4] + '-' + d[5]; }; /** * Returns nicely formatted date-time * @example 16:01:12 * * @param {object} [date] * @returns {string} */ exports.humanTime = function (date) { date = date || new Date(); const d = exports.getDateArray(date); return d[3] + ':' + d[4] + ':' + d[5]; }; /** * Returns a formatted date-time, optimized for machines * @example 2015-02-10_16-00-08 * * @param {object} [date] * @returns {string} */ exports.roboTime = function (date) { date = date || new Date(); const d = exports.getDateArray(date); return d[3] + '-' + d[4] + '-' + d[5]; };