utils.js 5.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203
  1. 'use strict';
  2. const os = require('os');
  3. const util = require('util');
  4. const chalk = require('chalk');
  5. const utility = require('utility');
  6. const iconv = require('iconv-lite');
  7. const levels = require('./level');
  8. const circularJSON = require('circular-json-for-egg');
  9. const { FrameworkBaseError, FrameworkErrorFormater } = require('egg-errors');
  10. const hostname = os.hostname();
  11. const duartionRegexp = /([0-9]+ms)/g;
  12. // eslint-disable-next-line no-useless-escape
  13. const categoryRegexp = /(\[[\w\-_.:]+\])/g;
  14. const httpMethodRegexp = /(GET|POST|PUT|PATH|HEAD|DELETE) /g;
  15. /**
  16. * @class LoggerUtils
  17. */
  18. module.exports = {
  19. normalizeLevel(level) {
  20. if (typeof level === 'number') {
  21. return level;
  22. }
  23. // 'WARN' => level.warn
  24. if (typeof level === 'string' && level) {
  25. return levels[level.toUpperCase()];
  26. }
  27. },
  28. // default format
  29. defaultFormatter(meta) {
  30. return meta.date + ' ' + meta.level + ' ' + meta.pid + ' ' + meta.message;
  31. },
  32. // output to Terminal format
  33. consoleFormatter(meta) {
  34. let msg = meta.date + ' ' + meta.level + ' ' + meta.pid + ' ' + meta.message;
  35. if (!chalk.supportsColor) {
  36. return msg;
  37. }
  38. if (meta.level === 'ERROR') {
  39. return chalk.red(msg);
  40. } else if (meta.level === 'WARN') {
  41. return chalk.yellow(msg);
  42. }
  43. msg = msg.replace(duartionRegexp, chalk.green('$1'));
  44. msg = msg.replace(categoryRegexp, chalk.blue('$1'));
  45. msg = msg.replace(httpMethodRegexp, chalk.cyan('$1 '));
  46. return msg;
  47. },
  48. /**
  49. * Get final formated log string buffer
  50. *
  51. * Invoke link: {@Link Logger#log} -> {@link Transport#log} -> LoggerUtils.format
  52. * @function LoggerUtils#format
  53. * @param {String} level - log level
  54. * @param {Array} args - format arguments
  55. * @param {Object} meta - loging behaviour meta infomation
  56. * - {String} level
  57. * - {Boolean} raw
  58. * - {Function} formatter
  59. * - {Error} error
  60. * - {String} message
  61. * - {Number} pid
  62. * - {String} hostname
  63. * - {String} date
  64. * @param {Object} options - {@link Transport}'s options
  65. * - {String} encoding
  66. * - {Boolean} json
  67. * - {Function} formatter
  68. * @return {String|Buffer} formatted log string buffer
  69. */
  70. format(level, args, meta, options) {
  71. meta = meta || {};
  72. let message;
  73. let output;
  74. let formatter = meta.formatter || options.formatter;
  75. if (meta.ctx && options.contextFormatter) formatter = options.contextFormatter;
  76. if (args[0] instanceof Error) {
  77. message = formatError(args[0]);
  78. } else {
  79. message = util.format.apply(util, args);
  80. }
  81. if (meta.raw === true) {
  82. output = message;
  83. } else if (options.json === true || formatter) {
  84. meta.level = level;
  85. meta.date = utility.logDate(',');
  86. meta.pid = process.pid;
  87. meta.hostname = hostname;
  88. meta.message = message;
  89. if (options.json === true) {
  90. const outputMeta = { ...meta };
  91. outputMeta.ctx = undefined;
  92. output = JSON.stringify(outputMeta);
  93. } else {
  94. output = formatter(meta);
  95. }
  96. } else {
  97. output = message;
  98. }
  99. if (!output) return Buffer.from('');
  100. output += options.eol;
  101. // convert string to buffer when encoding is not utf8
  102. return options.encoding === 'utf8' ? output : iconv.encode(output, options.encoding);
  103. },
  104. // Like `Object.assign`, but don't copy `undefined` value
  105. assign(target) {
  106. if (!target) {
  107. return {};
  108. }
  109. const sources = Array.prototype.slice.call(arguments, 1);
  110. for (let i = 0; i < sources.length; i++) {
  111. const source = sources[i];
  112. if (!source) continue;
  113. const keys = Object.keys(source);
  114. for (let j = 0; j < keys.length; j++) {
  115. const key = keys[j];
  116. if (source[key] !== undefined && source[key] !== null) {
  117. target[key] = source[key];
  118. }
  119. }
  120. }
  121. return target;
  122. },
  123. formatError,
  124. };
  125. function formatError(err) {
  126. if (FrameworkBaseError.isFrameworkError(err)) {
  127. return FrameworkErrorFormater.format(err);
  128. }
  129. if (err.name === 'Error' && typeof err.code === 'string') {
  130. err.name = err.code + err.name;
  131. }
  132. if (err.host) {
  133. err.message += ` (${err.host})`;
  134. }
  135. // name and stack could not be change on node 0.11+
  136. const errStack = err.stack || 'no_stack';
  137. const errProperties = Object.keys(err).map(key => inspect(key, err[key])).join('\n');
  138. return util.format('nodejs.%s: %s\n%s\n%s\npid: %s\nhostname: %s\n',
  139. err.name,
  140. err.message,
  141. errStack.substring(errStack.indexOf('\n') + 1),
  142. errProperties,
  143. process.pid,
  144. hostname
  145. );
  146. }
  147. function inspect(key, value) {
  148. return `${key}: ${formatObject(value)}`;
  149. }
  150. function formatString(str) {
  151. if (str.length > 10000) {
  152. return `${str.substr(0, 10000)}...(${str.length})`;
  153. }
  154. return str;
  155. }
  156. function formatBuffer(buf) {
  157. const tail = buf.data.length > 50 ? ` ...(${buf.data.length}) ` : '';
  158. const bufStr = buf.data.slice(0, 50).map(i => {
  159. i = i.toString(16);
  160. if (i.length === 1) i = `0${i}`;
  161. return i;
  162. }).join(' ');
  163. return `<Buffer ${bufStr}${tail}>`;
  164. }
  165. function formatObject(obj) {
  166. try {
  167. return circularJSON.stringify(obj, (key, v) => {
  168. if (typeof v === 'string') return formatString(v);
  169. if (v && v.type === 'Buffer' && Array.isArray(v.data)) {
  170. return formatBuffer(v);
  171. }
  172. if (v instanceof RegExp) return util.inspect(v);
  173. return v;
  174. });
  175. } catch (_) {
  176. /* istanbul ignore next */
  177. return String(obj);
  178. }
  179. }