UNPKG

@ladjs/logger

Version:
188 lines (163 loc) 5.23 kB
const { format } = require('util'); const autoBind = require('auto-bind'); const debug = require('debug'); const _ = require('lodash'); const chalk = require('chalk'); // `debug` (the least serious) // `info` // `warning` (also aliased as `warn`) // `error` (also aliased as `err`) // `fatal` (the most serious) const levels = { debug: 'cyan', info: 'green', warning: 'yellow', error: 'red', fatal: 'bgRed' }; const allowedColors = [ 'bgBlack', 'bgRed', 'bgGreen', 'bgYellow', 'bgBlue', 'bgMagenta', 'bgCyan', 'bgWhite', 'bgBlackBright', 'bgRedBright', 'bgGreenBright', 'bgYellowBright', 'bgBlueBright', 'bgMagentaBright', 'bgCyanBright', 'bgWhiteBright' ]; // these are known as "placeholder tokens", see this link for more info: // <https://nodejs.org/api/util.html#util_util_format_format_args> // // since they aren't exposed (or don't seem to be) by node (at least not yet) // we just define an array that contains them for now // <https://github.com/nodejs/node/issues/17601> const tokens = ['%s', '%d', '%i', '%f', '%j', '%o', '%O', '%%']; class Logger { constructor(config = {}) { autoBind(this); // debugName gets passed to logger.debug this.config = Object.assign( { timestamp: true, appName: '@ladjs/logger', showStack: false, silent: false, processName: null, processColor: 'bgCyan' }, config ); if ( this.config.processColor && !allowedColors.includes(this.config.processColor) ) throw new Error( `Invalid color ${ this.config.processColor }, must be one of ${allowedColors.join(', ')}` ); // bind helper functions for each log level const log = this.log; _.keys(levels).forEach(level => { this[level] = function() { log(...[level].concat([].slice.call(arguments))); }; }); // aliases this.err = this.error; this.warn = this.warning; } // TODO: rewrite this with better log parsing by cabin contextError(err) { // , ctx) { // TODO: add user object and request to meta here using `ctx` arg this.error(err); } log(level, message, meta = {}) { const { config } = this; let modifier = 0; if (level === 'warn') level = 'warning'; if (level === 'err') level = 'error'; if (!_.isString(level) || !_.includes(_.keys(levels), level)) { meta = message; message = level; level = 'info'; modifier = -1; } // if there are four or more args // then infer to use util.format on everything if (arguments.length >= 4 + modifier) { message = format(...[].slice.call(arguments).slice(1 + modifier)); meta = {}; } else if ( arguments.length === 3 + modifier && _.isString(message) && tokens.some(t => message.includes(t)) ) { // otherwise if there are three args and if the `message` contains // a placeholder token (e.g. '%s' or '%d' - see above `tokens` variable) // then we can infer that the `meta` arg passed is used for formatting message = format(message, meta); meta = {}; } else if ( !_.isPlainObject(meta) && !_.isUndefined(meta) && !_.isNull(meta) ) { // if the `meta` variable passed was not an Object then convert it message = format(message, meta); meta = {}; } else if (!_.isString(message)) { // if the message is not a string then we should run `util.format` on it // assuming we're formatting it like it was another argument // (as opposed to using something like fast-json-stringify) message = format(message); } if (!_.isPlainObject(meta)) meta = {}; if (_.isError(message)) { if (!_.isObject(meta.err)) meta.err = { stack: message.stack, message: message.message }; message = message.message; } // set default level on meta meta.level = level; // TODO: send to cabin here // if (config.env === 'production') cabin.log(message, meta); // Suppress logs if it was silent if (config.silent) return; if (level === 'debug') { let name = config.processName; if (!name && require.main && require.main.filename) name = require.main.filename; if (!name) name = '@ladjs/logger'; debug(name)(message, _.omit(meta, 'level')); } else { let prepend = ''; if (config.processName) { const color = chalk[config.processColor].bold; prepend = `${color(`[${config.processName}]`)} `; } const output = `${prepend}${chalk[levels[level]](level)}: ${message}`; console.log( `${config.timestamp ? new Date().toISOString() : ''} ${output}` ); // if there was meta information then output it if (!_.isEmpty(_.omit(meta, ['level', 'err']))) console.log(_.omit(meta, ['level', 'err'])); // if we're not showing the stack trace then return early if (!config.showStack) return; // output the stack trace to the console for debugging if (meta.err && meta.err.stack) console.log(meta.err.stack); } } } Logger.levels = _.keys(levels); module.exports = Logger;