EDIT: Jan 24th, 5:18pm (GMT+1): I am late to add this edit, but much of this post is now obsolete. Adob added Global Exception Handling to AIR 2.0. As far as I know, you won’t be able to get a stack trace if you’re not running in debug mode, and that’s a bummer, but it’s great to be able to catch unhandled exceptions now!
As I’ve been developing YNAB 3, I’ve been pulling my hair out to figure out a way to add crash reporting to our application. YNAB Pro, our popular C# app has it, and it’s saved us so many times I can’t even tell you! Our bug reporting system has a log of every crash we’ve ever had in the field, complete with a stack trace and user comments. I’m proud to say that we don’t get many, but when we do, it’s trivial to write back and explain how they can fix it.
Adobe doesn’t think this is important
Coming from the C# world, I thought it would be easy: Just put a try/catch around the main block, and then catch unhandled exceptions, pop up a dialog, and be done! Well, amazingly, you can’t catch unhandled exceptions in Flex/AIR. Yeah, you read that right. You can’t. The #1 voted on bug in their Flex database, with 415 votes as of now, is (to paraphrase) “Please add global exception handling“. Coming in at 160 votes is, to paraphrase again, “Please oh please add global exception handling!“. There seems to be a lot of silence from Adobe on these issues. They’re not prioritized high, and no one ever really comments on those bugs. It’s kinda weird to be honest….
Why it’s worse than you think
Anyway, the way it works is this: The Flash player intercepts exceptions like this and handles it for you. “That’s okay,” you’re thinking. “As long as my user knows something went wrong, I won’t have automatic exception handling, but at least they’ll know to email me when they see the error dialog.” What I haven’t mentioned yet is that there IS NO ERROR DIALOG on the runtime installations of the Flash player or in the default installation of Adobe AIR. Yep: When your shiny component throws an exception deep in the bowels, your users will know something went wrong because the app might start acting “strange” a few minutes later for who knows what reason. Some people in the Flex world get around this by telling their users to install the debug player of Flash. Even though that would be a crazy solution, I don’t have that luxury since we’re deploying in Adobe AIR.
Why it’s even worse than that
Thanks to Doug McCune’s excellent post on this topic, I saw that someone was Monkey Patching their Flex libraries to add some code to try and catch most errors. That got me thinking: “I’ll just monkey patch a low-level class like SystemManager to intercept most of the event dispatches and make sure I have a try-catch around it!” That’s all well and good, except that if an exception happens as the result of a “EventDispatcher.dispatchEvent”, your code will never know that an exception happened. That’s because the flash player is in charge of that handy dispatchEvent method, and it will happily swallow up your exception, display nothing to the user, and return the code to you as if nothing happened. (In case you’re wondering, the return value of dispatchEvent is still “true” even though it failed due to an exception). I learned all about this from Luke Bayes‘ excellent blog post on the topic.
Yep, still worse
At one point I thought that I’d just turn logging on and watch where my application’s error output was being sent. If I see an error, “BAM!” crash report time! From everything I’d read, the only way to get logging was with a debug flash player. AIR doesn’t have a debug version (or so I thought), so that looked like a dead end.
Some light at the end of the tunnel
I found out about Luke’s post from Jörg Birkhold’s post about his cool discovery that you can easily catch exceptions that are a result of “callLater” functions. That’s awesome, and it’s going to make this job 50% easier. You don’t even have to monkey-patch. So, that takes care of “callLater”, which will take care of a lot of the drawing code and plenty of other behind-the-scenes stuff that I don’t quite understand. That still doesn’t cover exceptions as the result of a dispatched event though, which means an uncaught exception as the result of user input is going to be ignored…
I’m getting desperate here…
Since the current dispatchEvent function swallows our exceptions, and a lot of our unexpected exceptions will be in response to events like mouse or keyboard input, maybe there is a way I can make the SystemManager use my own dispatchEvent method so I don’t have to rely upon the one built into Flash Player. Nope. You can’t implement your own IEventDispatcher without using EventDispatcher.dispatchEvent. It’s a good thing – that solution was going to be messy…
Popping up error messages in the release version of AIR!
When re-reading Doug’s post I saw my comment at the bottom that said, ‘For our beta testers (who are obviously more hardcore), I plan on using this cookbook trick to get it to pop up the error dialog: http://www.adobe.com/cfusion/communityengine/index.cfm?event=showdetails&postid=13206&loc=en_US&productid=4‘
I had forgotten all about that! Turns out that if you drop an empty file called “Debug” into your application’s META-INF/AIR folder, AIR will happily pop up an ActionScript error box, complete with stack trace, in case of an exception. Zavi discovered this by using Procmon, which is an excellent tool.
An app can set its own debug flag
So first I decided to see if the application itself could write that debug file out itself on startup. The answer is:
Windows XP: Yes
Vista and up: No!
Vista and Windows 7 have strict permissions around writing to Program Files, so that’s not going to work, but let’s just punt on that for now. So sure enough, I’ve got it writing the magic debug file at startup in the Application’s creationComplete event handler. Unfortunately, it appears that by the time the app can write this file out, AIR seems to have decided whether or not it’s in debug mode (not 100% confirmed yet), so if this goes into production, the app would have to restart itself when it first launches, but that would be doable. Even if I wrote the code in the first line of SystemManager, the AIR runtime is bootstrapped by then, so I doubt there is a way around this. On Windows, I think an external process is going to be required to install this file, but if you’re talking about hardcore beta testers, that might be okay.
Wait – that probably means we can turn on logging!
Something Zavi said in his post about that putting the AIR player into debug mode got me thinking: What if it really is just the Flash player running in debug? That means I might be able to turn on logging! I fired up ProcMon myself and sure enough, there were some beautiful calls to try and read mm.cfg, the Flash Player debug configuration file. I my mm.cfg in the right location for XP and told it to log traces and errors.
I fired my test app up again (one that throws an exception right when it launches), and I saw not only did I get a popup telling me about the error, but I simultaneously got the entire stack trace written out to the flashlog.txt log file! I also verified that the AIR app can read from the flash log file as it’s running. Now that I can watch the flashlog.txt file for changes, I have a way to detect with certainty if there is ever an exception anywhere in an AIR app. I need to turn this into a library and clean code, and it’s possible that I’m overlooking something, but with this, coupled with Jörg’s callLater handling, and I think we have all of the bases covered.
So, what risks am I running by running the app in debug mode? How big of a performance hit are we talking here?
I’ll post more as I finish up the code to verify that this works, but when YNAB 3 goes into beta, I think we’re going to have a way for people to automatically submit bugs, and I can’t tell you how releaved I am about that!