Exception.Message vs Exception.ToString ()

207

J'ai du code qui se connecte Exception.Message. Cependant, j'ai lu un article qui déclare qu'il vaut mieux l'utiliser Exception.ToString(). Avec ce dernier, vous conservez des informations plus cruciales sur l'erreur.

Est-ce vrai et est-il sûr de continuer et de remplacer tous les enregistrements de code Exception.Message?

J'utilise également une disposition basée sur XML pour log4net . Est-il possible qu'il Exception.ToString()puisse contenir des caractères XML non valides, ce qui peut provoquer des problèmes?

JL.
la source
1
Vous devriez également consulter ELMAH ( code.google.com/p/elmah ) - Un cadre très facile à utiliser pour la journalisation des erreurs pour ASP.NET.
Ashish Gupta

Réponses:

278

Exception.Messagecontient uniquement le message (doh) associé à l'exception. Exemple:

La référence d'objet n'est pas définie à une instance d'un objet

La Exception.ToString()méthode donnera une sortie beaucoup plus détaillée, contenant le type d'exception, le message (d'avant), une trace de pile et toutes ces choses à nouveau pour les exceptions imbriquées / internes. Plus précisément, la méthode renvoie les éléments suivants:

ToString renvoie une représentation de l'exception actuelle destinée à être comprise par les humains. Lorsque l'exception contient des données sensibles à la culture, la représentation sous forme de chaîne renvoyée par ToString est requise pour prendre en compte la culture système actuelle. Bien qu'il n'y ait aucune exigence exacte pour le format de la chaîne retournée, il doit essayer de refléter la valeur de l'objet telle que perçue par l'utilisateur.

L'implémentation par défaut de ToString obtient le nom de la classe qui a levé l'exception actuelle, le message, le résultat de l'appel à ToString sur l'exception interne et le résultat de l'appel à Environment.StackTrace. Si l'un de ces membres est une référence null (Nothing en Visual Basic), sa valeur n'est pas incluse dans la chaîne renvoyée.

S'il n'y a pas de message d'erreur ou s'il s'agit d'une chaîne vide (""), aucun message d'erreur n'est renvoyé. Le nom de l'exception interne et la trace de pile ne sont renvoyés que s'ils ne sont pas une référence nulle (Nothing en Visual Basic).

Jørn Schou-Rode
la source
86
+1 C'est très pénible de voir UNIQUEMENT que "Référence d'objet non définie sur une instance d'un objet" dans les journaux. Vous vous sentez vraiment impuissant. :-)
Ashish Gupta
1
Pour la dernière partie, il y a des exceptions qui ne viennent pas avec Exception.Message. En fonction de ce que vous faites dans la partie de gestion des erreurs, vous pouvez rencontrer des problèmes à cause du Exception.Message.
Coral Doe
50
Il est très douloureux de voir que j'ai écrit du code qui fait essentiellement exactement la même chose que ToString ().
Preston McCormick
1
@KunalGoel Si le journal provient de prod et que vous n'avez aucune indication de ce qu'était l'entrée, alors non, vous ne pouvez pas simplement "déboguer en activant l'exception CLR".
jpmc26
1
Remarque, c'est "l'implémentation par défaut de ToString" ... (accent sur "par défaut") .. cela ne signifie pas que tout le monde a suivi cette pratique avec des exceptions personnalisées. #learnedTheHardWay
granadaCoder
52

En plus de ce qui a déjà été dit, ne l' utilisez pasToString() sur l'objet d'exception pour l'afficher à l'utilisateur. Seule la Messagepropriété devrait suffire, ou un message personnalisé de niveau supérieur.

En termes de journalisation, utilisez certainement ToString() l'exception, pas seulement la Messagepropriété, comme dans la plupart des scénarios, vous vous laisserez gratter la tête là où cette exception s'est produite et quelle était la pile d'appels. Le stacktrace vous aurait dit tout cela.

Wim Hollebrandse
la source
Si vous utilisez ToString () dans les journaux, assurez-vous de ne pas inclure les informations sensibles dans ToString
Michael Freidgeim
22

Conversion de l'intégralité de l'exception en chaîne

