web-dev-qa-db-fra.com

Exception.Message vs Exception.ToString ()

J'ai un code qui enregistre Exception.Message. Cependant, j'ai lu un article qui dit qu'il est préférable d'utiliser Exception.ToString(). Avec ce dernier, vous conservez des informations plus cruciales sur l'erreur.

Est-ce vrai et est-il prudent de remplacer tous les enregistrements de code Exception.Message?

J'utilise également une présentation basée sur XML pour log4net . Est-il possible que Exception.ToString() puisse contenir des caractères XML non valides, susceptibles de générer des problèmes?

183
JL.

Exception.Message contient 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 méthode Exception.ToString() 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 encore pour imbriqué/interne exceptions. Plus précisément, la méthode retourne ce qui suit:

ToString retourne une représentation de l'exception en cours 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 nécessaire pour prendre en compte la culture système actuelle. Bien qu'il n'y ait pas d'exigences précises pour le format de la chaîne renvoyée, il convient de tenter de refléter la valeur de l'objet telle qu'elle est perçue par l'utilisateur.

L'implémentation par défaut de ToString obtient le nom de la classe qui a levé l'exception en cours, le message, le résultat de l'appel de ToString sur l'exception interne et le résultat de l'appel de 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 sont renvoyés uniquement s'ils ne constituent pas une référence null (Nothing en Visual Basic).

248
Jørn Schou-Rode

En plus de ce qui a déjà été dit, ne pas utiliser ToString() sur l'objet exception pour l'affichage à l'utilisateur. Seule la propriété Message devrait suffire, ou un message personnalisé de niveau supérieur.

Pour ce qui est de la journalisation, utilisez certainement ToString() sur l'exception, et pas seulement la propriété Message, comme dans la plupart des scénarios, il vous restera à vous gratter la tête lorsque cette exception s'est produite, et quoi la pile d'appels était. Le stacktrace vous aurait dit tout ça.

49
Wim Hollebrandse

Comme @JornSchouRode le fait remarquer, faire un exception.ToString() vous donne plus d'informations que d'utiliser simplement le exception.Message propriété. Cependant, même cela laisse encore de nombreuses informations, notamment:

  1. La propriété de collection Data trouvée dans toutes les exceptions.
  2. Toute autre propriété personnalisée ajoutée à 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 ordre de Nice. Il utilise C # 6.0 mais devrait être très facile à convertir si nécessaire. Voir aussi cette réponse associée.

public static class ExceptionExtensions
{
    public static string ToDetailedString(this Exception exception)
    {
        if (exception == null)
        {
            throw new ArgumentNullException(nameof(exception));
        }

        return ToDetailedString(exception, ExceptionOptions.Default);
    }

    public static string ToDetailedString(this Exception exception, ExceptionOptions options)
    {
        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 - Exceptions de journalisation

La plupart des gens utiliseront ce code pour se connecter. Pensez à utiliser Serilog avec mon Serilog.Exceptions paquet NuGet qui enregistre également tous propriétés d'une exception mais le fait plus rapidement et sans réflexion dans la majorité des cas. Serilog est un framework de journalisation très avancé qui fait fureur à l'heure actuelle.

Top Astuce - Traces de pile lisibles par l'homme

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

18

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 l'affichage à l'utilisateur. On pourrait soutenir que même cela n'est pas approprié pour un utilisateur, pour tous les types et occurrences d'exceptions (pensez à ArgumentExceptions, etc.).

De plus, en plus de StackTrace, ToString() inclura des informations que vous ne recevrez pas autrement. Par exemple, la sortie de la fusion, si activé , pour inclure les messages de journal dans les "messages" d’exception.

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

9
Christian.K

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

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

3
Dillie-O

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.

0
Thorsten Dittmar