【问题标题】:.NET: How to convert Exception to string?.NET:如何将异常转换为字符串?
【发布时间】:2011-12-23 18:57:54
【问题描述】:

当抛出异常时(在 IDE 中调试时),我有机会查看详细信息:

但是在代码中,如果我打电话给exception.ToString(),我看不到那些有用的细节:

System.Data.SqlClient.SqlException (0x80131904): Could not find stored procedure 'FetchActiveUsers'.
  [...snip stack trace...]

但 Visual Studio 有一些魔力,它可以将异常复制到剪贴板:

其中提供了有用的细节:

System.Data.SqlClient.SqlException was unhandled by user code
  Message=Could not find stored procedure 'FetchActiveUsers'.
  Source=.Net SqlClient Data Provider
  ErrorCode=-2146232060
  Class=16
  LineNumber=1
  Number=2812
  Procedure=""
  Server=vader
  State=62
  StackTrace:
       [...snip stack trace...]
  InnerException:

我想要那个!

内容是什么:

String ExceptionToString(Exception ex)
{ 
    //todo: Write useful routine
    return ex.ToString();
}

可以实现同样的魔法。是否有内置的.NET 函数? Exception 是否有某个秘密方法可以将其转换为字符串?

【问题讨论】:

  • 你需要反思;我不知道有任何内置代码可以做到这一点。
  • 您希望将 IDE 调试器窗口详细信息发布到某个地方?或者只是自定义异常对象输出,您只需要附加几个异常对象成员。还是你的 qs 不是很清楚。
  • 请记住,异常可以包含异常。有时真正的错误隐藏在另一个异常中。因此,无论代码应该在某种循环中,例如 while (innerException != null) ,这样所有的异常都会被报告,而不仅仅是顶级的。
  • @zenwalker 我想要一个ExceptionToString 函数,它具有微软自己的ExceptionToString 在将字符串复制到剪贴板时所做的所有有用功能。。跨度>
  • 嗯...您可能已经知道这一点,但-2146232060 与异常消息中报告的0x80131904 相同。这样信息就不会丢失,只是以不同的方式表示。 (在这种情况下,十六进制表示对于大多数用途来说是更“正确”的表示。)

标签: c# .net exception tostring


【解决方案1】:

ErrorCode 特定于 ExternalException,而不是 Exception 和 LineNumber 和 Number 特定于 SqlException,而不是 Exception。因此,从Exception 上的通用扩展方法获取这些属性的唯一方法是使用反射来迭代所有公共属性。

所以你必须这样说:

public static string GetExceptionDetails(this Exception exception) {
    var properties = exception.GetType()
                            .GetProperties();
    var fields = properties
                     .Select(property => new { 
                         Name = property.Name,
                         Value = property.GetValue(exception, null)
                     })
                     .Select(x => String.Format(
                         "{0} = {1}",
                         x.Name,
                         x.Value != null ? x.Value.ToString() : String.Empty
                     ));
    return String.Join("\n", fields);
}

(未针对编译问题进行测试。)

.NET 2.0 兼容答案:

public static string GetExceptionDetails(this Exception exception) 
{
    PropertyInfo[] properties = exception.GetType()
                            .GetProperties();
    List<string> fields = new List<string>();
    foreach(PropertyInfo property in properties) {
        object value = property.GetValue(exception, null);
        fields.Add(String.Format(
                         "{0} = {1}",
                         property.Name,
                         value != null ? value.ToString() : String.Empty
        ));    
    }         
    return String.Join("\n", fields.ToArray());
}

【讨论】:

  • +1 这比我建议的必须覆盖ToString() 要好得多。我喜欢扩展方法,这似乎是一个非常好的方法,因为我们真的想扩展基本异常类的功能。
  • 反射的替代方法是我构建了一个类,它知道我认为我可能感兴趣的几乎所有类型的异常。然后我可以将Exception 转换为每种已知的异常,并输出属性列表。
  • 反思可能是唯一的答案。发布一个 .NET 2.0 兼容的答案,我会尽快接受。否则,我将不得不再次尝试反思。
  • 而不是反射,为什么不检查有问题的Exception 是否是SqlException,如果是,则转换为SqlException 并读取属性?那会快很多。 (虽然额外的属性在这种情况下很有用,但我认为您不能笼统地说它总是对每种类型的异常都有用。)
  • 你在这里忘记了一些东西。很多时候,异常都有 InnerExceptions,它们实际上几乎比你在最顶层得到的更重要。如果 InnerException 不为空,您将需要某种递归方法来深入挖掘。
