Safe JavaScript Stack Trace

Here are my thoughts on the interesting problem of safely getting a stack trace in JavaScript.

tl;dr - The Current Solution

( '' + (function () { try { throw new Error('get stack trace') } catch (e) { return e && e.stack || '' } return '' })() )

If at all possible this will result in a string containing a stack trace, and if it is not possible to get a stack trace it should always result in a string. Just what we need for logging.

October 28th, 2013

Back in 2013, I was brought an interesting problem. How do you get a stack trace in JavaScript safely? There was a specific context for the question, but it got me thinking generically about the problem. Here is what I originally wrote up...

Google will very quickly tell you that the global Error constructor produces an object with a stack property (a string), but... is that safe to use everywhere? Of course not... Older versions of our "friend" IE don't have it (no surprise there), and being a globally scoped variable, you can override it. Plus there are many more engines than just web browsers now...

I was pondering how to make sure Error exists safely. Checking against window is fine if you know you'll be in the browser, but I'd rather safely check against all environments. That is to say I wanted:

For example, if (in the browser) you put: console.log( SomeVariableThatDoesntExist ); you'll get a *syntax* error, which means if you're in IE and checking for Error, that's a syntax error. Remember though, I don't want to rely on window, so I can't just try window.Error, because that would only support browsers.

I went searching, and there's a single, clean JavaScript keyword which is safe in all engines for any name regardless of whether or not it exists in scope. It's defined as a core concept and has been there since the beginning, so unless something really crazy happens, anything which is called "JavaScript" should support it. That keyword is typeof.

That is to say, typeof X will always work, regardless of whether X has ever been mentioned before. I don't really care if the global scope is this or window or module or something else entirely... and I specifically don't want to have to be in the global scope. I just care whether or not Error exists and can produce a .stack property.

Since any function can be called with the new keyword (whether or not it's meant to be a constructor, and even whether or not it has a prototype property), I really only care if Error is a function, which typeof will tell me. That lead me to:

( typeof Error === 'function' && (new Error()).stack )

But of course, that could return a string, or false, or undefined, or if you did something really interesting (that is to say, something bad), then the stack property could return another type entirely. Since I want false-ish results to return an empty string I added an "or empty string" to the above, and then because I want to guarantee that it's a string no matter what (even if you've done something bad to the String prototype or to the generic toString method), I decided to combine that with a string typecast via addition, leaving me with:

( '' + ( typeof Error === 'function' && (new Error()).stack || '' ) )

Which will always produce a string, hopefully containing the stack trace (otherwise containing an empty string if stack is false-ish or the result of toString of the stack property), and is safe in all environments. Score!

August 4th, 2017

This topic came up again, but with an interesting wrinkle. The original code snippet was still safe (wasn't causing any errors itself), but wasn't returning a stack trace in an environment that we knew could produce stack traces. What was going on?

It turns out according to MDN...

Different browsers set this value at different times. For example, Firefox sets it when creating an Error object, while PhantomJS sets it only when throwing the Error, and MSDN docs also seem to match the PhantomJS implementation.

This means I can't just create an Error object, I need to actually throw one. If I have to throw an error, I want to catch it, because one of my original goals was to not cause errors in the process of getting the stack trace.

The goal is to get a stack trace, right? So long as any thrown error is caught and not passed up the execution chain, it doesn't matter how we get the stack trace (whether it's from a "true" error created by the JavaScript interpreter or from creating our own Error object)

try { throw new Error('getting stack trace') } catch (e) { e.stack }

So in this case e.stack should be a string containing a stack trace here, right? Well, it may not be (for all the same original reasons), but at least in PhantomJS it should be. However, now we're inside a try-catch block, which is not expression friendly, so we need to convert a command list into an expression. Back to basics!

What we need is a closure to execute the set of instructions and return the stack trace as a string, or an empty string if we can't get a stack trace. Thus the new implementation:

(function () { try { throw new Error('get stack trace') } catch (e) { return e && e.string || '' } return '' })()

"Wait a minute!" you cry out. "What happened to all that use of typeof and other protections?"

The new code is encapsulated in a try-catch block, so if anything goes wrong (for example Error not a constructor), it'll be caught and an error passed to the catch-block, which is ultimately what we want anyway. We don't really care if it's a SyntaxError or some other type, so long as we get the stack trace off of it.

That only leaves the encapsulation protecting against non-string types for the .stack property, so the new format is:

( '' + (function () { try { throw new Error('get stack trace') } catch (e) { return e && e.stack || '' } return '' })() )