Exception.Message vs Exception.ToString ()

207

Ich habe Code, der protokolliert Exception.Message. Ich habe jedoch einen Artikel gelesen, der besagt, dass es besser ist, ihn zu verwenden Exception.ToString(). Mit letzterem behalten Sie wichtigere Informationen über den Fehler.

Ist dies wahr und ist es sicher, die gesamte Codeprotokollierung zu ersetzen Exception.Message?

Ich verwende auch ein XML-basiertes Layout für log4net . Ist es möglich, dass Exception.ToString()ungültige XML-Zeichen enthalten sind, was zu Problemen führen kann?

J L.
quelle
1
Sie sollten sich auch ELMAH ( code.google.com/p/elmah ) ansehen - Ein sehr benutzerfreundliches Framework für die Fehlerprotokollierung für ASP.NET.
Ashish Gupta

Antworten:

278

Exception.Messageenthält nur die der Ausnahme zugeordnete Nachricht (doh). Beispiel:

Der Objektverweis wurde nicht auf eine Instanz eines Objekts festgelegt

Die Exception.ToString()Methode liefert eine viel ausführlichere Ausgabe, die den Ausnahmetyp, die Nachricht (von zuvor), eine Stapelverfolgung und all diese Dinge erneut für verschachtelte / innere Ausnahmen enthält. Genauer gesagt gibt die Methode Folgendes zurück:

ToString gibt eine Darstellung der aktuellen Ausnahme zurück, die von Menschen verstanden werden soll. Wenn die Ausnahme kultursensitive Daten enthält, muss die von ToString zurückgegebene Zeichenfolgendarstellung die aktuelle Systemkultur berücksichtigen. Obwohl es keine genauen Anforderungen für das Format der zurückgegebenen Zeichenfolge gibt, sollte versucht werden, den vom Benutzer wahrgenommenen Wert des Objekts wiederzugeben.

Die Standardimplementierung von ToString erhält den Namen der Klasse, die die aktuelle Ausnahme ausgelöst hat, die Nachricht, das Ergebnis des Aufrufs von ToString für die innere Ausnahme und das Ergebnis des Aufrufs von Environment.StackTrace. Wenn eines dieser Elemente eine Nullreferenz ist (Nothing in Visual Basic), ist sein Wert nicht in der zurückgegebenen Zeichenfolge enthalten.

Wenn keine Fehlermeldung vorliegt oder wenn es sich um eine leere Zeichenfolge ("") handelt, wird keine Fehlermeldung zurückgegeben. Der Name der inneren Ausnahme und die Stapelverfolgung werden nur zurückgegeben, wenn sie keine Nullreferenz sind (Nothing in Visual Basic).

Jørn Schou-Rode
quelle
85
+1 Es ist sehr schmerzhaft, NUR in den Protokollen zu sehen, dass "Objektreferenz nicht auf eine Instanz eines Objekts festgelegt ist". Du fühlst dich wirklich hilflos. :-)
Ashish Gupta
1
Für den letzten Teil gibt es Ausnahmen, die nicht mit Exception.Message geliefert werden. In Abhängigkeit von dem, was Sie im Teil zur Fehlerbehandlung tun, können aufgrund der Exception.Message Probleme auftreten.
Coral Doe
49
Es ist sehr schmerzhaft zu sehen, dass ich Code geschrieben habe, der im Wesentlichen genau dasselbe tut wie ToString ().
Preston McCormick
1
@KunalGoel Wenn das Protokoll von prod stammt und Sie keinen Hinweis auf die Eingabe haben, können Sie nicht einfach "durch Aktivieren der CLR-Ausnahme debuggen".
jpmc26
1
Beachten Sie, dass es sich um die "Standardimplementierung von ToString" handelt ... (Betonung auf "Standard"). Dies bedeutet nicht, dass jeder diese Vorgehensweise mit benutzerdefinierten Ausnahmen befolgt hat. #learnedTheHardWay
GranadaCoder
52

Verwenden Sie das Ausnahmeobjekt nicht zusätzlich zu dem, was bereits gesagt wurde, ToString()für die Anzeige für den Benutzer. Nur die MessageEigenschaft sollte ausreichen oder eine übergeordnete benutzerdefinierte Nachricht.

Verwenden Sie in Bezug auf Protokollierungszwecke auf jeden Fall ToString()die Ausnahme und nicht nur die MessageEigenschaft, da Sie sich in den meisten Szenarien am Kopf kratzen müssen, wo genau diese Ausnahme aufgetreten ist und wie der Aufrufstapel war. Die Stapelspur hätte dir das alles gesagt.

Wim Hollebrandse
quelle
Wenn Sie ToString () in Protokollen verwenden, stellen Sie sicher, dass keine vertraulichen Informationen in ToString
Michael Freidgeim
22

Konvertieren der GANZEN Ausnahme in einen String

