layouts.js 8.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310
  1. "use strict";
  2. var dateFormat = require('./date_format')
  3. , os = require('os')
  4. , eol = os.EOL || '\n'
  5. , util = require('util')
  6. , replacementRegExp = /%[sdj]/g
  7. , layoutMakers = {
  8. "messagePassThrough": function() { return messagePassThroughLayout; },
  9. "basic": function() { return basicLayout; },
  10. "colored": function() { return colouredLayout; },
  11. "coloured": function() { return colouredLayout; },
  12. "pattern": function (config) {
  13. return patternLayout(config && config.pattern, config && config.tokens);
  14. }
  15. }
  16. , colours = {
  17. ALL: "grey",
  18. TRACE: "blue",
  19. DEBUG: "cyan",
  20. INFO: "green",
  21. WARN: "yellow",
  22. ERROR: "red",
  23. FATAL: "magenta",
  24. OFF: "grey"
  25. };
  26. function wrapErrorsWithInspect(items) {
  27. return items.map(function(item) {
  28. if ((item instanceof Error) && item.stack) {
  29. return { inspect: function() { return util.format(item) + '\n' + item.stack; } };
  30. } else {
  31. return item;
  32. }
  33. });
  34. }
  35. function formatLogData(logData) {
  36. var data = Array.isArray(logData) ? logData : Array.prototype.slice.call(arguments);
  37. return util.format.apply(util, wrapErrorsWithInspect(data));
  38. }
  39. var styles = {
  40. //styles
  41. 'bold' : [1, 22],
  42. 'italic' : [3, 23],
  43. 'underline' : [4, 24],
  44. 'inverse' : [7, 27],
  45. //grayscale
  46. 'white' : [37, 39],
  47. 'grey' : [90, 39],
  48. 'black' : [90, 39],
  49. //colors
  50. 'blue' : [34, 39],
  51. 'cyan' : [36, 39],
  52. 'green' : [32, 39],
  53. 'magenta' : [35, 39],
  54. 'red' : [31, 39],
  55. 'yellow' : [33, 39]
  56. };
  57. function colorizeStart(style) {
  58. return style ? '\x1B[' + styles[style][0] + 'm' : '';
  59. }
  60. function colorizeEnd(style) {
  61. return style ? '\x1B[' + styles[style][1] + 'm' : '';
  62. }
  63. /**
  64. * Taken from masylum's fork (https://github.com/masylum/log4js-node)
  65. */
  66. function colorize (str, style) {
  67. return colorizeStart(style) + str + colorizeEnd(style);
  68. }
  69. function timestampLevelAndCategory(loggingEvent, colour) {
  70. var output = colorize(
  71. formatLogData(
  72. '[%s] [%s] %s - '
  73. , dateFormat.asString(loggingEvent.startTime)
  74. , loggingEvent.level
  75. , loggingEvent.categoryName
  76. )
  77. , colour
  78. );
  79. return output;
  80. }
  81. /**
  82. * BasicLayout is a simple layout for storing the logs. The logs are stored
  83. * in following format:
  84. * <pre>
  85. * [startTime] [logLevel] categoryName - message\n
  86. * </pre>
  87. *
  88. * @author Stephan Strittmatter
  89. */
  90. function basicLayout (loggingEvent) {
  91. return timestampLevelAndCategory(loggingEvent) + formatLogData(loggingEvent.data);
  92. }
  93. /**
  94. * colouredLayout - taken from masylum's fork.
  95. * same as basicLayout, but with colours.
  96. */
  97. function colouredLayout (loggingEvent) {
  98. return timestampLevelAndCategory(
  99. loggingEvent,
  100. colours[loggingEvent.level.toString()]
  101. ) + formatLogData(loggingEvent.data);
  102. }
  103. function messagePassThroughLayout (loggingEvent) {
  104. return formatLogData(loggingEvent.data);
  105. }
  106. /**
  107. * PatternLayout
  108. * Format for specifiers is %[padding].[truncation][field]{[format]}
  109. * e.g. %5.10p - left pad the log level by 5 characters, up to a max of 10
  110. * Fields can be any of:
  111. * - %r time in toLocaleTimeString format
  112. * - %p log level
  113. * - %c log category
  114. * - %m log data
  115. * - %d date in various formats
  116. * - %% %
  117. * - %n newline
  118. * - %x{<tokenname>} add dynamic tokens to your log. Tokens are specified in the tokens parameter
  119. * You can use %[ and %] to define a colored block.
  120. *
  121. * Tokens are specified as simple key:value objects.
  122. * The key represents the token name whereas the value can be a string or function
  123. * which is called to extract the value to put in the log message. If token is not
  124. * found, it doesn't replace the field.
  125. *
  126. * A sample token would be: { "pid" : function() { return process.pid; } }
  127. *
  128. * Takes a pattern string, array of tokens and returns a layout function.
  129. * @param {String} Log format pattern String
  130. * @param {object} map object of different tokens
  131. * @return {Function}
  132. * @author Stephan Strittmatter
  133. * @author Jan Schmidle
  134. */
  135. function patternLayout (pattern, tokens) {
  136. var TTCC_CONVERSION_PATTERN = "%r %p %c - %m%n";
  137. var regex = /%(-?[0-9]+)?(\.?[0-9]+)?([\[\]cdmnprx%])(\{([^\}]+)\})?|([^%]+)/;
  138. pattern = pattern || TTCC_CONVERSION_PATTERN;
  139. function categoryName(loggingEvent, specifier) {
  140. var loggerName = loggingEvent.categoryName;
  141. if (specifier) {
  142. var precision = parseInt(specifier, 10);
  143. var loggerNameBits = loggerName.split(".");
  144. if (precision < loggerNameBits.length) {
  145. loggerName = loggerNameBits.slice(loggerNameBits.length - precision).join(".");
  146. }
  147. }
  148. return loggerName;
  149. }
  150. function formatAsDate(loggingEvent, specifier) {
  151. var format = dateFormat.ISO8601_FORMAT;
  152. if (specifier) {
  153. format = specifier;
  154. // Pick up special cases
  155. if (format == "ISO8601") {
  156. format = dateFormat.ISO8601_FORMAT;
  157. } else if (format == "ABSOLUTE") {
  158. format = dateFormat.ABSOLUTETIME_FORMAT;
  159. } else if (format == "DATE") {
  160. format = dateFormat.DATETIME_FORMAT;
  161. }
  162. }
  163. // Format the date
  164. return dateFormat.asString(format, loggingEvent.startTime);
  165. }
  166. function formatMessage(loggingEvent) {
  167. return formatLogData(loggingEvent.data);
  168. }
  169. function endOfLine() {
  170. return eol;
  171. }
  172. function logLevel(loggingEvent) {
  173. return loggingEvent.level.toString();
  174. }
  175. function startTime(loggingEvent) {
  176. return "" + loggingEvent.startTime.toLocaleTimeString();
  177. }
  178. function startColour(loggingEvent) {
  179. return colorizeStart(colours[loggingEvent.level.toString()]);
  180. }
  181. function endColour(loggingEvent) {
  182. return colorizeEnd(colours[loggingEvent.level.toString()]);
  183. }
  184. function percent() {
  185. return '%';
  186. }
  187. function userDefined(loggingEvent, specifier) {
  188. if (typeof(tokens[specifier]) !== 'undefined') {
  189. if (typeof(tokens[specifier]) === 'function') {
  190. return tokens[specifier](loggingEvent);
  191. } else {
  192. return tokens[specifier];
  193. }
  194. }
  195. return null;
  196. }
  197. var replacers = {
  198. 'c': categoryName,
  199. 'd': formatAsDate,
  200. 'm': formatMessage,
  201. 'n': endOfLine,
  202. 'p': logLevel,
  203. 'r': startTime,
  204. '[': startColour,
  205. ']': endColour,
  206. '%': percent,
  207. 'x': userDefined
  208. };
  209. function replaceToken(conversionCharacter, loggingEvent, specifier) {
  210. return replacers[conversionCharacter](loggingEvent, specifier);
  211. }
  212. function truncate(truncation, toTruncate) {
  213. var len;
  214. if (truncation) {
  215. len = parseInt(truncation.substr(1), 10);
  216. return toTruncate.substring(0, len);
  217. }
  218. return toTruncate;
  219. }
  220. function pad(padding, toPad) {
  221. var len;
  222. if (padding) {
  223. if (padding.charAt(0) == "-") {
  224. len = parseInt(padding.substr(1), 10);
  225. // Right pad with spaces
  226. while (toPad.length < len) {
  227. toPad += " ";
  228. }
  229. } else {
  230. len = parseInt(padding, 10);
  231. // Left pad with spaces
  232. while (toPad.length < len) {
  233. toPad = " " + toPad;
  234. }
  235. }
  236. }
  237. return toPad;
  238. }
  239. return function(loggingEvent) {
  240. var formattedString = "";
  241. var result;
  242. var searchString = pattern;
  243. while ((result = regex.exec(searchString))) {
  244. var matchedString = result[0];
  245. var padding = result[1];
  246. var truncation = result[2];
  247. var conversionCharacter = result[3];
  248. var specifier = result[5];
  249. var text = result[6];
  250. // Check if the pattern matched was just normal text
  251. if (text) {
  252. formattedString += "" + text;
  253. } else {
  254. // Create a raw replacement string based on the conversion
  255. // character and specifier
  256. var replacement =
  257. replaceToken(conversionCharacter, loggingEvent, specifier) ||
  258. matchedString;
  259. // Format the replacement according to any padding or
  260. // truncation specified
  261. replacement = truncate(truncation, replacement);
  262. replacement = pad(padding, replacement);
  263. formattedString += replacement;
  264. }
  265. searchString = searchString.substr(result.index + result[0].length);
  266. }
  267. return formattedString;
  268. };
  269. }
  270. module.exports = {
  271. basicLayout: basicLayout,
  272. messagePassThroughLayout: messagePassThroughLayout,
  273. patternLayout: patternLayout,
  274. colouredLayout: colouredLayout,
  275. coloredLayout: colouredLayout,
  276. layout: function(name, config) {
  277. return layoutMakers[name] && layoutMakers[name](config);
  278. }
  279. };