'use strict'; const Logger = require('bunyan'); const Error = require('http-errors'); const Stream = require('stream'); const chalk = require('chalk'); const Utils = require('./utils'); const pkgJSON = require('../package.json'); /** * Match the level based on buyan severity scale * @param {*} x severity level * @return {String} security level */ function getlvl(x) { switch(true) { case x < 15 : return 'trace'; case x < 25 : return 'debug'; case x < 35 : return 'info'; case x == 35 : return 'http'; case x < 45 : return 'warn'; case x < 55 : return 'error'; default : return 'fatal'; } } /** * Setup the Buyan logger * @param {*} logs list of log configuration */ function setup(logs) { let streams = []; if (logs == null) { logs = [{type: 'stdout', format: 'pretty', level: 'http'}]; } logs.forEach(function(target) { // create a stream for each log configuration const stream = new Stream(); stream.writable = true; if (target.type === 'stdout' || target.type === 'stderr') { // destination stream const dest = target.type === 'stdout' ? process.stdout : process.stderr; if (target.format === 'pretty') { // making fake stream for prettypritting stream.write = function(obj) { dest.write(print(obj.level, obj.msg, obj, dest.isTTY) + '\n'); }; } else if (target.format === 'pretty-timestamped') { // making fake stream for prettypritting stream.write = function(obj) { dest.write(obj.time.toISOString() + print(obj.level, obj.msg, obj, dest.isTTY) + '\n'); }; } else { stream.write = function(obj) { dest.write(JSON.stringify(obj, Logger.safeCycles()) + '\n'); }; } } else if (target.type === 'file') { const dest = require('fs').createWriteStream(target.path, {flags: 'a', encoding: 'utf8'}); dest.on('error', function(err) { Logger.emit('error', err); }); stream.write = function(obj) { if (target.format === 'pretty') { dest.write(print(obj.level, obj.msg, obj, false) + '\n'); } else { dest.write(JSON.stringify(obj, Logger.safeCycles()) + '\n'); } }; } else { throw Error('wrong target type for a log'); } if (target.level === 'http') target.level = 35; streams.push({ type: 'raw', level: target.level || 35, stream: stream, }); }); // buyan default configuration const logger = new Logger({ name: pkgJSON.name, streams: streams, serializers: { err: Logger.stdSerializers.err, req: Logger.stdSerializers.req, res: Logger.stdSerializers.res, }, }); module.exports.logger = logger; } // adopted from socket.io // this part was converted to coffee-script and back again over the years, // so it might look weird // level to color const levels = { fatal: chalk.red, error: chalk.red, warn: chalk.yellow, http: chalk.magenta, info: chalk.cyan, debug: chalk.black, trace: chalk.white, }; let max = 0; for (let l in levels) { if (Object.prototype.hasOwnProperty.call(levels, l)) { max = Math.max(max, l.length); } } /** * Apply whitespaces based on the length * @param {*} str the log message * @return {String} */ function pad(str) { if (str.length < max) { return str + ' '.repeat(max - str.length); } return str; } /** * Apply colors to a string based on level parameters. * @param {*} type * @param {*} msg * @param {*} obj * @param {*} colors * @return {String} */ function print(type, msg, obj, colors) { if (typeof type === 'number') { type = getlvl(type); } let finalmsg = msg.replace(/@{(!?[$A-Za-z_][$0-9A-Za-z\._]*)}/g, function(_, name) { let str = obj; let is_error; if (name[0] === '!') { name = name.substr(1); is_error = true; } let _ref = name.split('.'); for (let _i = 0; _i < _ref.length; _i++) { let id = _ref[_i]; if (Utils.is_object(str) || Array.isArray(str)) { str = str[id]; } else { str = undefined; } } if (typeof(str) === 'string') { if (!colors || str.includes('\n')) { return str; } else if (is_error) { return chalk.red(str); } else { return chalk.green(str); } } else { return require('util').inspect(str, null, null, colors); } }); const subsystems = [{ in: chalk.green('<--'), out: chalk.yellow('-->'), fs: chalk.black('-=-'), default: chalk.blue('---'), }, { in: '<--', out: '-->', fs: '-=-', default: '---', }]; const sub = subsystems[colors ? 0 : 1][obj.sub] || subsystems[+!colors].default; if (colors) { return ` ${levels[type]((pad(type)))}${chalk.white(`${sub} ${finalmsg}`)}`; } else { return ` ${(pad(type))}${sub} ${finalmsg}`; } } module.exports.setup = setup;