How to get Exception.StackTrace line numbers on Windows Phone
(update: I've written an updated blog post about this. Also I made it available on NuGet as package AsyncStackTraceEx. And published the source code under MIT license on Github.)
Scenario: I've written my app and released it to users. Some of them have reported crashes but I don't know where. So I released an update which captures unhandled exceptions and invites the users to email these to me, in the hope that I can figure out what's wrong.
Problem: On Windows Phone it's not possible to deploy the PDB alongside the EXE/DLL. Without it, Exception.StackTrace is unable to provide line-numbers, and so all my phone error-reports come back without line-numbers and I don't know how to debug.
Solution: I'll write a small helper which adds those line numbers to exception stack-traces, without needing PDBs.
- Download ExceptionStackTrace.zip [3mb, with demo projects in VB and C#, for Windows Phone 8.0]
How it works
Here's a side-by-side comparison of the output.
PDB-based Exception.StackTrace(the highlighted parts are absent on phone since they depend on PDBs) | Using my helper-library(the highlighted parts are present even on phone) |
at VB$StateMachine_3_BarAsync.MoveNext() in Class1.vb:line 24--- End of stack trace from previous location where exception was thrown --- at TaskAwaiter.ThrowForNonSuccess(Task task) at TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task) at TaskAwaiter.GetResult() at VB$StateMachine_2_FooAsync.MoveNext() in Class1.vb:line 19--- End of stack trace from previous location where exception was thrown --- at TaskAwaiter.ThrowForNonSuccess(Task task) at TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task) at TaskAwaiter.GetResult() at VB$StateMachine_1_TestAsync.MoveNext() in Class1.vb:line 14--- End of stack trace from previous location where exception was thrown --- at TaskAwaiter.ThrowForNonSuccess(Task task) at TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task) at TaskAwaiter`1.GetResult() at VB$StateMachine_0_Button1_Click.MoveNext() in Class1.vb:line 5 | at Test.BarAsync at Test.FooAsync()#BarAsync in Class1.vb:19 at Test.TestAsync()#FooAsync(True) in Class1.vb:14 at Test.Button1_Click() in Class1.vb:5 |
I noticed that all the exceptions I ever cared about either arose in async operations in the framework, or in my own async methods. That let me use a fluent syntax, like this: (I've given the examples in C#, to balance out the fact that implementation is in VB...)
private async void Button1_Click(object sender, RoutedEventArgs e)
{
try
{
await TestAsync().Log(); // so exception's stacktrace will show "Button1Click() in MainPage.xaml.cs:53"
}
catch (Exception ex)
{
MessageBox.Show(ex.Log()); // this retrieves the version of the stacktrace that includes line numbers
}
}
Whenever I await something, I stick on ".Log()" at the end. This ensures that line numbers are preserved for any exception that comes out of the awaited task. And rather than retrieving the Exception.StackTrace property, I retrieve the stack-trace through my Exception.Log() extension method. This cleans up the async callstack into something more readable, and inserts back all those line numbers. I also added two other optional overloads of await-point logging, to get richer information in the exception's async callstack; I use that richer information to (optionally) indicate which API I'm calling, and what arguments I'm passing to it. After all, it's often the arguments to a method that are crucial to understanding why it threw the exception.
await BarAsync(b).Log("BarAsync"); // so the exception stacktrace shows "...#BarAsync in MainPage.xaml.cs:61"
await FooAsync(true).Log("FooAsync", true); // so the exception stacktrace shows "...#FooAsync(true) in MainPage.xaml.cs:72"
Comments
Anonymous
August 15, 2013
Nice trick! However it seems to me that the implementation is more complex than it should... Here's my attempt at making it simpler: gist.github.com/.../6250080 (I also change the name of the method to AsyncTrace)Anonymous
August 19, 2013
That's nice. What you observed was that it's better to add into the Exception.Data dictionary, rather than mess around with weak reference list as I was using. I didn't know about Exception.Data :) ... You also decided not to bother correlating it with the actual exception stack trace, nor cleaning up the exception stack trace, nor providing extra data. Fair enough design decisions!Anonymous
October 18, 2013
Thanks Lucian and Thomas for sharing your ideas. The first time I saw something like this it was on "Tango Master" (a windows phone app). The application crashed, and when I restarted it then app asked for sending a crash report. Once I saw it, I implemented my own approach for providing the same functionality, and now that I see your ideas I have some code changes that It will provide more info. Kind regards, Herber