A tracer utility in 2kb

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.

tracer in chrome console

(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 😉

Advertisement

13 thoughts on “A tracer utility in 2kb

    1. Aeron, thanks for the very nice comments.

      Good point about preventing double tracing.
      I think the code you added might not work because the traceOn is just the name of the function that is assigned to thisObj. So the body of thisObj will not contain the name “traceOn” itself.

      One way to do it is to check if thisObj already has the property ‘traceOff’. I updated the code with this fix. Thanks again!

      1. I had made traceOn a named function in order to test.. but of course your solution is much more effecient! It would be awesome if you could add your code to github for better collaboration!

  1. Hi Angus,
    I am inspired by your work as I have been looking for a mechanism to support our javascript testing. The problem I am trying to solve involves testing closures. I don’t want to compromise my production javascript and have decided to use a revealing module pattern as an approach however using any kind of spies or method replacement to detect when the method is fired unobtrusively seems impossible. Have you tried your trace on anything other than global functions ?

    Thank you for sharing!

    1. This piece of code is awesome, traces all global functions, but i need to trace nested functions too, i m not able to find some simple ways to do it. Any help in this regard is very much appreciated

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Twitter picture

You are commenting using your Twitter account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s