Bug 879228 - [browserconsole] tune console.jsm to ease Addon SDK integration. r=msucan
☠☠ backed out by 62b7d9ca9586 ☠ ☠
authorAlexandre Poirot <poirot.alex@gmail.com>
Tue, 18 Jun 2013 17:27:04 -0400
changeset 135482 e086e888bd8dc52d64d0d4a2f3e1df8a2d7c8f56
parent 135481 ca5fe9958e9018b7e3290ac96be41de836b9eb4a
child 135483 80cf4c214910212cc169f11ff426866bbd7c931c
push id29691
push userryanvm@gmail.com
push dateTue, 18 Jun 2013 21:27:49 +0000
treeherdermozilla-inbound@80cf4c214910 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersmsucan
bugs879228
milestone24.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Bug 879228 - [browserconsole] tune console.jsm to ease Addon SDK integration. r=msucan
toolkit/devtools/Console.jsm
--- a/toolkit/devtools/Console.jsm
+++ b/toolkit/devtools/Console.jsm
@@ -15,17 +15,17 @@
  * - The Firebug console API is implemented in many places with differences in
  *   the implementations, so there isn't a single reference to adhere to
  * - The Firebug console is a rich display compared with dump(), so there will
  *   be many things that we can't replicate
  * - The primary use of this API is debugging and error logging so the perfect
  *   implementation isn't always required (or even well defined)
  */
 
-this.EXPORTED_SYMBOLS = [ "console" ];
+this.EXPORTED_SYMBOLS = [ "console", "ConsoleAPI" ];
 
 const Cu = Components.utils;
 
 Cu.import("resource://gre/modules/XPCOMUtils.jsm");
 Cu.import("resource://gre/modules/ConsoleAPIStorage.jsm");
 
 XPCOMUtils.defineLazyModuleGetter(this, "Services",
                                   "resource://gre/modules/Services.jsm");
@@ -267,16 +267,51 @@ function logProperty(aProp, aValue) {
     reply += formatTrace(trace);
   }
   else {
     reply += "    - " + aProp + " = " + stringify(aValue) + "\n";
   }
   return reply;
 }
 
+const LOG_LEVELS = {
+  "all": Number.MIN_VALUE,
+  "debug": 2,
+  "log": 3,
+  "info": 3,
+  "trace": 3,
+  "timeEnd": 3,
+  "time": 3,
+  "group": 3,
+  "groupEnd": 3,
+  "dir": 3,
+  "dirxml": 3,
+  "warn": 4,
+  "error": 5,
+  "off": Number.MAX_VALUE,
+};
+
+/**
+ * Helper to tell if a console message of `aLevel` type
+ * should be logged in stdout and sent to consoles given
+ * the current maximum log level being defined in `console.maxLogLevel`
+ *
+ * @param {string} aLevel
+ *        Console message log level
+ * @param {string} aMaxLevel {string}
+ *        String identifier (See LOG_LEVELS for possible
+ *        values) that allows to filter which messages
+ *        are logged based on their log level
+ * @return {boolean}
+ *        Should this message be logged or not?
+ */
+function shouldLog(aLevel, aMaxLevel) {
+  return LOG_LEVELS[aMaxLevel] <= LOG_LEVELS[aLevel];
+}
+
 /**
  * Parse a stack trace, returning an array of stack frame objects, where
  * each has filename/lineNumber/functionName members
  *
  * @param {string} aStack
  *        The serialized stack trace
  * @return {object[]}
  *        Array of { file: "...", line: NNN, call: "..." } objects
@@ -384,58 +419,83 @@ function startTimer(aName, aTimestamp) {
 function stopTimer(aName, aTimestamp) {
   let key = aName.toString();
   let duration = (aTimestamp || Date.now()) - gTimerRegistry.get(key);
   gTimerRegistry.delete(key);
   return { name: aName, duration: duration };
 }
 
 /**
+ * Dump a new message header to stdout by taking care of adding an eventual
+ * prefix
+ *
+ * @param {object} aConsole
+ *        ConsoleAPI instance
+ * @param {string} aLevel
+ *        The string identifier for the message log level
+ * @param {string} aMessage
+ *        The string message to print to stdout
+ */
+function dumpMessage(aConsole, aLevel, aMessage) {
+  aConsole.dump(
+    "console." + aLevel + ": " +
+    aConsole.prefix +
+    aMessage + "\n"
+  );
+}
+
+/**
  * Create a function which will output a concise level of output when used
  * as a logging function
  *
  * @param {string} aLevel
  *        A prefix to all output generated from this function detailing the
  *        level at which output occurred
  * @return {function}
  *        A logging function
  * @see createMultiLineDumper()
  */
 function createDumper(aLevel) {
   return function() {
+    if (!shouldLog(aLevel, this.maxLogLevel)) {
+      return;
+    }
     let args = Array.prototype.slice.call(arguments, 0);
     let frame = getStack(Components.stack.caller, 1)[0];
     sendConsoleAPIMessage(aLevel, frame, args);
     let data = args.map(function(arg) {
       return stringify(arg);
     });
-    dump("console." + aLevel + ": " + data.join(", ") + "\n");
+    dumpMessage(this, aLevel, data.join(", "));
   };
 }
 
 /**
  * Create a function which will output more detailed level of output when
  * used as a logging function
  *
  * @param {string} aLevel
  *        A prefix to all output generated from this function detailing the
  *        level at which output occurred
  * @return {function}
  *        A logging function
  * @see createDumper()
  */
 function createMultiLineDumper(aLevel) {
   return function() {
-    dump("console." + aLevel + ": \n");
+    if (!shouldLog(aLevel, this.maxLogLevel)) {
+      return;
+    }
+    dumpMessage(this, aLevel, "");
     let args = Array.prototype.slice.call(arguments, 0);
     let frame = getStack(Components.stack.caller, 1)[0];
     sendConsoleAPIMessage(aLevel, frame, args);
     args.forEach(function(arg) {
-      dump(log(arg));
-    });
+      this.dump(log(arg));
+    }, this);
   };
 }
 
 /**
  * Send a Console API message. This function will send a console-api-log-event
  * notification through the nsIObserverService.
  *
  * @param {string} aLevel
@@ -491,48 +551,84 @@ function sendConsoleAPIMessage(aLevel, a
   }
 
   Services.obs.notifyObservers(consoleEvent, "console-api-log-event", null);
   ConsoleAPIStorage.recordEvent("jsm", consoleEvent);
 }
 
 /**
  * This creates a console object that somewhat replicates Firebug's console
- * object.
+ * object
+ *
+ * @param {object} aConsoleOptions
+ *        Optional dictionary with a set of runtime console options:
+ *        - prefix {string} : An optional prefix string to be printed before
+ *                            the actual logged message
+ *        - maxLogLevel {string} : String identifier (See LOG_LEVELS for
+ *                            possible values) that allows to filter which
+ *                            messages are logged based on their log level.
+ *                            If falsy value, all messages will be logged.
+ *                            If wrong value that doesn't match any key of
+ *                            LOG_LEVELS, no message will be logged
+ *        - dump {function} : An optional function to intercept all strings
+ *                            written to stdout
+ * @return {object}
+ *        A console API instance object
  */
