You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

317 lines
9.0 KiB

4 years ago
  1. /**
  2. * Cross-browser support for logging in a web application.
  3. *
  4. * @author David I. Lehn <dlehn@digitalbazaar.com>
  5. *
  6. * Copyright (c) 2008-2013 Digital Bazaar, Inc.
  7. */
  8. var forge = require('./forge');
  9. require('./util');
  10. /* LOG API */
  11. module.exports = forge.log = forge.log || {};
  12. /**
  13. * Application logging system.
  14. *
  15. * Each logger level available as it's own function of the form:
  16. * forge.log.level(category, args...)
  17. * The category is an arbitrary string, and the args are the same as
  18. * Firebug's console.log API. By default the call will be output as:
  19. * 'LEVEL [category] <args[0]>, args[1], ...'
  20. * This enables proper % formatting via the first argument.
  21. * Each category is enabled by default but can be enabled or disabled with
  22. * the setCategoryEnabled() function.
  23. */
  24. // list of known levels
  25. forge.log.levels = [
  26. 'none', 'error', 'warning', 'info', 'debug', 'verbose', 'max'];
  27. // info on the levels indexed by name:
  28. // index: level index
  29. // name: uppercased display name
  30. var sLevelInfo = {};
  31. // list of loggers
  32. var sLoggers = [];
  33. /**
  34. * Standard console logger. If no console support is enabled this will
  35. * remain null. Check before using.
  36. */
  37. var sConsoleLogger = null;
  38. // logger flags
  39. /**
  40. * Lock the level at the current value. Used in cases where user config may
  41. * set the level such that only critical messages are seen but more verbose
  42. * messages are needed for debugging or other purposes.
  43. */
  44. forge.log.LEVEL_LOCKED = (1 << 1);
  45. /**
  46. * Always call log function. By default, the logging system will check the
  47. * message level against logger.level before calling the log function. This
  48. * flag allows the function to do its own check.
  49. */
  50. forge.log.NO_LEVEL_CHECK = (1 << 2);
  51. /**
  52. * Perform message interpolation with the passed arguments. "%" style
  53. * fields in log messages will be replaced by arguments as needed. Some
  54. * loggers, such as Firebug, may do this automatically. The original log
  55. * message will be available as 'message' and the interpolated version will
  56. * be available as 'fullMessage'.
  57. */
  58. forge.log.INTERPOLATE = (1 << 3);
  59. // setup each log level
  60. for(var i = 0; i < forge.log.levels.length; ++i) {
  61. var level = forge.log.levels[i];
  62. sLevelInfo[level] = {
  63. index: i,
  64. name: level.toUpperCase()
  65. };
  66. }
  67. /**
  68. * Message logger. Will dispatch a message to registered loggers as needed.
  69. *
  70. * @param message message object
  71. */
  72. forge.log.logMessage = function(message) {
  73. var messageLevelIndex = sLevelInfo[message.level].index;
  74. for(var i = 0; i < sLoggers.length; ++i) {
  75. var logger = sLoggers[i];
  76. if(logger.flags & forge.log.NO_LEVEL_CHECK) {
  77. logger.f(message);
  78. } else {
  79. // get logger level
  80. var loggerLevelIndex = sLevelInfo[logger.level].index;
  81. // check level
  82. if(messageLevelIndex <= loggerLevelIndex) {
  83. // message critical enough, call logger
  84. logger.f(logger, message);
  85. }
  86. }
  87. }
  88. };
  89. /**
  90. * Sets the 'standard' key on a message object to:
  91. * "LEVEL [category] " + message
  92. *
  93. * @param message a message log object
  94. */
  95. forge.log.prepareStandard = function(message) {
  96. if(!('standard' in message)) {
  97. message.standard =
  98. sLevelInfo[message.level].name +
  99. //' ' + +message.timestamp +
  100. ' [' + message.category + '] ' +
  101. message.message;
  102. }
  103. };
  104. /**
  105. * Sets the 'full' key on a message object to the original message
  106. * interpolated via % formatting with the message arguments.
  107. *
  108. * @param message a message log object.
  109. */
  110. forge.log.prepareFull = function(message) {
  111. if(!('full' in message)) {
  112. // copy args and insert message at the front
  113. var args = [message.message];
  114. args = args.concat([] || message['arguments']);
  115. // format the message
  116. message.full = forge.util.format.apply(this, args);
  117. }
  118. };
  119. /**
  120. * Applies both preparseStandard() and prepareFull() to a message object and
  121. * store result in 'standardFull'.
  122. *
  123. * @param message a message log object.
  124. */
  125. forge.log.prepareStandardFull = function(message) {
  126. if(!('standardFull' in message)) {
  127. // FIXME implement 'standardFull' logging
  128. forge.log.prepareStandard(message);
  129. message.standardFull = message.standard;
  130. }
  131. };
  132. // create log level functions
  133. if(true) {
  134. // levels for which we want functions
  135. var levels = ['error', 'warning', 'info', 'debug', 'verbose'];
  136. for(var i = 0; i < levels.length; ++i) {
  137. // wrap in a function to ensure proper level var is passed
  138. (function(level) {
  139. // create function for this level
  140. forge.log[level] = function(category, message/*, args...*/) {
  141. // convert arguments to real array, remove category and message
  142. var args = Array.prototype.slice.call(arguments).slice(2);
  143. // create message object
  144. // Note: interpolation and standard formatting is done lazily
  145. var msg = {
  146. timestamp: new Date(),
  147. level: level,
  148. category: category,
  149. message: message,
  150. 'arguments': args
  151. /*standard*/
  152. /*full*/
  153. /*fullMessage*/
  154. };
  155. // process this message
  156. forge.log.logMessage(msg);
  157. };
  158. })(levels[i]);
  159. }
  160. }
  161. /**
  162. * Creates a new logger with specified custom logging function.
  163. *
  164. * The logging function has a signature of:
  165. * function(logger, message)
  166. * logger: current logger
  167. * message: object:
  168. * level: level id
  169. * category: category
  170. * message: string message
  171. * arguments: Array of extra arguments
  172. * fullMessage: interpolated message and arguments if INTERPOLATE flag set
  173. *
  174. * @param logFunction a logging function which takes a log message object
  175. * as a parameter.
  176. *
  177. * @return a logger object.
  178. */
  179. forge.log.makeLogger = function(logFunction) {
  180. var logger = {
  181. flags: 0,
  182. f: logFunction
  183. };
  184. forge.log.setLevel(logger, 'none');
  185. return logger;
  186. };
  187. /**
  188. * Sets the current log level on a logger.
  189. *
  190. * @param logger the target logger.
  191. * @param level the new maximum log level as a string.
  192. *
  193. * @return true if set, false if not.
  194. */
  195. forge.log.setLevel = function(logger, level) {
  196. var rval = false;
  197. if(logger && !(logger.flags & forge.log.LEVEL_LOCKED)) {
  198. for(var i = 0; i < forge.log.levels.length; ++i) {
  199. var aValidLevel = forge.log.levels[i];
  200. if(level == aValidLevel) {
  201. // set level
  202. logger.level = level;
  203. rval = true;
  204. break;
  205. }
  206. }
  207. }
  208. return rval;
  209. };
  210. /**
  211. * Locks the log level at its current value.
  212. *
  213. * @param logger the target logger.
  214. * @param lock boolean lock value, default to true.
  215. */
  216. forge.log.lock = function(logger, lock) {
  217. if(typeof lock === 'undefined' || lock) {
  218. logger.flags |= forge.log.LEVEL_LOCKED;
  219. } else {
  220. logger.flags &= ~forge.log.LEVEL_LOCKED;
  221. }
  222. };
  223. /**
  224. * Adds a logger.
  225. *
  226. * @param logger the logger object.
  227. */
  228. forge.log.addLogger = function(logger) {
  229. sLoggers.push(logger);
  230. };
  231. // setup the console logger if possible, else create fake console.log
  232. if(typeof(console) !== 'undefined' && 'log' in console) {
  233. var logger;
  234. if(console.error && console.warn && console.info && console.debug) {
  235. // looks like Firebug-style logging is available
  236. // level handlers map
  237. var levelHandlers = {
  238. error: console.error,
  239. warning: console.warn,
  240. info: console.info,
  241. debug: console.debug,
  242. verbose: console.debug
  243. };
  244. var f = function(logger, message) {
  245. forge.log.prepareStandard(message);
  246. var handler = levelHandlers[message.level];
  247. // prepend standard message and concat args
  248. var args = [message.standard];
  249. args = args.concat(message['arguments'].slice());
  250. // apply to low-level console function
  251. handler.apply(console, args);
  252. };
  253. logger = forge.log.makeLogger(f);
  254. } else {
  255. // only appear to have basic console.log
  256. var f = function(logger, message) {
  257. forge.log.prepareStandardFull(message);
  258. console.log(message.standardFull);
  259. };
  260. logger = forge.log.makeLogger(f);
  261. }
  262. forge.log.setLevel(logger, 'debug');
  263. forge.log.addLogger(logger);
  264. sConsoleLogger = logger;
  265. } else {
  266. // define fake console.log to avoid potential script errors on
  267. // browsers that do not have console logging
  268. console = {
  269. log: function() {}
  270. };
  271. }
  272. /*
  273. * Check for logging control query vars.
  274. *
  275. * console.level=<level-name>
  276. * Set's the console log level by name. Useful to override defaults and
  277. * allow more verbose logging before a user config is loaded.
  278. *
  279. * console.lock=<true|false>
  280. * Lock the console log level at whatever level it is set at. This is run
  281. * after console.level is processed. Useful to force a level of verbosity
  282. * that could otherwise be limited by a user config.
  283. */
  284. if(sConsoleLogger !== null) {
  285. var query = forge.util.getQueryVariables();
  286. if('console.level' in query) {
  287. // set with last value
  288. forge.log.setLevel(
  289. sConsoleLogger, query['console.level'].slice(-1)[0]);
  290. }
  291. if('console.lock' in query) {
  292. // set with last value
  293. var lock = query['console.lock'].slice(-1)[0];
  294. if(lock == 'true') {
  295. forge.log.lock(sConsoleLogger);
  296. }
  297. }
  298. }
  299. // provide public access to console logger
  300. forge.log.consoleLogger = sConsoleLogger;