Exception.Message与Exception.ToString()

问题描述 投票:188回答:7

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

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

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

c# .net exception exception-handling
7个回答
254
投票

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

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

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

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

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

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


49
投票

除了已经说过的话,不要在异常对象上使用Exception.ToString()显示给用户。仅Exception.ToString()属性就足够了,或者是更高级别的自定义消息。

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


20
投票

将整个异常转换为字符串

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

  1. 在所有异常上均找到exception.ToString()集合属性。
  2. 添加到该例外的任何其他自定义属性。

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

Data

最重要的提示-记录例外

大多数人将使用此代码进行日志记录。考虑将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.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; } } NuGet程序包一起使用,该程序包还记录异常的所有属性,但在大多数情况下,它的执行速度更快且没有反射。 Serilog是一个非常高级的日志记录框架,在撰写本文时风靡一时。

最重要的提示-人类可读的堆栈轨迹

您可以使用Serilog NuGet包来获取人类可读的异常堆栈跟踪,如果使用Serilog,则可以使用Serilog.Exceptions NuGet包。


9
投票

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

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

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


8
投票

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

enabled

3
投票

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


0
投票

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

© www.soinside.com 2019 - 2024. All rights reserved.