-this.console = {
+function ConsoleAPI(aConsoleOptions = {}) {
+  // Normalize console options to set default values
+  // in order to avoid runtime checks on each console method call.
+  this.dump = aConsoleOptions.dump || dump;
+  this.prefix = aConsoleOptions.prefix || "";
+  this.maxLogLevel = aConsoleOptions.maxLogLevel || "all";
+}
+
+ConsoleAPI.prototype = {
   debug: createMultiLineDumper("debug"),
   log: createDumper("log"),
   info: createDumper("info"),
   warn: createDumper("warn"),
   error: createMultiLineDumper("error"),
   exception: createMultiLineDumper("error"),
 
   trace: function Console_trace() {
+    if (!shouldLog("trace", this.maxLogLevel)) {
+      return;
+    }
     let args = Array.prototype.slice.call(arguments, 0);
     let trace = getStack(Components.stack.caller);
     sendConsoleAPIMessage("trace", trace[0], args,
                           { stacktrace: trace });
-    dump("console.trace:\n" + formatTrace(trace) + "\n");
+    dumpMessage(this, "trace", "\n" + formatTrace(trace));
   },
   clear: function Console_clear() {},
 
   dir: createMultiLineDumper("dir"),
   dirxml: createMultiLineDumper("dirxml"),
   group: createDumper("group"),
   groupEnd: createDumper("groupEnd"),
 
   time: function Console_time() {
+    if (!shouldLog("time", this.maxLogLevel)) {
+      return;
+    }
     let args = Array.prototype.slice.call(arguments, 0);
     let frame = getStack(Components.stack.caller, 1)[0];
     let timer = startTimer(args[0]);
     sendConsoleAPIMessage("time", frame, args, { timer: timer });
-    dump("console.time: '" + timer.name + "' @ " + (new Date()) + "\n");
+    dumpMessage(this, "time",
+                "'" + timer.name + "' @ " + (new Date()));
   },
 
   timeEnd: function Console_timeEnd() {
+    if (!shouldLog("timeEnd", this.maxLogLevel)) {
+      return;
+    }
     let args = Array.prototype.slice.call(arguments, 0);
     let frame = getStack(Components.stack.caller, 1)[0];
     let timer = stopTimer(args[0]);
     sendConsoleAPIMessage("timeEnd", frame, args, { timer: timer });
-    dump("console.timeEnd: '" + timer.name + "' " + timer.duration + "ms\n");
+    dumpMessage(this, "timeEnd",
+                "'" + timer.name + "' " + timer.duration + "ms");
   },
 };
+
+const console = new ConsoleAPI();