Exception.Message vs Exception.ToString ()


207

У мене є код, який веде журнал Exception.Message. Однак я прочитав статтю, в якій сказано, що її краще використовувати Exception.ToString(). За допомогою останнього ви зберігаєте більш важливу інформацію про помилку.

Це правда, і чи безпечно йти вперед та замінювати весь журнал коду Exception.Message?

Я також використовую макет на основі XML для log4net . Чи можливо, що вони Exception.ToString()можуть містити недійсні символи XML, що може спричинити проблеми?


1
Також слід поглянути на ELMAH ( code.google.com/p/elmah ) - дуже простий у використанні фреймворк для реєстрації помилок для ASP.NET.
Ашиш Гупта

Відповіді:


278

Exception.Messageмістить лише повідомлення (doh), пов'язане з винятком. Приклад:

Посилання на об'єкт не встановлено для примірника об'єкта

Exception.ToString()Метод дасть набагато більш докладний висновок, що містить тип винятку, повідомлення (с) до того , трасування стека, і всі ці речі знову для вкладених / внутрішніх винятків. Точніше, метод повертає наступне:

ToString повертає уявлення про поточний виняток, який має бути зрозумілий людям. Якщо виняток містить дані, що залежать від культури, представлення рядків, повернене ToString, потрібно для врахування поточної системної культури. Хоча немає точних вимог до формату повернутого рядка, він повинен намагатися відобразити значення об'єкта, сприйняте користувачем.

Реалізація за замовчуванням ToString отримує назву класу, який кинув поточний виняток, повідомлення, результат виклику ToString на внутрішньому винятку та результат виклику Environment.StackTrace. Якщо будь-який з цих членів є нульовою посиланням (Nothing in Visual Basic), його значення не включається до повернутого рядка.

Якщо немає повідомлення про помилку або якщо це порожній рядок (""), повідомлення про помилку не повертається. Ім’я внутрішнього винятку та слід стека повертаються лише у тому випадку, якщо вони не є нульовою посиланням (Nothing in Visual Basic).


85
+1 Дуже болісно бачити ТОЛЬКО, що "Посилання на об'єкт не встановлено на екземпляр об'єкта" в журналах. Ви відчуваєте себе справді безпорадним. :-)
Ashish Gupta

1
В останній частині є винятки, які не поставляються з Exception.Message. Функція того, що ви робите в частині обробки помилок, у вас можуть виникнути проблеми через виняток.
Coral Doe

49
Дуже боляче бачити, що я написав код, який по суті робить саме те, що робить ToString ().
Престон МакКормік

1
@KunalGoel Якщо журнал походить від prod і у вас немає вказівки, яким був вхід, то ні, ви не можете просто "налагодити, включивши виняток CLR".
jpmc26

1
Зауважте, це "реалізація за замовчуванням ToString" ... (наголос на "за замовчуванням") .. це не означає, що всі дотримувались цієї практики за будь-якими спеціальними винятками. #learnedTheHardWay
granadaCoder

52

Крім того, що вже було сказано, не використовуйте ToString()об’єкт виключення для показу користувачеві. Достатньо мати лише Messageвластивості або спеціальне повідомлення більш високого рівня.

Що стосується цілей реєстрації, то, безумовно, використовуйте ToString()для Messageвинятку не лише властивість, як у більшості сценаріїв, вам залишатиметься чухати голову, де конкретно стався цей виняток, і який був стек викликів. Стек-трек сказав би вам усе це.


Якщо ви використовуєте ToString () у журналах, не включайте конфіденційну інформацію в ToString
Michael Freidgeim

22

Перетворення цілого винятку в рядок

Виклик 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.Exceptions NuGet, який також реєструє всі властивості виключення, але робить це швидше і без роздумів у більшості випадків. Serilog - це дуже вдосконалена система лісозаготівлі, яка викликає всю гнів на момент написання.

Верхня порада - людські читані стеки стека

Ви можете скористатися пакетом Ben.Demystifier NuGet, щоб отримати сліди, що читаються людиною, для ваших винятків або пакунок NuGet serilog-enrichers- demystify, якщо ви використовуєте Serilog.


9

Я б сказав, що @Wim має рацію. Вам слід використовувати ToString()для реєстраційних файлів - якщо припустити технічну аудиторію - і Message, якщо взагалі, для відображення користувачеві. Можна стверджувати, що навіть це не підходить користувачеві, для кожного типу винятків і випадковості (подумайте про ArgumentExceptions тощо).

Крім того, крім StackTrace, ToString()буде включена інформація, яку ви не отримаєте в іншому випадку. Наприклад, вихід синтезу, якщо він включений, включаючи повідомлення журналу, крім "повідомлень".

Деякі типи винятків включають навіть додаткову інформацію (наприклад, про власні властивості) у ToString(), але не у Повідомленні.


8

Залежить від потрібної вам інформації. Для налагодження корисні трасування стека та внутрішній виняток:

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

12
Це більш-менш те Exception.ToString(), що вам дасть, правда?
Jørn Schou-Rode

5
@Matt: Побудова екземпляра StringBuilderв цьому сценарії може бути дорожчою, ніж два нові розподіли рядків, це дуже дискусійно, тут було б ефективніше. Це не так, як ми маємо справу з ітераціями. Коні на курси.
Вім Холлебрандсе

2
Проблема тут полягає в тому, що ви отримаєте лише "InnerException" самого зовнішнього винятку. IOW, якщо InnerException має набір InnerException, ви його не скидаєте (припускаючи, що ви хочете в першу чергу). Я б дійсно дотримувався ToString ().
Крістіан.К

6
Просто використовуйте ex.ToString. Тут ви отримуєте всі деталі.
Джон Сондерс

3
@Christian: Компілятор є здоровим з кількома + s. Дивіться, наприклад, "Оператор + простий у використанні та створює інтуїтивно зрозумілий код. Навіть якщо ви використовуєте кілька операторів + в одному операторі, вміст рядка копіюється лише один раз." від msdn.microsoft.com/en-us/library/ms228504.aspx
Девід Ейсон

3

Що стосується формату XML для log4net, вам не потрібно турбуватися про ex.ToString () для журналів. Просто передайте сам об’єкт винятку, а log4net все інше дає вам усі деталі у його попередньо налаштованому форматі XML. Єдине, на що я стикаюся, - це форматування нового рядка, але саме тоді я читаю файли в сирому вигляді. Інакше розбір XML працює чудово.


0

Ну, я б сказав, це залежить від того, що ви хочете бачити в журналах, чи не так? Якщо ви задоволені тим, що надає ex.Message, використовуйте це. В іншому випадку використовуйте ex.toString () або навіть записуйте трасування стека.


5
ex.ToString включає слід стека
Джон Сондерс
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.