【解决方案2】:

我首先尝试了 Jason 的答案(在顶部),效果很好,但我也想要:

  • 通过内部异常迭代循环并缩进它们。
  • 忽略空属性并提高输出的可读性。
  • 它在数据属性中包含元数据。 (如果有)但不包括 Data 属性本身。 (没用)。

我现在用这个:

    public static void WriteExceptionDetails(Exception exception, StringBuilder builderToFill, int level)
    {
        var indent = new string(' ', level);

        if (level > 0)
        {
            builderToFill.AppendLine(indent + "=== INNER EXCEPTION ===");                
        }

        Action<string> append = (prop) =>
            {
                var propInfo = exception.GetType().GetProperty(prop);
                var val = propInfo.GetValue(exception);

                if (val != null)
                {
                    builderToFill.AppendFormat("{0}{1}: {2}{3}", indent, prop, val.ToString(), Environment.NewLine);
                }
            };

        append("Message");
        append("HResult");
        append("HelpLink");
        append("Source");
        append("StackTrace");
        append("TargetSite");

        foreach (DictionaryEntry de in exception.Data)
        {
            builderToFill.AppendFormat("{0} {1} = {2}{3}", indent, de.Key, de.Value, Environment.NewLine);
        }

        if (exception.InnerException != null)
        {
            WriteExceptionDetails(exception.InnerException, builderToFill, ++level);
        }
    }

这样调用:

        var builder = new StringBuilder();
        WriteExceptionDetails(exception, builder, 0);
        return builder.ToString();

【讨论】:

  • 我喜欢你的解决方案@Gerben Rampaart,但它需要一些照顾。为避免异常,我将var val = propInfo.GetValue(exception); 替换为var val = propInfo!=null?propInfo.GetValue(exception, null):null; 将其更改为私有方法并用于通过扩展名调用它:` public static string ToStringAllExceptionDetails(this Exception exception) { StringBuilder builderToFill = new StringBuilder(); WriteExceptionDetails(异常,builderToFill,0);返回 builderToFill.ToString(); }`
  • 嗨@Kraken101,感谢您的扩展。随意编辑我的答案以反映您的意思。
【解决方案3】:

这个综合答案处理写出:

  1. 在所有异常中都可以找到 Data 集合属性(接受的答案不这样做)。
  2. 添加到异常的任何其他自定义属性。
  3. 递归写出InnerException(接受的答案不这样做)。
  4. 写出AggregateException 中包含的异常集合。

它还以更好的顺序写出异常的属性。它使用 C# 6.0,但如果需要,您应该很容易转换为旧版本。

public static class ExceptionExtensions
{
    public static string ToDetailedString(this Exception exception)
    {
        if (exception == null)
        {
            throw new ArgumentNullException(nameof(exception));
        }

        return ToDetailedString(exception, ExceptionOptions.Default);
    }

    public static string ToDetailedString(this Exception exception, ExceptionOptions options)
    {
        var stringBuilder = new StringBuilder();

        AppendValue(stringBuilder, "Type", exception.GetType().FullName, options);

        foreach (PropertyInfo property in exception
            .GetType()
            .GetProperties()
            .OrderByDescending(x => string.Equals(x.Name, nameof(exception.Message), StringComparison.Ordinal))
            .ThenByDescending(x => string.Equals(x.Name, nameof(exception.Source), StringComparison.Ordinal))
            .ThenBy(x => string.Equals(x.Name, nameof(exception.InnerException), StringComparison.Ordinal))
            .ThenBy(x => string.Equals(x.Name, nameof(AggregateException.InnerExceptions), StringComparison.Ordinal)))
        {
            var value = property.GetValue(exception, null);
            if (value == null && options.OmitNullProperties)
            {
                if (options.OmitNullProperties)
                {
                    continue;
                }
                else
                {
                    value = string.Empty;
                }
            }

            AppendValue(stringBuilder, property.Name, value, options);
        }

        return stringBuilder.ToString().TrimEnd('\r', '\n');
    }

