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