Here are my thoughts on the interesting problem of safely getting a stack trace in JavaScript.
( '' + (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.
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:
this
has been re-scoped (this
is only guaranteed to be the global object if you're at the script level)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!
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 '' })() )