    private static void AppendCollection(
        StringBuilder stringBuilder,
        string propertyName,
        IEnumerable collection,
        ExceptionOptions options)
        {
            stringBuilder.AppendLine($"{options.Indent}{propertyName} =");

            var innerOptions = new ExceptionOptions(options, options.CurrentIndentLevel + 1);

            var i = 0;
            foreach (var item in collection)
            {
                var innerPropertyName = $"[{i}]";

                if (item is Exception)
                {
                    var innerException = (Exception)item;
                    AppendException(
                        stringBuilder,
                        innerPropertyName,
                        innerException,
                        innerOptions);
                }
                else
                {
                    AppendValue(
                        stringBuilder,
                        innerPropertyName,
                        item,
                        innerOptions);
                }

                ++i;
            }
        }

    private static void AppendException(
        StringBuilder stringBuilder,
        string propertyName,
        Exception exception,
        ExceptionOptions options)
    {
        var innerExceptionString = ToDetailedString(
            exception, 
            new ExceptionOptions(options, options.CurrentIndentLevel + 1));

        stringBuilder.AppendLine($"{options.Indent}{propertyName} =");
        stringBuilder.AppendLine(innerExceptionString);
    }

    private static string IndentString(string value, ExceptionOptions options)
    {
        return value.Replace(Environment.NewLine, Environment.NewLine + options.Indent);
    }

    private static void AppendValue(
        StringBuilder stringBuilder,
        string propertyName,
        object value,
        ExceptionOptions options)
    {
        if (value is DictionaryEntry)
        {
            DictionaryEntry dictionaryEntry = (DictionaryEntry)value;
            stringBuilder.AppendLine($"{options.Indent}{propertyName} = {dictionaryEntry.Key} : {dictionaryEntry.Value}");
        }
        else if (value is Exception)
        {
            var innerException = (Exception)value;
            AppendException(
                stringBuilder,
                propertyName,
                innerException,
                options);
        }
        else if (value is IEnumerable && !(value is string))
        {
            var collection = (IEnumerable)value;
            if (collection.GetEnumerator().MoveNext())
            {
                AppendCollection(
                    stringBuilder,
                    propertyName,
                    collection,
                    options);
            }
        }
        else
        {
            stringBuilder.AppendLine($"{options.Indent}{propertyName} = {value}");
        }
    }
}

public struct ExceptionOptions
{
    public static readonly ExceptionOptions Default = new ExceptionOptions()
    {
        CurrentIndentLevel = 0,
        IndentSpaces = 4,
        OmitNullProperties = true
    };

    internal ExceptionOptions(ExceptionOptions options, int currentIndent)
    {
        this.CurrentIndentLevel = currentIndent;
        this.IndentSpaces = options.IndentSpaces;
        this.OmitNullProperties = options.OmitNullProperties;
    }

    internal string Indent { get { return new string(' ', this.IndentSpaces * this.CurrentIndentLevel); } }

    internal int CurrentIndentLevel { get; set; }

    public int IndentSpaces { get; set; }

    public bool OmitNullProperties { get; set; }
}

重要提示 - 记录异常

大多数人将使用此代码进行日志记录。考虑将Serilog 与我的Serilog.Exceptions NuGet 包一起使用,它还记录异常的所有属性,但在大多数情况下执行速度更快且没有反射。 Serilog 是一个非常先进的日志框架,在撰写本文时风靡一时。

重要提示 - 人类可读的堆栈跟踪

您可以使用Ben.Demystifier NuGet 包来获取人类可读的异常堆栈跟踪,或者如果您使用的是 Serilog,则可以使用serilog-enrichers-demystify NuGet 包。如果您使用的是 .NET Core 2.1,则此功能已内置。

