'use strict'; const os = require('os'); const util = require('util'); const chalk = require('chalk'); const utility = require('utility'); const iconv = require('iconv-lite'); const levels = require('./level'); const circularJSON = require('circular-json-for-egg'); const { FrameworkBaseError, FrameworkErrorFormater } = require('egg-errors'); const hostname = os.hostname(); const duartionRegexp = /([0-9]+ms)/g; // eslint-disable-next-line no-useless-escape const categoryRegexp = /(\[[\w\-_.:]+\])/g; const httpMethodRegexp = /(GET|POST|PUT|PATH|HEAD|DELETE) /g; /** * @class LoggerUtils */ module.exports = { normalizeLevel(level) { if (typeof level === 'number') { return level; } // 'WARN' => level.warn if (typeof level === 'string' && level) { return levels[level.toUpperCase()]; } }, // default format defaultFormatter(meta) { return meta.date + ' ' + meta.level + ' ' + meta.pid + ' ' + meta.message; }, // output to Terminal format consoleFormatter(meta) { let msg = meta.date + ' ' + meta.level + ' ' + meta.pid + ' ' + meta.message; if (!chalk.supportsColor) { return msg; } if (meta.level === 'ERROR') { return chalk.red(msg); } else if (meta.level === 'WARN') { return chalk.yellow(msg); } msg = msg.replace(duartionRegexp, chalk.green('$1')); msg = msg.replace(categoryRegexp, chalk.blue('$1')); msg = msg.replace(httpMethodRegexp, chalk.cyan('$1 ')); return msg; }, /** * Get final formated log string buffer * * Invoke link: {@Link Logger#log} -> {@link Transport#log} -> LoggerUtils.format * @function LoggerUtils#format * @param {String} level - log level * @param {Array} args - format arguments * @param {Object} meta - loging behaviour meta infomation * - {String} level * - {Boolean} raw * - {Function} formatter * - {Error} error * - {String} message * - {Number} pid * - {String} hostname * - {String} date * @param {Object} options - {@link Transport}'s options * - {String} encoding * - {Boolean} json * - {Function} formatter * @return {String|Buffer} formatted log string buffer */ format(level, args, meta, options) { meta = meta || {}; let message; let output; let formatter = meta.formatter || options.formatter; if (meta.ctx && options.contextFormatter) formatter = options.contextFormatter; if (args[0] instanceof Error) { message = formatError(args[0]); } else { message = util.format.apply(util, args); } if (meta.raw === true) { output = message; } else if (options.json === true || formatter) { meta.level = level; meta.date = utility.logDate(','); meta.pid = process.pid; meta.hostname = hostname; meta.message = message; if (options.json === true) { const outputMeta = { ...meta }; outputMeta.ctx = undefined; output = JSON.stringify(outputMeta); } else { output = formatter(meta); } } else { output = message; } if (!output) return Buffer.from(''); output += options.eol; // convert string to buffer when encoding is not utf8 return options.encoding === 'utf8' ? output : iconv.encode(output, options.encoding); }, // Like `Object.assign`, but don't copy `undefined` value assign(target) { if (!target) { return {}; } const sources = Array.prototype.slice.call(arguments, 1); for (let i = 0; i < sources.length; i++) { const source = sources[i]; if (!source) continue; const keys = Object.keys(source); for (let j = 0; j < keys.length; j++) { const key = keys[j]; if (source[key] !== undefined && source[key] !== null) { target[key] = source[key]; } } } return target; }, formatError, }; function formatError(err) { if (FrameworkBaseError.isFrameworkError(err)) { return FrameworkErrorFormater.format(err); } if (err.name === 'Error' && typeof err.code === 'string') { err.name = err.code + err.name; } if (err.host) { err.message += ` (${err.host})`; } // name and stack could not be change on node 0.11+ const errStack = err.stack || 'no_stack'; const errProperties = Object.keys(err).map(key => inspect(key, err[key])).join('\n'); return util.format('nodejs.%s: %s\n%s\n%s\npid: %s\nhostname: %s\n', err.name, err.message, errStack.substring(errStack.indexOf('\n') + 1), errProperties, process.pid, hostname ); } function inspect(key, value) { return `${key}: ${formatObject(value)}`; } function formatString(str) { if (str.length > 10000) { return `${str.substr(0, 10000)}...(${str.length})`; } return str; } function formatBuffer(buf) { const tail = buf.data.length > 50 ? ` ...(${buf.data.length}) ` : ''; const bufStr = buf.data.slice(0, 50).map(i => { i = i.toString(16); if (i.length === 1) i = `0${i}`; return i; }).join(' '); return ``; } function formatObject(obj) { try { return circularJSON.stringify(obj, (key, v) => { if (typeof v === 'string') return formatString(v); if (v && v.type === 'Buffer' && Array.isArray(v.data)) { return formatBuffer(v); } if (v instanceof RegExp) return util.inspect(v); return v; }); } catch (_) { /* istanbul ignore next */ return String(obj); } }