DIY javascript stack trace

Javascript functions have a handy local variable called arguments that you can use to get information about the currently executing function. This can be used to build your own stack trace in any browser for situations where you can’t use a debugger to get information about your code.

That’s madness!

Yes it is. In most cases there are better tools like Firebug that have a full javascript debugger that you can use to get a stack trace whenever you want without having to do any tricksy javascript coding:
firebug's stack trace

I originally wrote this code because I had to debug a page that was running in the WinForms web browser component. The page worked fine when running in regular IE but totally locked up when it was running in the browser component. I was on my own debugging a huge tangle of javascript event code without any debugger to help me.

Other places this might help is if you want to log stack traces on mouse events where breaking into the debugger will stop the execution, when you’re automating browser testing or if you want to be able to silently log information a nasty to test error condition running on someone else’s browser.

The code

Here’s a stack trace implementation that adds a new debugging method of trace() to all functions. It uses the function.caller property to walk the call stack and the arguments collection to create a method signature for each function in the call stack.

It’s been tested in Firefox 3, IE7, Safari 3 and Opera 9.6.

  1. Function.prototype.trace = function()
  2. {
  3.     var trace = [];
  4.     var current = this;
  5.     while(current)
  6.     {
  7.         trace.push(current.signature());
  8.         current = current.caller;
  9.     }
  10.     return trace;
  11. }
  12. Function.prototype.signature = function()
  13. {
  14.     var signature = {
  15.         name: this.getName(),
  16.         params: [],
  17.         toString: function()
  18.         {
  19.             var params = this.params.length > 0 ?
  20.                 "'" + this.params.join("', '") + "'" : "";
  21.             return this.name + "(" + params + ")"
  22.         }
  23.     };
  24.     if(this.arguments)
  25.     {
  26.         for(var x=0; x<this .arguments.length; x++)
  27.             signature.params.push(this.arguments[x]);
  28.     }
  29.     return signature;
  30. }
  31. Function.prototype.getName = function()
  32. {
  33.     if(this.name)
  34.         return this.name;
  35.     var definition = this.toString().split("\n")[0];
  36.     var exp = /^function ([^\s(]+).+/|>;
  37.     if(exp.test(definition))
  38.         return definition.split("\n")[0].replace(exp, "$1") || "anonymous";
  39.     return "anonymous";
  40. }

Javascript file

To use the code, call the trace() method on any function. You will only get a meaningful stack trace on a function that is currently running so in most cases you’d want to call trace() on the arguments.callee method of the current function:

  1. var trace = arguments.callee.trace();
  2. document.getElementById("output").innerHTML = trace.join("<br />\n");

Complete example

How it works

The arguments collection is a local variable that is automatically created inside all javascript functions that contains a list of all parameters that are passed to the function. The arguments variable has a property called callee which is reference to the function itself. This is possible because functions are just a special type of object in javascript so they can be stored in a regular variable just like a number or a string can.

The arguments.callee property gives a reference to the currently running function. The function variable has a property called caller that gives a reference to the function that called the current function. This can be used to walk all the way up the function stack trace. We’ve reached the top of the stack when the caller property of a function returns null:

  1. var current = this;
  2. while(current)
  3. {
  4.     trace.push(current.signature());
  5.     current = current.caller;
  6. }

In most browsers, the function has a property called name that returns the name given to the function when it was created. IE doesn’t support this but calling the function’s toString() method will return the whole function definition and the name can be extracted from the definition. Anonymous functions don’t have to be given a name, so these functions will just return “anonymous” instead of a name:

  1. Function.prototype.getName = function()
  2. {
  3.     if(this.name)
  4.         return this.name;
  5.     var definition = this.toString().split("\n")[0];
  6.     var exp = /^function ([^\s(]+).+/|>;
  7.     if(exp.test(definition))
  8.         return definition.split("\n")[0].replace(exp, "$1") || "anonymous";
  9.     return "anonymous";
  10. }

Limitations

It won’t work with recursive code. The caller property gives a reference to the function not to the function’s activation object. The activation object is like an instance of the function that sits on the call stack. I’d be interested in hearing if anyone finds a way to work around this problem.

More information

For ASP.NET users, Joel Rumerman has created an version of the trace code and has included a neat trick for getting useful function names from functions created using prototypes.

Posted on 12 May 07 by Helen Emerson (last updated on 29 Aug 11).
Filed under Javascript

Comments

Ganja 22 May 2007

Thanks for the clean implementation/examples! Very useful.

UUDIBUUDI 08 Nov 2007

Good stuff! One suggestion to make it work in a .HTA file (W2k3, IE7):

in the function getSignature I get an error when it is apparently processing a TypeError, which has no arguments. Hence theFunction.arguments doesn’t seem to exist and javascript throws a nice error there. I changed it into this:

if (theFunction.arguments) {
for (…) {
}
}

and it worked like a charm.

Henadzi 10 Jan 2008

Great thx for the example! I think that creating javascript stack trace is a fantastic!

TJ 15 Feb 2008

nice stuff. Unfortunately looping endlessly on recursive function calls.

Lee 19 Jul 2008

It does fall over if an invalid argument is being passed into a function – theFunction.arguments.length is 1, say, but theFunction.arguments[ 0 ] is null. In this case, adding “if ( nextArgument == null ) nextArgument = ‘?’” will give a sensible output.

zeeman 04 Sep 2008

Thx for this great code!

sebasd 30 Aug 2009

I get a parse error when running “Function.prototype.getName = function()” under Safari. The reg expression is not accepted.

I changed:

var exp = /^function ([^\s(]+).+/|>;

To:

var exp = /^function ([^\s(]+).+/;

Maybe a typo because the code snippet was parsed by the Website ?

Thanks, this function is usefull by itself :

function myFunction()
{
var myCaller = myFunction.caller.getName();
}

Fritz Schenk 16 Sep 2009

Happy to report that it works in IE6 and IE8
Thanks for the great code.

Trackbacks