最近我遇到了一个问题,即尽可能多地从异常中获取详细信息。原因?好吧,当您需要解决发货产品中的问题时,日志通常是您唯一拥有的东西。
明显地
Exception.ToString()
工作得很好,但是当您处理FaultException并且谁知道自定义异常会给您带来什么惊喜时,它并不是很有帮助。
那么,以适当的偏执程度获得异常详细信息的最佳方法是什么?
最近我遇到了一个问题,即尽可能多地从异常中获取详细信息。原因?好吧,当您需要解决发货产品中的问题时,日志通常是您唯一拥有的东西。
明显地
Exception.ToString()
工作得很好,但是当您处理FaultException并且谁知道自定义异常会给您带来什么惊喜时,它并不是很有帮助。
那么,以适当的偏执程度获得异常详细信息的最佳方法是什么?
我环顾四周并用谷歌搜索了这个话题。令人惊讶的是,没有太多关于这个的讨论。无论如何,我试图在这里复合精髓。
这里我们有抛出异常的示例代码。
protected void TestExceptionDetails()
{
try
{
int zero = 0;
try
{
int z = zero / zero;
}
catch (Exception e)
{
var applicationException = new ApplicationException("rethrow", e);
// put some hint why exception occured
applicationException.Data.Add("divider_value", zero);
throw applicationException;
}
}
catch (Exception e)
{
var extendedexceptionDetails = GetExtendedexceptionDetails(e);
log.ErrorFormat("Detailed:{0}", extendedexceptionDetails);
}
}
下面是 GetExtendedExceptionDetails 方法:
/// <summary>
/// This utility method can be used for retrieving extra details from exception objects.
/// </summary>
/// <param name="e">Exception.</param>
/// <param name="indent">Optional parameter. String used for text indent.</param>
/// <returns>String with as much details was possible to get from exception.</returns>
public static string GetExtendedexceptionDetails(object e, string indent = null)
{
// we want to be robust when dealing with errors logging
try
{
var sb = new StringBuilder(indent);
// it's good to know the type of exception
sb.AppendLine("Type: " + e.GetType().FullName);
// fetch instance level properties that we can read
var props = e.GetType().GetProperties(BindingFlags.Public | BindingFlags.Instance).Where(p => p.CanRead);
foreach (PropertyInfo p in props)
{
try
{
var v = p.GetValue(e, null);
// in case of Fault contracts we'd like to know what Detail contains
if (e is FaultException && p.Name == "Detail")
{
sb.AppendLine(string.Format("{0}{1}:", indent, p.Name));
sb.AppendLine(GetExtendedexceptionDetails(v, " " + indent));// recursive call
}
// Usually this is InnerException
else if (v is Exception)
{
sb.AppendLine(string.Format("{0}{1}:", indent, p.Name));
sb.AppendLine(GetExtendedexceptionDetails(v as Exception, " " + indent));// recursive call
}
// some other property
else
{
sb.AppendLine(string.Format("{0}{1}: '{2}'", indent, p.Name, v));
// Usually this is Data property
if (v is IDictionary)
{
var d = v as IDictionary;
sb.AppendLine(string.Format("{0}{1}={2}", " " + indent, "count", d.Count));
foreach (DictionaryEntry kvp in d)
{
sb.AppendLine(string.Format("{0}[{1}]:[{2}]", " " + indent, kvp.Key, kvp.Value));
}
}
}
}
catch (Exception exception)
{
//swallow or log
}
}
//remove redundant CR+LF in the end of buffer
sb.Length = sb.Length - 2;
return sb.ToString();
}
catch (Exception exception)
{
//log or swallow here
return string.Empty;
}
}
如您所见,我们使用反射来获取实例属性,然后获取它们的值。我知道这很昂贵,但我们并不真正知道具体异常暴露了哪些可能的属性。我们都希望在应用程序中不会经常发生错误,以免影响性能。
现在让我们看看我们实际获得了什么。
这是 Exception.ToString 返回的:
System.ApplicationException: rethrow ---> System.DivideByZeroException: Attempted to divide by zero.
at NET4.TestClasses.Other.TestExceptionDetails() in c:\tmp\prj\NET4\TestClasses\Other.cs:line 1116
--- End of inner exception stack trace ---
at NET4.TestClasses.Other.TestExceptionDetails() in c:\tmp\prj\NET4\TestClasses\Other.cs:line 1123
这将返回我们的新方法:
Type: System.ApplicationException
Message: 'rethrow'
Data: 'System.Collections.ListDictionaryInternal'
count=1
[divider_value]:[0]
InnerException:
Type: System.DivideByZeroException
Message: 'Attempted to divide by zero.'
Data: 'System.Collections.ListDictionaryInternal'
count=0
InnerException: ''
TargetSite: 'Void TestExceptionDetails()'
StackTrace: ' at NET4.TestClasses.Other.TestExceptionDetails() in c:\tmp\prj\NET4\TestClasses\Other.cs:line 1116'
HelpLink: ''
Source: 'NET4'
TargetSite: 'Void TestExceptionDetails()'
StackTrace: ' at NET4.TestClasses.Other.TestExceptionDetails() in c:\tmp\prj\NET4\TestClasses\Other.cs:line 1123'
HelpLink: ''
Source: 'NET4'
我们使用log4net进行日志记录,并使用ILog.IsErrorEnabled属性来降低性能开销。我只是在调用扩展异常处理之前检查它。
记录来自异常的信息可能很有用,但事务记录的方法可能会提供更多信息。
这个想法是尽可能多地跟踪日志消息,但不要将它们写在日志文件中,而是将它们累积在内存队列中。如果用户提交的操作已成功完成,那么您只需处置跟踪消息的集合。但是,如果抛出异常,您可以同时记录异常和执行期间积累的跟踪消息。
好处是显而易见的 - 您不仅知道出现问题的信息,而且您还知道系统内部发生了哪些导致异常的进程。