Sunday, July 26, 2009

3 painful ways to obtain a stack trace in JavaScript

Whatever evil you can think of, JavaScript stack traces are worse.

After releasing a satisfactory version of CrashKit Python we've turned our attention to AJAX web applications and collection of bug reports from client-side JavaScript code. Unfortunately, whatever solutions we could find on the Web failed to collect enough information and did not suite our requirement of being browser-independent. We really wanted identical exceptions occurring in different browsers to be displayed as a single bug, which means that the topmost line of a stack trace has to be the same.

It turns out there are 3 ways to obtain a stack trace: ex.stack (Firefox-specific), ex.message (Opera-specific) and traversing a chain of arguments.callee.caller.caller.caller. The latter one is extremely limited, and overcoming some of its quirks is both hard and browser-specific.

In Firefox and Opera, the stack trace can be obtained from an exception object. This means we have to get a hold of the exceptions somehow. There is no global event loop or any other central place where a catch-all handler can be installed, so every event handler has to be wrapped in a try-catch block. JavaScript frameworks often make it easier by introducing a common place where event handlers are bound, and in fact we have an experimental jQuery support that wraps all handlers into try-catch blocks automatically.

Other browsers have to use the arguments-based approach, which has several big problems:

1) Because the arguments pseudo-variable corresponds to the current function and is not related to an exception in any way, the catch block has no way to know the function/line the exception has actually occurred at. It can only determine the callers of the function containing the catch block itself, thus the top of the stack trace will be missing.

2) The value of caller attribute is a function, not a stack frame, so the bottom of the stack trace will be missing if some function is called recursively.

3) The only thing we get by traversing the chain of callers is function bodies (the source code). No line numbers or URLs, and function names are only available in Safari, but not in IE.

Fortunately, Safari provides a line number and a URL for the topmost stack trace entry as ex.lineNumber and ex.sourceURL. Thus we can augment the partial stack trace with a correct topmost entry, which is the most important requirement for CrashKit bug grouping to work.

Internet Explorer (6.x–8.x) has no such properties in the exception object. Luckily, Internet Explorer has window.onerror event which is fired when it's too late to collect a stack trace, but does provide a line number and a URL for the topmost stack trace entry. Thus the strategy for handling IE is to collect a partial stack trace and re-throw the exception, hoping that noone would catch it and IE will end up raising onerror, allowing us to augment the stack trace with a correct topmost entry.

We also try to guess some missing information automatically. Given a function body, we use XMLHTTPRequest to download the source code of all included scripts and search for the body textually, which gives us a URL to add to the stack trace. Given a line number within a function, we look for constructs like xxx = function or 'xxx': function to guess a “real name” of an anonymous function.

We also collect a few lines of source code around the position of exception occurrence for the upcoming source code display feature in CrashKit.

The details of the described operations are quirky too. In Opera, some line numbers are relative to the start of a SCRIPT tag, so we have to adjust for that. Automatic semicolon insertion and whitespace differences interfere with the textual function body search, so we have to turn the body into a regular expression. Inline handlers like <button onclick="somecode()"> have to be treated specially for the purposes of stack trace collection (good news is that we can always determine line numbers for such one-line functions).

Another important thing to realize when coding in JavaScript is that Everything Changes. For example, both ex.stack and (of course!) ex.message are strings parsable with a few simple regular expressions, but their (undocumented) format may easily change in the future. New browser versions may be released which support or don't support something that we expect. That's why browser detection is widely recognized as an evil practise, and CrashKit JavaScript does not do any. Instead, it tries to use Firefox- and Opera-specific approaches and falls back to walking the call chain in case of any problems, so it's pretty future-proof.

You can grab the source code on GitHub: crashkit-javascript.js + sample. It defines two reusable functions, CrashKit.report and CrashKit.computeStackTrace, both documented pretty throughly. There has not been much testing yet, so consider this to be of beta quality. The post will be updated as the code matures. (And do check out the CrashKit itself, although it is in a limited beta mode at the moment.)