Wenn Exception.ToString()Sie anrufen, erhalten Sie mehr Informationen als nur die Nutzung der Exception.MessageEigenschaft. Selbst dies lässt jedoch noch viele Informationen aus, darunter:

  1. Die DataSammlungseigenschaft, die in allen Ausnahmen gefunden wurde.
  2. Alle anderen benutzerdefinierten Eigenschaften, die der Ausnahme hinzugefügt wurden.

Es gibt Zeiten, in denen Sie diese zusätzlichen Informationen erfassen möchten. Der folgende Code behandelt die oben genannten Szenarien. Außerdem werden die Eigenschaften der Ausnahmen in einer schönen Reihenfolge geschrieben. Es verwendet C # 7, sollte aber für Sie sehr einfach sein, bei Bedarf auf ältere Versionen zu konvertieren. Siehe auch diese verwandte Antwort.

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

Top Tipp - Protokollierungsausnahmen

Die meisten Benutzer verwenden diesen Code für die Protokollierung. Erwägen Sie die Verwendung von Serilog mit meinem Serilog.Exceptions NuGet-Paket, das auch alle Eigenschaften einer Ausnahme protokolliert, dies jedoch in den meisten Fällen schneller und ohne Reflexion tut. Serilog ist ein sehr fortschrittliches Protokollierungsframework, das zum Zeitpunkt des Schreibens der letzte Schrei ist.

Top Tipp - Vom Menschen lesbare Stapelspuren

Sie können das Ben.Demystifier NuGet-Paket verwenden, um lesbare Stapelspuren für Ihre Ausnahmen abzurufen, oder das Serilog-Anreicherungs-entmystifizieren NuGet-Paket, wenn Sie Serilog verwenden.

Muhammad Rehan Saeed
quelle
9

Ich würde sagen, @Wim ist richtig. Sie sollten sie ToString()für Protokolldateien verwenden - unter der Annahme einer technischen Zielgruppe - und Messagewenn überhaupt, um sie dem Benutzer anzuzeigen. Man könnte argumentieren, dass selbst das nicht für einen Benutzer geeignet ist, für jeden Ausnahmetyp und jedes Vorkommen da draußen (denken Sie an ArgumentExceptions usw.).

Enthält zusätzlich zu StackTrace ToString()auch Informationen, die Sie sonst nicht erhalten. Zum Beispiel die Ausgabe von Fusion, wenn diese Option aktiviert ist , um Protokollnachrichten in die Ausnahme "Nachrichten" aufzunehmen.

Einige Ausnahmetypen enthalten sogar zusätzliche Informationen (z. B. aus benutzerdefinierten Eigenschaften) in ToString(), jedoch nicht in der Nachricht.

Christian.K
quelle
8

Hängt von den Informationen ab, die Sie benötigen. Zum Debuggen sind der Stack-Trace und die innere Ausnahme nützlich:

    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";
    }
Carra
quelle
12
Das ist mehr oder weniger das, was Exception.ToString()dir geben wird, oder?
Jørn Schou-Rode
5
@Matt: Das Erstellen einer Instanz von StringBuilderin diesem Szenario ist möglicherweise teurer als zwei neue Zeichenfolgenzuweisungen. Es ist höchst umstritten, dass es hier effizienter wäre. Es ist nicht so, als hätten wir es mit Iterationen zu tun. Pferde für Kurse.
Wim Hollebrandse
2
Das Problem hierbei ist, dass Sie nur die "InnerException" der äußersten Ausnahme erhalten. IOW: Wenn für InnerException selbst eine InnerException festgelegt ist, wird diese nicht ausgegeben (vorausgesetzt, Sie möchten dies zunächst tun). Ich würde wirklich bei ToString () bleiben.
Christian.K
6
Verwenden Sie einfach ex.ToString. Sie erhalten alle Details.
John Saunders
3
@Christian: Der Compiler ist vernünftig mit mehreren + s. Siehe zum Beispiel "Der Operator + ist einfach zu verwenden und sorgt für intuitiven Code. Selbst wenn Sie mehrere Operatoren + in einer Anweisung verwenden, wird der Zeichenfolgeninhalt nur einmal kopiert." von msdn.microsoft.com/en-us/library/ms228504.aspx
David Eison
3

In Bezug auf das XML-Format für log4net müssen Sie sich keine Gedanken über ex.ToString () für die Protokolle machen. Übergeben Sie einfach das Ausnahmeobjekt selbst und log4net erledigt den Rest, um Ihnen alle Details in seinem vorkonfigurierten XML-Format zu geben. Das einzige, was mir gelegentlich begegnet, ist die Formatierung neuer Zeilen, aber dann lese ich die Dateien roh. Ansonsten funktioniert das Parsen von XML hervorragend.

Dillie-O
quelle
0

Nun, ich würde sagen, es hängt davon ab, was Sie in den Protokollen sehen möchten, nicht wahr? Wenn Sie mit den Angeboten von ex.Message zufrieden sind, verwenden Sie diese. Verwenden Sie andernfalls ex.toString () oder protokollieren Sie sogar den Stack-Trace.

Thorsten Dittmar
quelle
5
ex.ToString enthält die Stapelverfolgung
John Saunders