1

最近我遇到了一个问题,即尽可能多地从异常中获取详细信息。原因?好吧,当您需要解决发货产品中的问题时,日志通常是您唯一拥有的东西。

明显地

Exception.ToString()

工作得很好,但是当您处理FaultException并且谁知道自定义异常会给您带来什么惊喜时,它并不是很有帮助。

那么,以适当的偏执程度获得异常详细信息的最佳方法是什么?

4

2 回答 2

1

我环顾四周并用谷歌搜索了这个话题。令人惊讶的是,没有太多关于这个的讨论。无论如何,我试图在这里复合精髓。

说话便宜。给我看代码。

  1. 这里我们有抛出异常的示例代码。

    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);
        }
    }
    
  2. 下面是 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属性来降低性能开销。我只是在调用扩展异常处理之前检查它。

于 2012-10-10T19:52:22.597 回答
1

记录来自异常的信息可能很有用,但事务记录的方法可能会提供更多信息。

这个想法是尽可能多地跟踪日志消息,但不要将它们写在日志文件中,而是将它们累积在内存队列中。如果用户提交的操作已成功完成,那么您只需处置跟踪消息的集合。但是,如果抛出异常,您可以同时记录异常和执行期间积累的跟踪消息。

好处是显而易见的 - 您不仅知道出现问题的信息,而且您还知道系统内部发生了哪些导致异常的进程。

于 2012-10-10T21:03:56.923 回答