Thursday, July 23, 2009

Python stack trace saga

How hard can it be to obtain a stack trace in a mature language like Python?

The task turns out to be non-trivial. Python gives you the following dump:

Traceback (most recent call last):
  File "/frickingly/long/path/google/appengine/ext/webapp/__init__.py", line 503, in __call__
    handler.post(*groups)
  File "/another/path/controllers/base.py", line 55, in decoration
    return original_func(self, *args)
  File "/another/path/controllers/product.py", line 42, in post
    self.product.reopening_mode = NAMES_TO_REOPENING_MODES[self.valid_string('reopening_mode')]
KeyError: u'on'

There is something mind-bogglingly odd here if you look closely enough. Python does not provide class names as part of a stack trace. One should expect to see WSGIApplication.__call__ and ProductSettingsHandler.post, while the stack trace only lists __call__ and post. I have, like, a few dozen post functions in my project, and at least 3 of them are in product.py. This hip new OOP thing has never reached this corner of the language.

The way we deal with it is similar to the problem itself, that is, stupid and mind-boggling. A simple heuristic checks whether the first argument of a function is an object that has an attribute pointing to the function. Decorators make the problem worse, and there's another heuristic that deals with some function-based decorators. Net result: CrashKit reports correct class names in most cases, even for decorated functions. (See the code at http://gist.github.com/152398)

Another problem is that “/another/path/controllers/product.py” is a very verbose way of saying “controllers.product”. The path might differ across various machines, and it's crucial to obtain machine-independent stack traces for CrashKit bug grouping to work.

Good news on calculating package names is that you can simply query the value of __name__ from the global context of the frame of interest. Bad news is that sometimes you get None in return, and one notable case is when an import statement has not yet finished execution. In such cases, one has to guess a possible package name by iterating through all sys.path entries.

Again, the implementation (taken from CrashKit Python) is available at http://gist.github.com/152407. Both fragments of code mentioned here are known to work in production environments and are tested on Mac OS X, Linux and Windows. The whole stack trace collection code is available under BSD license as part of CrashKit Python client.

Saturday, July 18, 2009

CrashKit in April–July

Sounds like CrashKit is making progress. Bug listings aren't ugly any more, bugs can be closed and ignored, and occurrence counts are actually useful now.

We've had only a few active users these months, and it gave us a well-needed freedom to change things. Now we are finally satisfied enough to let more users in, and will invite everyone waiting on the list during the coming weeks.

CrashKit is the first product we're launching on our own, and man have we learned a lot of things. We learned that it takes a looooong time (and many loooong discussions) to understand how your product should behave and look like, and what your users actually need. And then just as much to settle various small details.

Now that we've covered the basics, new features will be coming soon. CrashKit was born as a solution for desktop applications, and our primary focus for July is to provide more features for web developers. Follow our progress on the public bug tracker of CrashKit.

The blog's been inactive for a while, and this is going to change. Stay tuned for some stories about the development of CrashKit.

Wednesday, March 4, 2009

CrashKit beta and YourSway introduction

Hey.

Today's a big day: we are launching a limited beta of CrashKit, our multi-language online crash reporting tool.

We've started building it a few weeks ago for our consulting projects' needs. CrashKit has proved itself extremely useful for us. Perhaps you will love it just as much.

So many features are on our roadmap: collection of usage statistics, two-way customer interaction, much better bug tracker integration, more notification options, branding and internationalization. And, of course, more supported languages and frameworks. If you are missing any particular feature or have anything else to say, please drop us a line.

CrashKit is free to use during the beta period, and will always be free for open-source and small personal projects.

Who are the guys behind CrashKit?

We are a team of six guys and girls in their twenties, providing a wide range of consulting services and working on our own developer productivity tools.

We believe in being agile in everything. We believe in well-designed software, brain-driven development and highly satisfied customers. That's the life worth living.