【讨论】:

    【解决方案4】:

    对于不想搞乱覆盖的人来说,这种简单的非侵入性方法可能就足够了:

        public static string GetExceptionDetails(Exception exception)
        {
            return "Exception: " + exception.GetType()
                + "\r\nInnerException: " + exception.InnerException
                + "\r\nMessage: " + exception.Message
                + "\r\nStackTrace: " + exception.StackTrace;
        }
    

    它没有显示您想要的特定于 SQLException 的详细信息...

    【讨论】:

      【解决方案5】:

      没有秘诀。您可能只需覆盖 ToString() 方法并构建您想要的字符串。

      ErrorCode 和 Message 之类的内容只是异常的属性,您可以将它们添加到所需的字符串输出中。


      更新:重新阅读您的问题并对此进行更多思考后,Jason 的answer 更有可能是您想要的。覆盖 ToString() 方法只会对您创建的异常有帮助,而不是已经实现的异常。仅仅为了添加此功能而对现有异常进行子类化是没有意义的。

      【讨论】:

      • 是的,我不能真正覆盖 System.Data.SqlClient.SqlException 并让 System.Data.SqlConnection 使用它。
      【解决方案6】:

      要向用户显示一些详细信息,您应该使用ex.Message。为了向开发人员展示,您可能需要ex.Message 和ex.StackTrace。

      没有“秘密”方法,您可以认为 Message 属性最适合用户友好的消息。

      还要注意,在某些情况下,您捕获的异常中可能有内部异常,这对记录也很有用。

      【讨论】:

        【解决方案7】:

        您可能必须通过连接您感兴趣的各个字段来手动构造该字符串。

        【讨论】:

          【解决方案8】:

          每个左侧名称都是异常中的属性。如果你想显示 Message 字段,你可以这样做

          return ex.Message;
          

          很简单。同样,StackTrace 可以显示如下链接。

          StackTrace 的完整示例:http://msdn.microsoft.com/en-us/library/system.exception.stacktrace.aspx

          和异常类:http://msdn.microsoft.com/en-us/library/system.exception.aspx

          【讨论】:

            【解决方案9】:

            如果您在 Exception 对象上调用 ToString,您将获得由消息附加的类名,然后是内部异常,然后是堆栈跟踪。

            className + message + InnerException + stackTrace
            

            鉴于此,仅当 InnerException 和 StackTrace 不为 null 时才会添加它们。此外,您在屏幕截图中提到的字段不是标准异常类的一部分。是的,异常确实提供了一个名为“数据”的公共属性,其中包含有关异常的其他用户定义信息。

            【讨论】:

              【解决方案10】:

              在 Visual Studio 中,调试器可视化器可以输出此类信息。

              我假设因为可以编写自己的调试器可视化工具: http://msdn.microsoft.com/en-us/library/e2zc529c.aspx

              理论上,如果您可以对内置调试器可视化器进行逆向工程以处理异常(如果您可以计算出它们的存储位置),那么您可以使用相同的功能。

              编辑:

              这是一篇关于调试器可视化器保存位置的帖子:Where do I find Microsoft.VisualStudio.DebuggerVisualizers?

              您也许可以将其用于您自己的目的。

              【讨论】:

                【解决方案11】:

                我认为 JSON 的异常序列化是一个不错的选择。样本结果:

                {
                "Errors": [{
                        "Source": ".Net SqlClient Data Provider",
                        "Number": -1,
                        "Class": 20,
                        "Server": "111.168.222.70",
                        "Message": "A transport-level error has occurred when receiving results from the server. (provider: Session Provider, error: 19 - Physical connection is not usable)"
                    }
                ],
                "ClientConnectionId": "b1854037-51e4-4943-94b4-72b7bb4c6ab7",
                "ClassName": "System.Data.SqlClient.SqlException",
                "Message": "A transport-level error has occurred when receiving results from the server. (provider: Session Provider, error: 19 - Physical connection is not usable)",
                "Data": {
                    "HelpLink.ProdName": "Microsoft SQL Server",
                    "HelpLink.EvtSrc": "MSSQLServer",
                    "HelpLink.EvtID": "-1",
                    "HelpLink.BaseHelpUrl": "http://go.microsoft.com/fwlink",
                    "HelpLink.LinkId": "20476"
                },
                "InnerException": null,
                "HelpURL": null,
                "StackTraceString": "   at System.Data.SqlClient.SqlConnection.OnError ... DbExecutionStrategy.Execute[TResult](Func`1 operation)",
                "RemoteStackTraceString": null,
                "RemoteStackIndex": 0,
                "ExceptionMethod": "8\nOnError\nSystem.Data, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089\nSystem.Data.SqlClient.SqlConnection\nVoid OnError(System.Data.SqlClient.SqlException, Boolean, System.Action`1[System.Action])",
                "HResult": -2146232060,
                "Source": ".Net SqlClient Data Provider",
                "WatsonBuckets": null
                }
                

                【讨论】:

                  猜你喜欢
                  • 2016-12-26
                  • 2011-12-16
                  • 2011-03-25
                  • 2010-12-15
                  • 2016-10-07
                  • 1970-01-01
                  • 2015-03-22
                  • 1970-01-01
                  • 1970-01-01
                  相关资源
                  最近更新 更多