Exception handling best practices
No, I am not going to tell you to use throw; instead of throw e; I am going to talk about exception messages, assumptions, and pain.
Exception hierarchies are useful in many ways, mostly because they bring order to the way we handle exceptions. We can catch a specific exception, or a root exception in a hierarchy, and hanlde them specifically. But, one of the usages of exception hierarchies is to add additional data to an exception. In many cases, this is very useful data, such as the SQL error code or the details node of in a soap fault or the list of assemblies that could not be loaded.
Do you know what this three datums has in common?
- They are very useful
- They do not show in ex.ToString()
Guess what is going to be shown in any log, error message, etc?
You got that right, the ex.ToString() output!
If you have additional information in the exception, it must be available on the exception afterward. Trying to diagnose assembly load failures is driving me mad.
Imagine finding things like this in the log:
- "ReflectionTypeLoadException: Unable to load one or more of the requested types. Retrieve the LoaderExceptions property for more information."
- "SoapException: Server was unable to process request"
Imagine gettting one of those during dev, you have no way of knowing where this is happening. Often you can't even set a breakpoint in the code there, and inspect the exception, because it is handled inside some library code. ASP.Net is a good example of how this can happen, and where this is a highly annoying issue to work with.
To summrise, if you create exceptions, make sure to remember a simple rule, everything should go in the ex.ToString().
Comments
It's not clear to me whether when you say "exception hierarchies" you mean in terms of inheritance, or nested exceptions. Using nested exceptions gives the desired behaviour automatically:
using System;
class Test
{
}
Output:
System.Exception: Outer detail ---> System.Exception: Inner detail
--- End of inner exception stack trace ---
(There would be stack traces if the exceptions had actually been thrown, of course.)
Jon
inheritance, I have clarified it in the post
I think all creators of frameworks where you cannot prevent the swallowing of exception information, should be smacked on the head repeatedly with their own keyboard, until their code does allow the exception details... or am I too harsh (I could be one of them :P )?
Ayende's not talking about losing nested exception information, he's talking about ancillary information that is stored in a custom exception type that may not be revealed in the ToString().
This is problematic because unless you explicitly decode each exception type, you won't see these details. That can make the difference between spending hours futilely chasing down a problem based on a vague exception message and a stack trace and being led right to the root cause thanks to some extra little detail (that was in the exception object but perhaps not represented in the message).
Anyways, I definitely agree with Ayende on this point. If you've got some extra details available on your custom exception type then you should include them in the ToString() wherever possible since that's what most people will see in the end.
I always include any original exception in the ones I throw - and when an exception is logged, both the ToString() and the full message + stack trace are included. The full info is hidden until a button is pressed (quite easy in HTML).
Why isn't it standard to run a reflection-based object dumper against the exception? If the nesting level is set to something high, one should get all nested exceptions, anything in the IDictionary, and any custom properties. One does need to watch out for sensitive information, which is why I think there should be an exception with a SensitiveData dictionary that one can derive from if it is possible that your exception might have sensitive information. If that is too hard, add SensitiveData to Exception.
When I put lots of information in my exceptions, I find that debugging often isn't required. It's so nice to see an error and say, "I know exactly what caused that."
Another solution is to use a logging system that traverse all the innerexception dumping everything. But the suggestion of ayende is real true, when you create a custom exception always remember to include all the information in the ToString().
Alk.
Luke,
Because there is a standard way to output all the required information.
ex.ToString()
Requiring something like that is a smell
Should ex.ToString() reveal sensitive information?
I agree and practice exactly as Luke and Gian. Dump wads of stuff into your exception handler during your initial coding, and also dump everything to logs from day one, and debugging becomes more of an exercise of pin the tail on the donkey.
The donkey being how to fix the issue, not what caused the issue.
What about making the exception serializable? I think that's how Service Trace Viewer logs exceptions.
In the little in house app at work, I use the Microsoft Exception Message Box from SQL Server as my last-minute-throw-up-a-dialog-before-dying dialog for uncaught exceptions. It's pretty hot because it unwinds the exceptions, lets you peruse the dictionary, and most importantly has a "copy to clipboard" that my coworkers use to send the complete error.
Check it out at http://www.microsoft.com/downloads/details.aspx?familyid=DF0BA5AA-B4BD-4705-AA0A-B477BA72A9CB&displaylang=en.
It's part of the SQL Server feature pack because it's the same dialog Management Studio Express uses, but it's just a DLL that has no dependency on SQL Server.
For our Web site, we use log4net, and I wrote a quick and dirty renderer that would unwind the entire exception in the server logs:
http://piasecki.name/InnerExceptionRenderer.cs
It doesn't do any fancy reflection, but it helps a lot because as Luke said, it's nice to be able to say "oh, I know exactly what caused that."
Is there a way to set up log4net to traverse all the innerexception, dumping everything?
Yes,
ex.ToString()
Comment preview