logger.js 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380
  1. /*
  2. The MIT License (MIT)
  3. Copyright (c) 2014-2020 Nikolai Suslov and the Krestianstvo.org project contributors. (https://github.com/NikolaySuslov/livecodingspace/blob/master/LICENSE.md)
  4. Virtual World Framework Apache 2.0 license (https://github.com/NikolaySuslov/livecodingspace/blob/master/licenses/LICENSE_VWF.md)
  5. */
  6. /// @module logger
  7. /// @requires vwf/configuration
  8. //define( [ "vwf/configuration" ], function( configuration ) {
  9. class Logger {
  10. constructor() {
  11. //console.log("Logger constructor");
  12. // exports.configure( undefined, undefined, vwf.configuration["log-level"] || "warn" );
  13. this.TRACE = 1
  14. this.DEBUG = 2
  15. this.INFO = 3
  16. this.WARN = 4
  17. this.ERROR = 5
  18. this.FATAL = 6
  19. this.levels = {
  20. trace: this.TRACE,
  21. debug: this.DEBUG,
  22. info: this.INFO,
  23. warn: this.WARN,
  24. error: this.ERROR,
  25. // fatal: this.FATAL,
  26. }
  27. this.label = undefined
  28. this.context = undefined
  29. this.level = this.WARN
  30. }
  31. for ( label, context, level ) {
  32. return Object.create( this ).configure( label, context, level );
  33. }
  34. configure ( label, context, level ) {
  35. var proto = Object.getPrototypeOf( this ) !== Object.prototype ?
  36. Object.getPrototypeOf( this ) : undefined;
  37. this.label = this.combined_label( proto && proto.label, label );
  38. this.context = context || proto && proto.context;
  39. this.level = { name: "warn", number: this.WARN }; // default
  40. switch( level ) {
  41. case "trace": case "TRACE": this.level = { name: "trace", number: this.TRACE }; break;
  42. case "debug": case "DEBUG": this.level = { name: "debug", number: this.DEBUG }; break;
  43. case "info": case "INFO": this.level = { name: "info", number: this.INFO }; break;
  44. case "warn": case "WARN": this.level = { name: "warn", number: this.WARN }; break;
  45. case "error": case "ERROR": this.level = { name: "error", number: this.ERROR }; break;
  46. // case "fatal": case "FATAL": this.level = { name: "fatal", number: FATAL }; break;
  47. default: proto && delete this.level; break; // inherit from the prototype
  48. }
  49. return this;
  50. }
  51. /// Log a message and open a group if the log threshold is "trace" or below.
  52. traceg ( /* ... */ ) {
  53. this.TRACE >= this.level.number &&
  54. this.log.call( this, arguments, console && console.group, console );
  55. }
  56. /// Log a message and open a group if the log threshold is "debug" or below.
  57. debugg ( /* ... */ ) {
  58. this.DEBUG >= this.level.number &&
  59. this.log.call( this, arguments, console && console.group, console );
  60. }
  61. /// Log a message and open a group if the log threshold is "info" or below.
  62. infog ( /* ... */ ) {
  63. this.INFO >= this.level.number &&
  64. this.log.call( this, arguments, console && console.group, console );
  65. }
  66. /// Close a group if the log threshold is "trace" or below.
  67. traceu () {
  68. this.TRACE >= this.level.number &&
  69. this.log.call( this, arguments, console && console.groupEnd, console );
  70. }
  71. /// Close a group if the log threshold is "debug" or below.
  72. debugu () {
  73. this.DEBUG >= this.level.number &&
  74. this.log.call( this, arguments, console && console.groupEnd, console );
  75. }
  76. /// Close a group if the log threshold is "info" or below.
  77. infou () {
  78. this.INFO >= this.level.number &&
  79. this.log.call( this, arguments, console && console.groupEnd, console );
  80. }
  81. /// Log a message if the log threshold is "trace" or below.
  82. trace ( /* ... */ ) {
  83. this.TRACE >= this.level.number &&
  84. this.log.call( this, arguments, console && console.debug, console ); // not console.trace(), which would log the stack
  85. }
  86. /// Log a message if the log threshold is "debug" or below.
  87. debug ( /* ... */ ) {
  88. this.DEBUG >= this.level.number &&
  89. this.log.call( this, arguments, console && console.debug, console );
  90. }
  91. /// Log a message if the log threshold is "info" or below.
  92. info ( /* ... */ ) {
  93. this.INFO >= this.level.number &&
  94. this.log.call( this, arguments, console && console.info, console );
  95. }
  96. /// Log a message if the log threshold is "warn" or below.
  97. warn ( /* ... */ ) {
  98. this.WARN >= this.level.number &&
  99. this.log.call( this, arguments, console && console.warn, console );
  100. }
  101. /// Log a message if the log threshold is "error" or below.
  102. error ( /* ... */ ) {
  103. this.ERROR >= this.level.number &&
  104. this.log.call( this, arguments, console && console.error, console );
  105. }
  106. /// Log a message.
  107. log ( /* ... */ ) {
  108. this.log.call( this, arguments, console && console.log, console );
  109. }
  110. // Log with an extra one-time label. Equivalent to this.logger.for( label ).log( ... ),
  111. // etc., but without the overhead of creating a new logger.
  112. /// Log a message with an extra one-time label and open a group if the log threshold is
  113. /// "trace" or below.
  114. tracegx ( /* label, ... */ ) {
  115. this.TRACE >= this.level.number &&
  116. this.log.call( this, arguments, console && console.group, console, true );
  117. }
  118. /// Log a message with an extra one-time label and open a group if the log threshold is
  119. /// "debug" or below.
  120. debuggx ( /* label, ... */ ) {
  121. this.DEBUG >= this.level.number &&
  122. this.log.call( this, arguments, console && console.group, console, true );
  123. }
  124. /// Log a message with an extra one-time label and open a group if the log threshold is
  125. /// "info" or below.
  126. infogx ( /* label, ... */ ) {
  127. this.INFO >= this.level.number &&
  128. this.log.call( this, arguments, console && console.group, console, true );
  129. }
  130. /// Log a message with an extra one-time label if the log threshold is "trace" or below.
  131. tracex ( /* ... */ ) {
  132. this.TRACE >= this.level.number &&
  133. this.log.call( this, arguments, console && console.debug, console, true ); // not console.trace(), which would log the stack
  134. }
  135. /// Log a message with an extra one-time label if the log threshold is "debug" or below.
  136. debugx ( /* label, ... */ ) {
  137. this.DEBUG >= this.level.number &&
  138. this.log.call( this, arguments, console && console.debug, console, true );
  139. }
  140. /// Log a message with an extra one-time label if the log threshold is "info" or below.
  141. infox ( /* label, ... */ ) {
  142. this.INFO >= this.level.number &&
  143. this.log.call( this, arguments, console && console.info, console, true );
  144. }
  145. /// Log a message with an extra one-time label if the log threshold is "warn" or below.
  146. warnx ( /* label, ... */ ) {
  147. this.WARN >= this.level.number &&
  148. this.log.call( this, arguments, console && console.warn, console, true );
  149. }
  150. /// Log a message with an extra one-time label if the log threshold is "error" or below.
  151. errorx ( /* label, ... */ ) {
  152. this.ERROR >= this.level.number &&
  153. this.log.call( this, arguments, console && console.warn, console, true );
  154. }
  155. /// Log a message with an extra one-time label.
  156. logx ( /* label, ... */ ) {
  157. this.log.call( this, arguments, console && console.log, console, true );
  158. }
  159. /////
  160. /// Log a message to the console. Normalize the arguments list and invoke the appender function.
  161. ///
  162. /// @param {Array} args
  163. /// An Array-like list of arguments passed to a log function. normalize describes the formats
  164. /// supported.
  165. /// @param {Function} appender
  166. /// A Firebug-like log function that logs its arguments, such as window.console.log.
  167. /// @param {Object} context
  168. /// The *this* object for the appender, such as window.console.
  169. /// @param {Boolean} [extra]
  170. /// If true, interpret args[0] as a one-time label that extends the logger's output prefix.
  171. log ( args, appender, context, extra ) { // invoke with *this* as the logger module
  172. // Normalize the arguments and log the message. Don't log a message if normalize() returned
  173. // undefined (because a generator function didn't return a result).
  174. if ( args = /* assignment! */ this.normalize.call( this, args, extra ) ) {
  175. appender && appender.apply( context, args );
  176. }
  177. }
  178. /// Normalize the arguments provided to a log function. The arguments may take one of the
  179. /// following forms:
  180. ///
  181. /// A series of values, or a function that generates the values:
  182. ///
  183. /// [ value, value, ... ]
  184. /// [ function( name, number ) { return [ value, value, ... ] }, context ]
  185. ///
  186. /// For a generator function, an optional context argument provides the function's *this*
  187. /// context. The logger's default context will be used if the context argument is no provided.
  188. /// The log threshold name and number ("info" and 3, for example) are passed as arguments to the
  189. /// function.
  190. ///
  191. /// No message will be logged if a generator function returns undefined. Since the function will
  192. /// only be called if the message type exceeds the log threshold, logger calls may be used to
  193. /// control program behavior based on the log level:
  194. ///
  195. /// this.logger.debug( function( name, number ) {
  196. /// debugControls.visible = true; // returns undefined, so no message
  197. /// } );
  198. ///
  199. /// When *extra* is truthy, the first argument is interpreted as a one-time label that extends
  200. /// the logger's output prefix:
  201. ///
  202. /// [ "label", value, value, ... ]
  203. /// [ "label", function( name, number ) { return [ value, value, ... ] }, context ]
  204. ///
  205. /// The arguments are normalized into a list of values ready to pass to the appender:
  206. ///
  207. /// [ value, value, ... ]
  208. ///
  209. /// @param {Array} args
  210. /// An Array-like list of arguments passed to one of the log functions.
  211. /// @param {Boolean} [extra]
  212. /// If true, interpret args[0] as a one-time label that extends the logger's output prefix.
  213. normalize( args, extra ) { // invoke with *this* as the logger module
  214. // Record the extra one-time label if one is provided. We leave it in the arguments list so
  215. // that we don't convert Arguments to an Array if it isn't necessary.
  216. if ( extra && ( typeof args[0] == "string" || args[0] instanceof String ) ) {
  217. var label = args[0];
  218. var start = 1;
  219. } else {
  220. var label = undefined;
  221. var start = 0;
  222. }
  223. // If a generator function is provided (instead of a series of values), call it to get the
  224. // arguments list.
  225. if ( typeof args[ start ] == "function" || args[ start ] instanceof Function ) {
  226. // Call the function using the provided context or this logger's context. We expect the
  227. // function to return an array of values, a single value, or undefined.
  228. args = args[ start ].call( args[ start+1 ] || this.context, this.level.name, this.level.number );
  229. // Convert a single value to an Array. An Array remains an Array. Leave undefined
  230. // unchanged.
  231. if ( args !== undefined && ( typeof args != "object" || ! ( args instanceof Array ) ) ) {
  232. args = [ args ];
  233. }
  234. } else {
  235. // Remove the extra one-time label.
  236. if ( start > 0 ) {
  237. args = Array.prototype.slice.call( args, start );
  238. }
  239. }
  240. // Add the prefix label to the arguments list and return. But return undefined if a
  241. // generator didn't return a result.
  242. return args ? this.prefixed_arguments( this.label, label, args ) : undefined;
  243. }
  244. /// Update an arguments list to prepend "<label>: " to the output.
  245. prefixed_arguments( label, extra, args ) {
  246. if ( label || extra ) {
  247. if ( args.length == 0 ) {
  248. return [ this.combined_label( label, extra ) ]; // just show the module and function name when there are no additional arguments
  249. } else if ( typeof args[0] == "string" || args[0] instanceof String ) {
  250. return [ this.combined_label( label, extra ) + ": " + args[0] ].concat( Array.prototype.slice.call( args, 1 ) ); // concatenate when the first field is a string so that it may remain a format string
  251. } else {
  252. return [ this.combined_label( label, extra ) + ":" ].concat( args ); // otherwise insert a new first field
  253. }
  254. } else {
  255. return args;
  256. }
  257. }
  258. /// Generate a new label from a parent label and an extra part.
  259. combined_label( label, extra ) {
  260. // Combine with "." unless the extension provides its own separator.
  261. var separator = extra && extra.match( /^[0-9A-Za-z]/ ) ? "." : "";
  262. // Concatenate and return.
  263. if ( label && extra ) {
  264. return label + separator + extra;
  265. } else if ( extra ) {
  266. return extra;
  267. } else if ( label ) {
  268. return label;
  269. } else {
  270. return undefined;
  271. }
  272. }
  273. }
  274. export {
  275. Logger
  276. }