JS.StackTrace
The StackTrace module can be mixed into any class or module and helps with debugging
programs using Firebug. When it is mixed into a class, it makes the class log all its
method calls to the console, along with arguments passed in and values returned. Firebug
will let you inspect the arguments and return values, making this a powerful tool for
seeing what your program is doing.
Let’s take a reasonably complex example as it demonstrates the full capabilities of
StackTrace. This shows how you can mix it in when defining a class or a module, how
to add it to existing modules, and shows some complex output.
// Parent class. run() calls getString()
Parent = new JS.Class({
include: JS.StackTrace,
run: function() {
return this.getString();
},
getString: function() {
return 'Running';
}
});
// Mixin module. run() calls the super method
Mixin = new JS.Module({
include: JS.StackTrace,
run: function() {
return this.callSuper().toUpperCase();
}
});
// Augment Observable module with StackTrace
JS.Observable.include(JS.StackTrace);
// Child class, inherits from Parent, includes Mixin and Observable
// run() calls fire(), which calls Observable#notifyObservers()
Child = new JS.Class(Parent, {
include: [Mixin, JS.Observable, JS.StackTrace],
run: function() {
this.fire();
return this.callSuper() + '!!!';
},
fire: function() {
this.notifyObservers();
}
});
Suppose that somewhere else in your code you have this:
var child = new Child, parent = new Parent;
child.addObserver(parent.method('run'));
child.run('something')
StackTrace will print the following to the console. Every method call in traced classes
is logged, with indentation to show how deep in the call stack you are. Vertical lines
connect the beginning and end of a single method call.
JS.Observable#addObserver( [function()] )
JS.Observable#addObserver() --> undefined
Child#run( ["something"] )
| Child#fire( [] )
| | JS.Observable#notifyObservers( [] )
| | | JS.Observable#isChanged( [] )
| | | JS.Observable#isChanged() --> true
| | | JS.Observable#countObservers( [] )
| | | JS.Observable#countObservers() --> 1
| | | Parent#run( [] )
| | | | Parent#getString( [] )
| | | | Parent#getString() --> Running
| | | Parent#run() --> Running
| | JS.Observable#notifyObservers() --> undefined
| Child#fire() --> undefined
| Mixin#run( ["something"] )
| | Parent#run( ["something"] )
| | | Parent#getString( [] )
| | | Parent#getString() --> Running
| | Parent#run() --> Running
| Mixin#run() --> RUNNING
Child#run() --> RUNNING!!!
Tracing modes
StackTrace has a couple of different modes that control its output. The default mode
is full, which logs all method calls and shows stack level using indentation. The
other mode is errors, which only prints out the call stack when an exception is
thrown and is not caught. The method that threw the exception is printed at the top,
the method that called that next, and so on until we reach the outermost method.
Suppose we make the following changes to our program:
Parent.define('getString', function() {
throw new TypeError;
});
JS.StackTrace.logLevel = 'errors';
Then running the program again produces:
TypeError thrown by Parent#getString. Backtrace:
Parent#getString( [] )
Parent#run( [] )
JS.Observable#notifyObservers( [] )
Child#fire( [] )
Child#run( ["something"] )
This output has been condensed slightly for clarity. In practise, StackTrace also
logs the object that each method is called on so you can inspect it with Firebug.
Caveats
It is important to know the limitations of this module as the limitations of JavaScript mean it can cause serious problems if abused. The first and most important caveat is, do not use this module in production code. It wraps all the functions in your classes with extra calls, at least trippling the number of function calls and causing a large performance hit. Only use it in development.
The other caveats relate to consequences of JavaScript’s naming system. When you create
a class like Parent = new JS.Class(), the class stored in the variable Parent has
no way of knowing the name of the variable(s) that reference it. Therefore, StackTrace
has an autodiscovery system to find out the names of classes and methods at runtime. It works
using a breadth-first search of all global objects in the program, continuing until
it finds an object matching the class to trace. This lookup is expensive, and the cost
grows exponentially with how deeply nested your namespace is. For example, finding a
class Parent is much faster than finding MyApp.utils.Parent, as the latter is two
levels of nesting deeper.
One obvious side-effect of the above is that your class must be globally accessible
so that StackTrace can figure out its name. Classes defined inside closures will
never be found.
Finally, note that StackTrace cannot find names for classes immediately, there is
some delay in naming a class after you include StackTrace into it. Consider this:
var Foo = new JS.Class({
include: JS.StackTrace
});
This creates a new class, mixes StackTrace into it, then assigns the finished
class to the variable Foo. So at the point that StackTrace is mixed in, the
class has not been bound to a name. To get around this, names are looked up
asynchronously using a setTimeout() call. Code that runs synchronously with the
class definition will not be correctly traced.