I worked on a simple error handler that solved those issues at my prior job. What we did was to make every function have one in and one out (so no returns in the middle).  Then you just have this static Logger object, and you add the current stackframe at the top of the funtion, and then just pop it off at the end. 

By doing this, the logger knows what the 'current' function is, so you can call functions "Annotate" to mark any variables or information you want.  The logger would handle the thread exception so that when an error occured, it would just spit out the call stack and also show all of the annotations recorded at each level for the functions. It was pretty cool.  Was nice cause you could see not just the callstack, but the last 10 or so functions that were called as well to help figure out the flow that might have caused a problem.  We later tied in some events to track function timings in a general way (without modifying the application code in any of the programs which were already using the logger).  Having a global object is also nice cause you can setup global things to mark, which might be useful for all errors.  Not just appdomain names, but if you load an order, just globally mark that you're working with so and so order, so you do not have to keep marking it in every function.  It doesn't take too long to set this kind of thing up,a nd it just adds one line at the top, one at the bottom and the annotation functions in the middle.

This method was quite a bit faster than using tracing, and we would leave this on during production.