A Javascript stacktrace in any browser
Chances are that if you’ve done any significant Javascript work, you’ve run into a situation where part of the debugging process could be much improved if you just had the function call stack.
I’m going to give you some ways of doing this with and without the popular Firebug extension and have some examples of their uses.
Without Firebug and friends? Using IE?
Sometimes s**t only happens in other browsers. Here’s how to create/log your own stack trace. Put this code in an accessible place in your Javascript file(s) and call the printStackTrace() function inside any function.
function printStackTrace() {
var callstack = [];
var isCallstackPopulated = false;
try {
i.dont.exist+=0; //does not exist - that's the point
} catch(e) {
if (e.stack) { //Firefox
var lines = e.stack.split("\n");
for (var i = 0, len = lines.length; i < len; i++) {
if (lines[i].match(/^\s*[A-Za-z0-9\-_\$]+\(/)) {
callstack.push(lines[i]);
}
}
//Remove call to printStackTrace()
callstack.shift();
isCallstackPopulated = true;
}
else if (window.opera && e.message) { //Opera
var lines = e.message.split("\n");
for (var i = 0, len = lines.length; i < len; i++) {
if (lines[i].match(/^\s*[A-Za-z0-9\-_\$]+\(/)) {
var entry = lines[i];
//Append next line also since it has the file info
if (lines[i+1]) {
entry += " at " + lines[i+1];
i++;
}
callstack.push(entry);
}
}
//Remove call to printStackTrace()
callstack.shift();
isCallstackPopulated = true;
}
}
if (!isCallstackPopulated) { //IE and Safari
var currentFunction = arguments.callee.caller;
while (currentFunction) {
var fn = currentFunction.toString();
//If we can't get the function name set to "anonymous"
var fname = fn.substring(fn.indexOf("function") + 8, fn.indexOf("(")) || "anonymous";
callstack.push(fname);
currentFunction = currentFunction.caller;
}
}
output(callstack);
}
function output(arr) {
//Optput however you want
alert(arr.join("\n\n"));
}
UPDATE: Luke Smith has taken and upgraded the script with a bunch of new features. I encourage you to check out his code here. Thanks, Luke!
It’s ugly, but this works for the latest versions of IE, Firefox, Opera, and Safari. Firefox and Opera give you file names and line numbers when they can, but I couldn’t find a mechanism to get the same from IE and Opera. Hopefully the inline comments describe enough of what is going on. If not, ask :).
Try it out
Give it a shot by clicking here. It will run the snippet below.
function foo() {
var blah;
bar("blah");
}
function bar(blah) {
var stuff;
thing();
}
function thing() {
if (true) { //your error condition here
printStackTrace();
}
}
foo();
Obvious easy way: Firebug (eventually Drosera and Dragonfly)
You can easily get a stack trace at any time by calling console.trace() in your Javascript or in the Firebug console.
Not only will it tell you which functions are on the stack, but it will include the value of each argument that was passed to each function.
This is obviously the best way to go if you are using Firefox.
Furthermore, these tools allow you to dig deeper. Of course, we can’t count on them for ALL situations.
Conclusion
I hope you find this useful. If you have any suggestions/improvements I’d like to hear them! Also all kidding aside, I worked pretty hard on this function, so I’d really appreciate if you’d help me share this with more people. Thanks!
A 25 year-old programmer for
very useful function..
Thanks
Indeed, a very useful function.
Thank you very much to share it.
Thanks, hope it stays useful :)
Interesting!
And where i can use this function?
@Dimitry:
Inside any function, including anonymous ones. Depending on which browser you’re in you’ll get different info. for anon functions, though.
Amazing ! I thought caller was deprecated, glad to see it’s still there. Thank you !
I added some code to display the arguments of each function (Tested in Safari only) :
var currentFunction = arguments.callee.caller;
while (currentFunction) {
var fn = currentFunction.toString();
//If we can’t get the function name set to “anonymous”
var fname = fn.substring(fn.indexOf(”function”) + 8, fn.indexOf(”(”)) || “anonymous”;
// callstack.push(fname);
var args = currentFunction.arguments.length + ‘ args (’
for (var i=0; i<currentFunction.arguments.length; i++) args += i + ‘=’ + currentFunction.arguments[i] + ‘ ‘
args += ‘)’
callstack.push(fname + ‘ ‘ + args)
currentFunction = currentFunction.caller;
}
@Patrick:
Thanks for the update!
Amazing code, thanks!
This is very useful when you need to understand other people code and have no idea who is calling who.
does not work, n95 8gb
@concordiam:
Where are you testing this?
Patched lineRE for Opera referencing functions from remote files:
http://pastie.org/253087
Odd. Looks like my original comment was lost. Text below:
Nice! I was inspired by your work to create a version that included
* load time forking
* normalized stack entries (as much as possible)
* incorporate Patrick’s argument inclusion for IE/Safari
* returns an array of stack entries, thus leaving the printing to the consumer
* and for bonus point, likely far more confusing to read! (yours isn’t bad at all)
http://pastie.org/253058
@Luke:
Very nice! I didn’t give time to flesh it out like you did, but I must say I really like how you organized it.
I’m going to update the article to link to your code and give credit. Thanks!
Glad you like it, and thanks back for paving the way. TBH though, I didn’t do much breadth testing. Just checked against a simple test case in FF2&3, IE6&7, Saf3.1, and Op9.50&9.51. In fact, I’ve made a couple more tweaks in the last couple minutes for Opera’s rendering of anonymous functions (http://pastie.org/253111).
I really ought to get to work now, though :)
@Eric and @Luke:
You might want to look into gist instead of pastie: (a new free feature of GitHub)
http://gist.github.com/
At gist each paste (which looks just like pastie) becomes source controlled under git. That means anyone can fork the code, make modifications, improvements, etc. and:
1) You can be notified of changes
2) Others can see the modifications and newer versions
3) There are diffs of everything
4) You could link to the most current version of the source
5) Accounts are free so they can see more of your code and you can link to your blog
I’m thinking of rewriting it a little myself and making a gist. This code is very clean.
Eric this is some great work. I really understand and appreciate the work that you must have put into it. Thanks! Same to the others who have suggested improvements.
I have some questions, maybe you can point me to some resources or just give a quick answer. I wondered why you used e.stack for “Firefox” when the default block “IE or Safari” seemed to work great in Firefox 3.0 and could include more information? Maybe for older versions of Firefox? Is this standardized?
Keep up the great work!
@Joseph:
I used Firefox’s Error.stack because it gives better information (like filename and line number) instead of the old and busted mechanism :)
@joseph
Additionally, it’s possible for FF and Op at least to abstract the function to optionally accept an Error instance and report on that.
something like
… return function (e) {
if (!(e instanceof Error)) {
try {(0)()} catch (ex) {
e = { stack : ex.stack.replace(/^.*?\n/,”) };
}
}
…
I couldn’t determine a way to support that sort of thing via the caller chain, though I only spent a few mins on it.
Dang, your comment section kills pre tags :(
Cool. Thanks guys =)
Hmm, actually, that’d be a bad call. Probably better as
… return function (e) {
if (e === undefined) {
try {(0)()} catch (ex) {…}
}
return ((e && e.stack) || ”).replace(/(?:\n@:0)?\s+$/m,”). // and so forth
Hi,
I would love to include this code in my own Free Software. Could you please add a license to it ? Without a license this artwork is proprietary and noone is allowed to distribute it.
Congratulations !
I hereby license my code here to the public domain. Use it however you want, AS IS, with no warranty or any obligation on anyone’s part.
I cannot license the additional work that Luke did, thoughts Luke?
-Eric
On a related note, I just introduced a similar tool into JS.Class:
http://jsclass.jcoglan.com/stacktrace.html
This stack tracer is for use with Firebug, and can print names of classes, modules and methods, even for anonymous functions. Mind you, it does this by wrapping all the methods in a class without doing any browser hacks, and by doing lots of expensive name lookups, and your approach can capture stuff mine can’t at present. Nice work.
@James:
Neat Class! Your Javascript certainly seems much more elegant and I learned a bit from the way you approach this. Thank you!
I think the IE and Safari block will blow up if there is any recursion in your call stack. The caller, callee stuff points to function objects, not stack frames / activation records which is what you really need.
@Joey:
Yes, you have a valid point. There needs to be some logic in the IE/Safari block that handles this situation. Perhaps I or someone else will make an update.
Thanks!
Оценка 5!
I have something similar to this attached to a window.onerror handler. When a script error occurs it creates a new Image() and loads error.php with a bunch of query params including the stack trace. On the server side the error reports go into a database. It’s very handy for diagnosing and fixing ajax-related errors.
Is there a way to use this as an onerror handler without the onerror handler breaking the stack information? it seems that the handler will always appear as though it was the first thing called. I don’t want to have to put tries into every function of my code.
Hi,
I wrote tests for the pastie version of printstacktrace/getstacktrace to ensure it does what is expected. I refactored the code to be test friendly. It also uses json2.js instead of custom (flawed) argument formatting in IE.
run the testsuite,
printstacktrace.js,
test-printstacktrace.js
@Loic,
Thanks for pointing out that the arg serialization in the IE/Saf definition was incomplete! I will say, though, that the lack of detail (but not the omission of args) was deliberate to provide consistent output across the browser spectrum.
Interesing idea using JSON.stringify for the arguments. I don’t know if I’d go all the way to using full verbose serialization of args without an opt-in config, though. Beyond introducing a library dependency to support a nice-to-have, json2.js has no recursion protection inside stringify and otherwise substantial object structures passed as params would make the output nigh unreadable. Actually, though, your replacer function does account for the recursion issue, but not correctly.
Assigning
values[value] = index++will cause all objects to be treated the same since object keys are strings, and{foo:'bar'}.toString() === '[object Object]‘making that line effectivelyvalues['[object Object]‘] = index++. This means that theif (values[value])will return true for every object past the first, including nested objects within the first, unless they have an overriddentoStringmethod. So while this will protect stringify from an infinite loop, it won’t return accurate serialization of nested objects or any object beyond the first in the arguments array.Two other things of note in your replacer:
* Why not just use
valuein place ofif (typeof this[key]…?*
indexis not being incremented in the function condition, so if there are dup args around a function arg, the #n will be off* I’m not sure of the benefit of the #n since it does further distinguish the output from disparate browsers.
Also, stringify will return e.g. ‘["arg 1","function",5]‘ resulting in a trace entry ‘myMethod(["arg 1","function",5])’ which suggests that the function was passed one argument, an array with two strings and one number rather than three args comprised of a string, a function, and a number.
I also note that you opted out of the load time fork to define the function behavior. This introduces a runtime requirement to determine the env on every execution of the stacktrace function. This is wasteful.
I hadn’t gotten around to implementing the recursion protection in the IE caller loop that @Joey noticed (Thanks Joey!), so this would certainly be a good thing to include as well.
I sound pretty critical, but honestly I do appreciate that you took the time to do some real testing of it, even so far as setting up a test suite for it! It’s great to see some proof that it appears to be a stable implementation (less the missing/incorrect arg serialization and the lack of protection from recursive functions in both our implementations).
@Eric (et al)
Sure, Public Domain FTW! I’ve already rolled this into a toy lib of mine and it may evolve a bit further and wind up in YUI v3. If it does, I’ll make sure you get attribution props :)
Hi,
Thanks for pointing the recursion protection defect. Does anyone knows how to get a unique id from a JS object ? The memory address would be perfect.
Johan Euphrosine added functions to get the function names from the source file (refactoring code from FireBug) and make the stack trace more readable. The URLs above have been updated and browsershots.org reports tests are working cross platform.
Cheers