While I think the exception model in .NET is very good, there are two aspects which can make diagnosing issues awkward, particularly when you are reading exception messages that have been written using logging components such as log4net. These are:
1. Strongly typed exceptions that inherit from the Exception base class may contain extra properties that hold vital information. These are not shown by default.
2. Exceptions can contain inner exceptions, and often these are more informative than the outer exception. Sometimes this can go three or four levels deep.
I’ve experienced aspect 1 when using a number of the Google APIs, and aspect 2 with NHibernate.
The Exception extension method given below solves these issues respectively by:
1. Using reflection to write out all properties.
2. Iterating through all exception levels.
Here’s the code:
using System; using System.Linq; using System.Text; namespace ExtensionMethods { public static class ExceptionExtensionMethods { public static string GetFullExceptionDetails(this Exception exception, string customMessage = null) { var message = new StringBuilder(); var exceptionLevel = 0; var currentException = exception; if (string.IsNullOrEmpty(customMessage)) { message.AppendLine(customMessage); } // Loop over exceptions and inner exceptions: while (currentException != null) { exceptionLevel++; var title = string.Format("Exception Level {0}", exceptionLevel); message.AppendLine(new string('=', title.Length)); message.AppendLine(title); message.AppendLine(new string('=', title.Length)); // Read all properties associated with the exception: message.AppendLine(currentException.GetExceptionProperties()); message.AppendLine(); // Get the next leve of exception: currentException = currentException.InnerException; } return message.ToString(); } private static string GetExceptionProperties(this Exception exception) { var properties = exception.GetType().GetProperties(); var fields = properties.Select( property => String.Format("{0}: {1}", property.Name, (property.GetValue(exception, null) ?? String.Empty))); return String.Join(Environment.NewLine, fields); } } }
…which can be demonstrated using the following console application:
using System; namespace ExtensionMethods { public static class Program { public static void Main() { try { var argumentException = new ArgumentException("Level 2", "parameter"); var invalidOperationException = new InvalidOperationException("Level 1", argumentException); throw invalidOperationException; } catch (Exception exception) { Console.WriteLine(exception.GetFullExceptionDetails("An error occurred")); Console.ReadLine(); } } } }
This outputs:
There is still room for improvement, particularly in dealing with properties that contain collections, but the extra information could prove invaluable so it’s well worth the effort.