Inspired by a snippet of code in Oliver Steele’s legendary Functional library, here’s a lightweight tool to help keep track of JavaScript invocations. It works in Chrome, Safari, Firebug and IE8.

(Disclaimer: I developed this module over the course of just a few hours so I can’t vouch for its robustness. It’s intended for illustration as much as utility, though having said that, it’s stood up to everything I’ve thrown at it so far)

The key idea here is dynamic function replacement (the Russian Doll Principle I blogged about a while back). I don’t attempt to mutate the original function, instead I create a new function which sandwiches a call to the original function between tracer code. This new function then gets assigned to the method property of the host object.

Indeed this is really a method tracer. Calling traceAll for a root object will trace-enable all methods of that object. This method search will recurse if you pass true as the second argument, which allows it to traverse prototype chains. The beauty of this top-down approach is that we get to assign a name (i.e. the name of the method property) to every function we find. Only pure anonymous functions (those that were never assigned to a variable) remain nameless – actually they are ignored entirely since they can not be reached by navigating the object-property map.

Here’s the API:

traceAll Enable trace for all methods of a given object (repeated calls are additive)

root The object whose descendant methods are to be trace-enabled window is not allowed).

recurse If supplied, method searching will recurse down the property chain.

tracer.traceAll(jQuery,true); >> tracing init >> tracing size >> tracing toArray >> tracing get >> tracing pushStack >> tracing each >> tracing ready etc.

untraceAll Remove all tracing.

tracer.untraceAll(); >> tracing disabled

traceMe Trace a function. This is mostly used privately, but you might also use this to turn tracing on for just one function (useful for globally defined functions)

function function object to be traced

methodName the method name that will appear on the tracer logs

If you want to inspect all the currently traced methods check the tracer.tracing array.

This is how the tracer looks in firebug. I’m showing a trace of our real-life development code and it suggests there may be a redundancy issue that needs looking at:

Here’s the full code listing for tracer:

String.prototype.times = function(count) { return count < 1 ? '' : new Array(count + 1).join(this); } var tracer = { nativeCodeEx: /\[native code\]/, indentCount: -4, tracing: [], traceMe: function(func, methodName) { var traceOn = function() { var startTime = +new Date; var indentString = " ".times(tracer.indentCount += 4); console.info(indentString + methodName + '(' + Array.prototype.slice.call(arguments).join(', ') + ')'); var result = func.apply(this, arguments); console.info(indentString + methodName, '-> ', result, "(", new Date - startTime, 'ms', ")"); tracer.indentCount -= 4; return result; } traceOn.traceOff = func; for (var prop in func) { traceOn[prop] = func[prop]; } console.log("tracing " + methodName); return traceOn; }, traceAll: function(root, recurse) { if ((root == window) || !((typeof root == 'object') || (typeof root == 'function'))) {return;} for (var key in root) { if ((root.hasOwnProperty(key)) && (root[key] != root)) { var thisObj = root[key]; if (typeof thisObj == 'function') { if ((this != root) && !thisObj.traceOff && !this.nativeCodeEx.test(thisObj)) { root[key] = this.traceMe(root[key], key); this.tracing.push({obj:root,methodName:key}); } } recurse && this.traceAll(thisObj, true); } } }, untraceAll: function() { for (var i=0; i<this.tracing.length; ++i) { var thisTracing = this.tracing[i]; thisTracing.obj[thisTracing.methodName] = thisTracing.obj[thisTracing.methodName].traceOff; } console.log("tracing disabled"); tracer.tracing = []; } }

The heart of the tracer is the traceMe method which was also the easiest part to write. A new function traceOn is defined in which the original function gets called (line 15) surrounded by tracing paraphernalia. Note the ingenious times function that I’m using to render the indentation – I pilfered that from Prototype.js.

We assign the original function to the traceOn.traceOff property so it can be accessed when the user wishes to revert tracing. JavaScript functions are first class objects so they support properties (such as traceOff) which must be transferred to the traceOn function to ensure the tracer functions correctly mimics the original (especially since the tracer also works with constructors and functions assigned to prototypes – to ensure the latter get traced, recurse should be set to true). Function properties get copied in lines 21-23.

Finally the new function is returned so that it can be assigned in place of the original.

The traceAll function walks the object hierarchy from the root down looking for existing methods and using traceMe to swap them out. I chose to disallow window as a root object for a couple of reasons. First there are several native objects (StorageList in Firefox is one) that do not take kindly to being asked to give up property names and will throw a security exception. Taming these cases even with try/catch proved troublesome and erratic. Secondly when running tracer.traceAll(window, true) on Chrome it throws an oversized stack exception. If and when I have more time I’ll try to bring window back into the fold. In the meantime you can call traceMe directly if you want to add tracing to individual global functions.

function add(a,b){return a + b}; add = tracer.traceMe(add,'add'); >> tracing add add(2, 4) >> add(2,4); >> add -> 6 (9 ms)

traceAll iterates properties of objects, tossing out those properties that evaluate to the host object (to avoid stack overflow) and native functions, which we test for using the regEx. To cut out unnecessary noise I also reject methods of tracer itself (see line 34)

After candidate functions have been swapped for their tracing sibling, an object representing the method property is pushed onto the tracer.tracing array to help us to efficiently untrace when the time comes. Hopefully the untraceAll function speaks for itself.

And that’s pretty much it! Have fun and please let me know about all the inevitable bugs and oversights 😉