Exception.Message 与 Exception.ToString()

Posted

技术标签:

【中文标题】Exception.Message 与 Exception.ToString()【英文标题】:Exception.Message vs Exception.ToString() 【发布时间】:2011-01-11 17:06:54 【问题描述】:

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

这是真的吗,继续替换所有代码日志记录是否安全Exception.Message

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

【问题讨论】:

您还应该查看 ELMAH (code.google.com/p/elmah) - 一个非常易于使用的 ASP.NET 错误日志记录框架。 【参考方案1】:

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

对象引用未设置为对象的实例

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

ToString 返回旨在被人类理解的当前异常的表示。如果异常包含文化敏感数据,则 ToString 返回的字符串表示需要考虑当前系统文化。虽然对返回字符串的格式没有确切的要求,但它应该尽量反映用户感知到的对象的值。

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

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

【讨论】:

+1 在日志中只看到“对象引用未设置为对象的实例”非常痛苦。你真的感到很无助。 :-) 对于最后一部分,有 Exception.Message 不附带的异常。根据您在错误处理部分所做的工作,您可能会因为 Exception.Message 而遇到问题。 看到我写的代码基本上和 ToString() 做的事情完全一样,这让我很痛苦。 @KunalGoel 如果日志来自 prod 并且您没有指示输入是什么,那么不,您不能只是“通过打开 CLR 异常进行调试”。 注意,它是“ToString 的默认实现”……(强调“默认”)……这并不意味着每个人都遵循这种做法,但有任何自定义例外。 #learnedTheHardWay【参考方案2】:

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

【讨论】:

ex.ToString 包括堆栈跟踪【参考方案3】:

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

    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";
    

【讨论】:

这或多或少是Exception.ToString() 会给你的,对吧? @Matt:在这种情况下构造StringBuilder 的实例可能比两个新的字符串分配更昂贵,这是非常有争议的,在这里它会更有效。这不像我们正在处理迭代。课程用马。 这里的问题是,你只会得到最外层异常的“InnerException”。 IOW,如果 InnerException 本身有一个 InnerException 集,您将不会转储它(假设您首先要转储)。我真的会坚持使用 ToString()。 只需使用 ex.ToString。它可以为您提供所有详细信息。 @Christian:编译器很正常,有多个 +s。例如,请参阅“+ 运算符易于使用且代码直观。即使您在一个语句中使用多个 + 运算符,字符串内容也只会复制一次。”来自msdn.microsoft.com/en-us/library/ms228504.aspx【参考方案4】:

除了已经说过的,不要在异常对象上使用ToString() 来向用户显示。只需 Message 属性就足够了,或者更高级别的自定义消息。

就记录目的而言,一定要在异常上使用ToString(),而不仅仅是Message 属性,因为在大多数情况下,您会摸不着头脑,具体发生此异常的位置以及调用堆栈是什么.堆栈跟踪会告诉你所有这些。

【讨论】:

如果您在日志中使用 ToString(),请确保不要在 ToString 中包含敏感信息【参考方案5】:

我会说@Wim 是对的。您应该将ToString() 用于日志文件(假设是技术受众)和Message(如果有的话)显示给用户。有人可能会争辩说,即使这不适合用户,也不适合所有异常类型和发生(想想 ArgumentExceptions 等)。

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

某些异常类型甚至在 ToString() 中包含附加信息(例如来自自定义属性),但在消息中不包含。

【讨论】:

【参考方案6】:

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

【讨论】:

【参考方案7】:

将整个异常转换为字符串

调用Exception.ToString() 可以为您提供比仅使用Exception.Message 属性更多的信息。然而,即使这样仍然遗漏了很多信息,包括:

    在所有异常中都可以找到 Data 集合属性。 添加到异常的任何其他自定义属性。

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

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.IndentpropertyName =");

            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.IndentpropertyName =");
        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.IndentpropertyName = 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.IndentpropertyName = 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 包。

【讨论】:

【参考方案8】:

理想情况下,最好序列化整个异常对象而不是 .ToString()。这将封装整个异常对象(所有内部异常、消息、堆栈跟踪、数据、键等)。

您可以确保没有遗漏任何内容。此外,您还拥有可以在任何应用程序中使用的通用格式的对象。

    public static void LogError(Exception exception, int userId)
    
        LogToDB(Newtonsoft.Json.JsonConvert.SerializeObject(exception), userId);
    

【讨论】:

以上是关于Exception.Message 与 Exception.ToString()的主要内容,如果未能解决你的问题,请参考以下文章

消息字符串为空时如何隐藏 div? (特别是:SPRING_SECURITY_LAST_EXCEPTION.message)

Unity C# Firebase:如果“task.Exception.Message”返回“发生了一个或多个错误”并且没有返回数据,我该怎么办?

Better exception message for missing @RequestBody method parameter

急:win7使用Matlab2010出现错误!Caught std::exception Exception message is:

Elasticsearch Exception:Message not fully read (response) for requestId

freemarker.core.InvalidReferenceException: [... Exception message was already printed; see it above