Exception.Message vs Exception.ToString()

时间:2010-02-01 12:53:14

标签: c# .net exception exception-handling

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

这是真的,并且可以安全地替换所有代码记录Exception.Message吗?

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

8 个答案:

答案 0 :(得分:243)

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

  

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

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

  

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

     

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

     

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

答案 1 :(得分:48)

除了已经说过的内容之外,在异常对象上使用ToString()来显示给用户。只需Message属性即可,或更高级别的自定义消息。

就日志记录而言,肯定在异常上使用ToString(),而不仅仅是Message属性,就像在大多数情况下一样,在特定的异常发生的地方,你会留下一个问题,以及调用堆栈是。堆栈跟踪会告诉你所有这些。

答案 2 :(得分:15)

正如@JornSchouRode所指出的,做exception.ToString()提供的信息不仅仅是使用exception.Message属性。但是,即便如此,仍然会留下大量信息,包括:

  1. 在所有例外情况下找到Data集合属性。
  2. 添加到例外的任何其他自定义属性。
  3. 有时您想要捕获这些额外信息。下面的代码处理上述场景。它还以良好的顺序写出异常的属性。它使用的是C#6.0,但如果需要,您应该很容易转换为旧版本。另请参阅this相关答案。

    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是一个非常先进的日志框架,在撰写本文时风靡一时。

    最高提示 - 人类可读堆栈跟踪

    如果使用Serilog,您可以使用Ben.Demystifier NuGet包来获取异常的人类可读堆栈跟踪或serilog-enrichers-demystify NuGet包。

答案 3 :(得分:9)

我会说@Wim是对的。您应该使用ToString()作为日志文件 - 假设是技术受众 - 而Message(如果有的话)则显示给用户。有人可能会争辩说,即使这样也不适合用户,因为每个异常类型和出现(想想ArgumentExceptions等)。

此外,除了StackTrace之外,ToString()还将包含您不会获得的信息。例如,Fusion的输出,如果enabled包含异常“消息”中的日志消息。

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

答案 4 :(得分: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";
    }

答案 5 :(得分:3)

就log4net的XML格式而言,您无需担心日志的ex.ToString()。只需传递异常对象本身,log4net就会以其预先配置的XML格式为您提供所有细节。我偶尔遇到的唯一问题是新行格式化,但那是我正在读取原始文件的时候。否则解析XML效果很好。

答案 6 :(得分:1)

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

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

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

答案 7 :(得分:0)

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