例外。Message vs . Exception.ToString()

我有代码记录Exception.Message。然而,我读过一篇文章,指出最好使用Exception.ToString()。对于后者,您可以保留关于错误的更重要的信息。

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

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

153908 次浏览

Exception.Message只包含与异常相关的消息(doh)。例子:

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

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

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

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

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

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

这取决于你需要的信息。用于调试堆栈跟踪&内部异常是有用的:

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

除了上面所说的,在异常对象上使用ToString()来显示给用户。只要Message属性就足够了,或者一个更高级别的自定义消息。

就日志记录的目的而言,一定要在Exception上使用ToString(),而不仅仅是Message属性,因为在大多数情况下,你会摸不着头脑这个异常具体发生在哪里,调用堆栈是什么。堆栈轨迹会告诉你所有这些。

我想说@Wim是对的。你应该为日志文件使用ToString()——假设是技术观众——如果有的话,使用Message来显示给用户。有人可能会说,即使这样也不适合用户,因为存在各种异常类型和出现的情况(比如ArgumentExceptions等)。

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

一些异常类型甚至在ToString()中包含额外的信息(例如来自自定义属性),但不在Message中。

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

将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。异常 NuGet包一起使用,它也会记录异常的所有属性,但在大多数情况下它会更快,并且没有反射。Serilog是一个非常先进的日志记录框架,在撰写本文时非常流行。

提示-人类可读的堆栈跟踪

你可以使用本。阐明 NuGet包为你的异常获取人类可读的堆栈跟踪,如果你使用Serilog,也可以使用serilog-enrichers-demystify NuGet包。

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

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

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