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

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

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


当前回答

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

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

其他回答

将WHOLE异常转换为字符串

调用Exception. tostring()会提供比仅使用Exception更多的信息。消息属性。然而,即使这样,仍然遗漏了很多信息,包括:

在所有异常上找到的“数据收集”属性。 添加到异常中的任何其他自定义属性。

有时,您希望捕获这些额外的信息。下面的代码处理上述场景。它还以良好的顺序写出异常的属性。它使用的是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。exception NuGet包,它也记录异常的所有属性,但在大多数情况下它更快且没有反射。Serilog是一个非常先进的日志记录框架,在撰写本文时非常流行。

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

你可以用本。如果您正在使用Serilog,则可以使用Serilog -enrichers-demystify NuGet包来获取人类可读的异常堆栈跟踪。

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

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

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

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

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

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

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

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

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