Exception.Message vs Exception.ToString ()

207

У меня есть код, который входит в систему Exception.Message. Тем не менее, я прочитал статью, в которой говорится, что лучше использовать Exception.ToString(). С последним вы сохраняете более важную информацию об ошибке.

Это правда, и безопасно ли идти вперед и заменить все записи кода Exception.Message?

Я также использую макет на основе XML для log4net . Возможно ли, что Exception.ToString()могут содержать недопустимые символы XML, что может вызвать проблемы?

JL.
источник
1
Вы также должны взглянуть на ELMAH ( code.google.com/p/elmah ) - очень простая в использовании платформа для регистрации ошибок для ASP.NET.
Ашиш Гупта

Ответы:

278

Exception.Messageсодержит только сообщение (doh), связанное с исключением. Пример:

В экземпляре объекта не задана ссылка на объект

Exception.ToString()Метод даст намного более подробный вывод, содержащий тип исключения, сообщение (с) до того , трассировки стека, и все эти вещи снова для вложенных / внутренних исключений. Точнее, метод возвращает следующее:

ToString возвращает представление текущего исключения, которое предназначено для понимания людьми. Если исключение содержит данные, чувствительные к культуре, строковое представление, возвращаемое ToString, должно учитывать текущую системную культуру. Хотя нет точных требований к формату возвращаемой строки, она должна пытаться отразить значение объекта, воспринимаемое пользователем.

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

Если сообщение об ошибке отсутствует или это пустая строка (""), сообщение об ошибке не возвращается. Имя внутреннего исключения и трассировка стека возвращаются, только если они не являются нулевой ссылкой (ничего в Visual Basic).

Йорн Шоу-Роде
источник
86
+1 Очень больно видеть ТОЛЬКО, что «Ссылка на объект не установлена ​​на экземпляр объекта» в журналах. Вы чувствуете себя действительно беспомощным. :-)
Ашиш Гупта
1
В последней части есть исключения, которые не поставляются с Exception.Message. В зависимости от того, что вы делаете в части обработки ошибок, вы можете столкнуться с проблемами из-за Exception.Message.
Коралловая Доу
50
Очень больно видеть, что я написал код, который по сути делает то же самое, что и ToString ().
Престон Маккормик
1
@KunalGoel Если журнал поступил от prod, и у вас нет никаких сведений о вводе, то нет, вы не можете просто «отладить, включив исключение CLR».
jpmc26
1
Обратите внимание, что это «реализация по умолчанию ToString» ... (акцент на «default») .. это не означает, что каждый следовал этой практике с любыми пользовательскими исключениями. #learnedTheHardWay
granadaCoder
52

В дополнение к тому, что уже было сказано, не используйте ToString()объект исключения для отображения пользователю. Достаточно только Messageсвойства или пользовательского сообщения более высокого уровня.

С точки зрения ведения журнала, определенно используйте ToString()Исключение, а не только Messageсвойство, как в большинстве сценариев, вам придется почесать голову, где конкретно произошло это исключение, и каков был стек вызовов. Stacktrace сказал бы вам все это.

Вим Холлебрандсе
источник
Если вы используете ToString () в журналах, убедитесь, что в ToString не включена конфиденциальная информация
Майкл Фрейдгейм,
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 - это очень продвинутый каркас журналирования, который очень популярен на момент написания.

Главный совет - следы стека, читаемые человеком

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

Мухаммед Рехан Саид
источник
9

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

Также, помимо StackTrace, ToString()будет включена информация, которую вы не получите иначе. Например, вывод слияния, если он включен включать сообщения журнала в исключение «сообщения».

Некоторые типы исключений даже включают дополнительную информацию (например, из пользовательских свойств) в ToString(), но не в Сообщение.

Christian.K
источник
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()вам даст, верно?
Йорн Шоу-Роде
5
@ Matt: Создание экземпляра StringBuilderв этом случае вполне может быть более дорогим , чем две новые строки распределения, это очень спорно , было бы более эффективным здесь. Мы не имеем дело с итерациями. Лошади на курсы.
Вим Холлебрандс
2
Проблема здесь в том, что вы получите только «InnerException» самого внешнего исключения. IOW, если InnerException сам имеет набор InnerException, вы не будете его выгружать (при условии, что вы хотите в первую очередь). Я бы действительно придерживался ToString ().
Christian.K
6
Просто используйте ex.ToString. Это дает вам все детали.
Джон Сондерс
3
@Christian: компилятор вменяемый с несколькими + s. См., Например, «Оператор + прост в использовании и обеспечивает интуитивно понятный код. Даже если вы используете несколько операторов + в одной инструкции, содержимое строки копируется только один раз». от msdn.microsoft.com/en-us/library/ms228504.aspx
Дэвид Эйсон
3

С точки зрения формата XML для log4net вам не нужно беспокоиться о ex.ToString () для журналов. Просто передайте сам объект исключения, а log4net сделает все остальное и предоставит вам все детали в предварительно настроенном формате XML. Единственное, с чем я иногда сталкиваюсь, это форматирование новой строки, но именно тогда я читаю сырые файлы. В противном случае синтаксический анализ XML прекрасно работает.

Dillie-О
источник
0

Ну, я бы сказал, это зависит от того, что вы хотите увидеть в журналах, не так ли? Если вы довольны тем, что предоставляет ex.Message, используйте это. В противном случае используйте ex.toString () или даже зарегистрируйте трассировку стека.

Торстен Диттмар
источник
6
ex.ToString включает в себя трассировку стека
Джон Сондерс