Thursday, August 12, 2010

A better way to handle debug logging in IE

When it comes to debugging Javascript in Internet Explorer, the Internet Explorer Developer Toolbar and Firebug Lite are both very useful tools. For a long time, Firebug Lite offered functionality not otherwise available to Internet Explorer developers. Recently, the Developer Toolbar has began incorporating some of the functionality found in Firebug Lite, such as console debugging, They both allow you to execute arbitrary Javascript code to inspect and manipulate elements and state on your page (in IE).

Firebug Lite is installed by dragging a bookmarklet to your Favourites bar. The bookmarklet contains Javascript that opens up a Firebug instance for whatever page you are on. Open the Developer Tools via Tools menu > Developer Tools or F12. Once you have either open, you can run arbitrary Javascript statements and see console logging displayed nicely.

The problem is that often I have Javascript console logging that executes while the document is loading (such as during onLoad). This means IE will always show an error when the page loads because the console object doesn't exist until you open the Developer Tools or Firebug Lite. Usually I will develop in Firefox or Chrome (which both have a console object by default), use a lot of console logging to debug etc and get a nasty surprise when I eventually switch to IE and find a bunch of console is not defined errors. Worse, what happens if you forget to remove those logging statements and the code hits production with those errors?

I have attempted to deal with the development phase of this issue before by opening a text area and dumping console logging to it, but that solution was ugly, didn't play nicely in frame documents and needed to be tied into onLoad. Since those days, I have been playing around with jQuery and Firebug and found a better way to do it.

The code below checks for the existence of the console object. If it is not present, make one up with all the expected log methods: log, debug, info, warn, error. Instead of outputting the messages, it will store them in an array that can be referenced later. When Firebug Lite or the Developer Tools are opened, they override the console object with their own. The old messages can still be accessed though - through the console using the jQuery syntax $(oldConsole.history). That code is below.

/**
 * Catch logging to console in browsers that don't have the console
 * object e.g. IE. Even though you have Firebug Lite, you can only turn
 * that on after the page has loaded - too late for console logging
 * made before you load Firebug Lite.
 *
 * NOTE: This code needs to be loaded before any logging statements.
 */
if (!window.console) {
   window.console = {
      logHistory: function () {
         // Store logs to an array for reference. Thanks to
         // paulirish.com/2009/log-a-lightweight-wrapper-for-consolelog/
         console.history = console.history || [];
         console.history.push(arguments);
      },
      log: function() {
         this.logHistory(arguments);
      },
      debug: function() {
         this.logHistory(arguments);
      },
      info: function() {
         this.logHistory(arguments);
      },
      warn: function() {
         this.logHistory(arguments);
      },
      error: function() {
         this.logHistory(arguments);
      }
   };

   // Once Firebug is opened up, can still access old messages here.
   // E.g. jQuery syntax: $(oldConsole.history).
   window.oldConsole = window.console;
}

This code needs to be placed before any logging statements happen.

Here is a nice way to re-print those old statements after Firebug has loaded (using jQuery syntax).

$(oldConsole.history).each(function(index) {
    console.debug($(oldConsole.history)[index]);
});

Additionally, I have found the following two snippets to be very useful. The first provides a shortcut (log) instead of console.log. The second allows you to use use log as part of a jQuery builder chain.

/**
 * Allows logging to console.log shortcut "log" object. E.g.:
 *    log('inside coolFunc',this,arguments);
 * paulirish.com/2009/log-a-lightweight-wrapper-for-consolelog/
 */
window.log = function(){
  if(this.console){
    console.log( Array.prototype.slice.call(arguments) );
  }
};


// Allow inline jQuery logging following builder pattern: E.g.
//    $("p.someClass").log("inline logging").css("background-color", "red");
// http://ajaxian.com/archives/jquery-logging
jQuery.fn.log = function (msg) {
   console.log("%s: %o", msg, this);
   return this;
};

Pages that helped me with this code.