L'appel Exception.ToString()vous donne plus d'informations que l'utilisation de la Exception.Messagepropriété. Cependant, même cela laisse de côté de nombreuses informations, notamment:

  1. le Data propriété de collection trouvée sur toutes les exceptions.
  2. Toutes les autres propriétés personnalisées ajoutées à l'exception.

Il y a des moments où vous souhaitez capturer ces informations supplémentaires. Le code ci-dessous gère les scénarios ci-dessus. Il écrit également les propriétés des exceptions dans un bel ordre. Il utilise C # 7 mais devrait être très facile pour vous de convertir vers des versions plus anciennes si nécessaire. Voir également cette réponse connexe.

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

Conseil supérieur - Journalisation des exceptions

La plupart des gens utiliseront ce code pour se connecter. Envisagez d'utiliser Serilog avec mon Serilog. Serilog.Exceptions qui enregistre également toutes les propriétés d'une exception mais le fait plus rapidement et sans réflexion dans la majorité des cas. Serilog est un cadre de journalisation très avancé qui fait fureur au moment de la rédaction.

Astuce - Traces de pile lisibles par l'homme

Vous pouvez utiliser le package Ben.Demystifier NuGet pour obtenir des traces de pile lisibles par l'homme pour vos exceptions ou le package NuGet serilog-enrichers- demystify si vous utilisez Serilog.

Muhammad Rehan Saeed
la source
9

Je dirais que @Wim a raison. Vous devez utiliser ToString()pour les fichiers journaux - en supposant une audience technique - et Message, le cas échéant, pour afficher à l'utilisateur. On pourrait soutenir que même cela ne convient pas à un utilisateur, pour chaque type d'exception et occurrence (pensez à ArgumentExceptions, etc.).

En outre, en plus de StackTrace, ToString()inclura des informations que vous n'obtiendrez pas autrement. Par exemple, la sortie de fusion, si elle est activée pour inclure les messages de journal dans les "messages" d'exception.

Certains types d'exceptions incluent même des informations supplémentaires (par exemple des propriétés personnalisées) dans ToString(), mais pas dans le message.

Christian.K
la source
8

Cela dépend des informations dont vous avez besoin. Pour le débogage, la trace de pile et l'exception interne sont utiles:

    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
la source
12
C'est plus ou moins ce Exception.ToString()qui vous donnera, non?
Jørn Schou-Rode
5
@Matt: Construire une instance de StringBuilderdans ce scénario pourrait bien être plus cher que deux nouvelles allocations de chaînes, c'est très discutable, ce serait plus efficace ici. Ce n'est pas comme s'il s'agissait d'itérations. Chevaux de course.
Wim Hollebrandse
2
Le problème ici est que vous n'obtiendrez que l '"InnerException" de l'exception la plus externe. IOW, si InnerException lui-même a un ensemble InnerException, vous ne le viderez pas (en supposant que vous le vouliez en premier lieu). Je resterais vraiment avec ToString ().
Christian.K
6
Utilisez simplement ex.ToString. Il vous donne tous les détails.
John Saunders
3
@Christian: Le compilateur est sain d'esprit avec plusieurs + s. Voir par exemple "L'opérateur + est facile à utiliser et rend le code intuitif. Même si vous utilisez plusieurs opérateurs + dans une seule instruction, le contenu de la chaîne n'est copié qu'une seule fois." de msdn.microsoft.com/en-us/library/ms228504.aspx
David Eison
3

En termes de format XML pour log4net, vous n'avez pas à vous soucier de ex.ToString () pour les journaux. Passez simplement l'objet d'exception lui-même et log4net fait le reste vous donne tous les détails dans son format XML préconfiguré. La seule chose que je rencontre à l'occasion est le nouveau formatage des lignes, mais c'est à ce moment que je lis les fichiers bruts. Sinon, l'analyse du XML fonctionne très bien.

Dillie-O
la source
0

Eh bien, je dirais que cela dépend de ce que vous voulez voir dans les journaux, n'est-ce pas? Si vous êtes satisfait de ce que propose ex.Message, utilisez-le. Sinon, utilisez ex.toString () ou même enregistrez la trace de la pile.

Thorsten Dittmar
la source
6
ex.ToString inclut la trace de la pile
John Saunders