Exception.Message与Exception.ToString()


207

我有正在记录的代码Exception.Message。但是,我读了一篇文章,指出最好使用Exception.ToString()。使用后者,您可以保留有关该错误的更多关键信息。

这是真的,继续所有代码记录Exception.Message是否安全?

我还在log4net上使用基于XML的布局。是否Exception.ToString()可能包含无效的XML字符,这可能会引起问题?


1
您还应该查看ELMAH(code.google.com/p/elmah)-一种非常易于使用的ASP.NET错误记录框架。
Ashish Gupta

Answers:


278

Exception.Message仅包含与异常关联的消息(doh)。例:

你调用的对象是空的

Exception.ToString()方法将给出更详细的输出,其中包含异常类型,消息(来自之前),堆栈跟踪以及所有这些嵌套/内部异常的东西。更准确地说,该方法返回以下内容:

ToString返回人类希望理解的当前异常的表示形式。如果异常包含对区域性敏感的数据,则ToString返回的字符串表示形式需要考虑当前的系统区域性。尽管对返回的字符串的格式没有确切的要求,但它应尝试反映用户所感知的对象的值。

ToString的默认实现获取引发当前异常的类的名称,消息,在内部异常上调用ToString的结果以及调用Environment.StackTrace的结果。如果这些成员中的任何一个为空引用(Visual Basic中为Nothing),则其值不包含在返回的字符串中。

如果没有错误消息,或者它是一个空字符串(“”),则不会返回任何错误消息。仅当内部异常的名称和堆栈跟踪不是空引用时才返回(Visual Basic中为Nothing)。


85
+1很难看到日志中只有“对象引用未设置为对象的实例”。你真的很无助。:-)
Ashish Gupta

1
最后一部分是Exception.Message不附带的异常。在错误处理部分中执行的操作中,由于Exception.Message,您可能会遇到问题。
珊瑚母鹿

49
看到我编写的代码实际上执行与ToString()完全相同的操作,这是非常痛苦的。
Preston McCormick

1
@KunalGoel如果日志来自prod,并且您没有指示输入的内容,那么不能,您不能只是“通过打开CLR异常进行调试”。
jpmc26

1
请注意,这是“ ToString的默认实现” ...(强调“默认”)。.并不意味着每个人都遵循该惯例并带有任何自定义异常。#learnedTheHardWay
granadaCoder 18'Apr

52

除了什么已经说过,不要使用ToString()用于显示给用户的例外对象。仅该Message属性就足够了,或者是更高级别的自定义消息。

就日志记录目的而言,一定ToString()要在Exception上使用它,而不仅仅是Message在大多数情况下使用该属性,就像在大多数情况下一样,您将不知所措,具体是发生此异常的位置以及调用堆栈是什么。stacktrace会告诉您所有这些。


如果您在日志中使用ToString(),请确保在ToString中不包含敏感信息
Michael Freidgeim,

22

将WHOLE异常转换为字符串

调用不仅Exception.ToString()为您提供了更多信息,还提供了更多信息Exception.Message。但是,即使如此,仍然遗漏了大量信息,包括:

  1. Data集合属性上的所有异常发现。
  2. 任何其他自定义属性添加到异常。

有时您想捕获这些额外的信息。下面的代码处理上述情况。它还以良好的顺序写出异常的属性。它使用的是C#7,但是如果需要的话,应该很容易转换为旧版本。另请参阅相关答案。

public static class ExceptionExtensions
{
    public static string ToDetailedString(this Exception exception) =>
        ToDetailedString(exception, ExceptionOptions.Default);

    public static string ToDetailedString(this Exception exception, ExceptionOptions options)
    {
        if (exception == null)
        {
            throw new ArgumentNullException(nameof(exception));
        } 

        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-enrichers-demystify NuGet程序包(如果您使用的是Serilog)。


9

我会说@Wim是正确的。您应该使用ToString()日志文件(假定是技术人员),并使用Message(如果有的话)显示给用户。有人可能会争辩说,对于每种异常类型和那里发生的情况,即使这样也不适合用户(例如ArgumentExceptions等)。

此外,除了StackTrace外,ToString()还将包含您将无法获得的其他信息。例如,融合的输出(如果启用了将日志消息包括在异常“消息”中)。

某些异常类型甚至在中包含其他信息(例如,来自自定义属性的信息)ToString(),而在消息中则没有。


8

取决于您需要的信息。对于调试堆栈跟踪和内部异常很有用:

    string message =
        "Exception type " + ex.GetType() + Environment.NewLine +
        "Exception message: " + ex.Message + Environment.NewLine +
        "Stack trace: " + ex.StackTrace + Environment.NewLine;
    if (ex.InnerException != null)
    {
        message += "---BEGIN InnerException--- " + Environment.NewLine +
                   "Exception type " + ex.InnerException.GetType() + Environment.NewLine +
                   "Exception message: " + ex.InnerException.Message + Environment.NewLine +
                   "Stack trace: " + ex.InnerException.StackTrace + Environment.NewLine +
                   "---END Inner Exception";
    }

12
这或多或少Exception.ToString()会给您带来什么,对吧?
约恩休乌-罗德

5
@Matt:StringBuilder在这种情况下构造一个实例可能比两个新的字符串分配要昂贵,这很有争议,在这里效率更高。并不是说我们要处理迭代。马匹的课程。
Wim Hollebrandse'2

2
这里的问题是,您只会得到最外面的异常的“ InnerException”。IOW,如果InnerException本身具有InnerException设置,则不会转储它(假设首先要保存)。我真的会坚持使用ToString()。
Christian.K,2010年

6
只需使用ex.ToString。它为您提供了所有详细信息。
约翰·桑德斯

3
@克里斯蒂安:编译器是明智的,有多个+。例如,请参见“ +运算符易于使用,并且使代码直观。即使在一个语句中使用多个+运算符,字符串内容也只会被复制一次。” 从msdn.microsoft.com/en-us/library/ms228504.aspx
David Eison 2012年

3

就log4net的XML格式而言,您不必担心日志的ex.ToString()。只需传递异常对象本身,log4net就会以其预配置的XML格式为您提供所有详细信息。偶尔我碰到的唯一事情就是换行格式,但是那是在我读取原始文件时。否则,解析XML效果很好。


0

好吧,我想说这取决于您想在日志中看到的内容,不是吗?如果您对ex.Message提供的功能感到满意,请使用它。否则,请使用ex.toString()甚至记录堆栈跟踪。


5
ex.ToString包括堆栈跟踪
约翰·桑德斯
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.