.NET: Как преобразовать исключение в строку? - PullRequest
50 голосов
/ 07 ноября 2011

Когда выдается исключение (при отладке в IDE), у меня есть возможность просмотреть детали исключения:

enter image description here

Но вкод, если я позвоню exception.ToString() я не смогу увидеть эти полезные детали:

System.Data.SqlClient.SqlException (0x80131904): Could not find stored procedure 'FetchActiveUsers'.
  [...snip stack trace...]

Но в Visual Studio есть какая-то магия, когда он может скопировать исключение в буфер обмена :

enter image description here

Что дает полезную информацию:

System.Data.SqlClient.SqlException was unhandled by user code
  Message=Could not find stored procedure 'FetchActiveUsers'.
  Source=.Net SqlClient Data Provider
  ErrorCode=-2146232060
  Class=16
  LineNumber=1
  Number=2812
  Procedure=""
  Server=vader
  State=62
  StackTrace:
       [...snip stack trace...]
  InnerException:

Ну, я хочу это!

Что бы содержать:

String ExceptionToString(Exception ex)
{ 
    //todo: Write useful routine
    return ex.ToString();
}

, которые могут совершить ту же магию.Есть ли где-нибудь встроенная функция .NET?Есть ли у Exception секретный метод для преобразования его в строку?

Ответы [ 10 ]

54 голосов
/ 07 ноября 2011

ErrorCode относится к ExternalException, а не Exception, а LineNumber и Number относятся к SqlException, а не Exception.Следовательно, единственный способ получить эти свойства из общего метода расширения в Exception - это использовать отражение, чтобы перебрать все открытые свойства.

Так что вам придется сказать что-то вроде:

public static string GetExceptionDetails(this Exception exception) {
    var properties = exception.GetType()
                            .GetProperties();
    var fields = properties
                     .Select(property => new { 
                         Name = property.Name,
                         Value = property.GetValue(exception, null)
                     })
                     .Select(x => String.Format(
                         "{0} = {1}",
                         x.Name,
                         x.Value != null ? x.Value.ToString() : String.Empty
                     ));
    return String.Join("\n", fields);
}

(не проверено на наличие проблем с комплиментами.)

.NET 2.0 совместимый ответ:

public static string GetExceptionDetails(this Exception exception) 
{
    PropertyInfo[] properties = exception.GetType()
                            .GetProperties();
    List<string> fields = new List<string>();
    foreach(PropertyInfo property in properties) {
        object value = property.GetValue(exception, null);
        fields.Add(String.Format(
                         "{0} = {1}",
                         property.Name,
                         value != null ? value.ToString() : String.Empty
        ));    
    }         
    return String.Join("\n", fields.ToArray());
}
21 голосов
/ 24 июля 2013

Я сначала попробовал ответ Джейсона (вверху), который работал довольно хорошо, но я также хотел:

  • Для итеративного цикла внутренних исключений и отступа от них.
  • Игнорировать нулевые свойства и повышает читабельность вывода.
  • Включает метаданные в свойстве Data. (если есть), но исключает само свойство Data. (бесполезно).

Я сейчас использую это:

    public static void WriteExceptionDetails(Exception exception, StringBuilder builderToFill, int level)
    {
        var indent = new string(' ', level);

        if (level > 0)
        {
            builderToFill.AppendLine(indent + "=== INNER EXCEPTION ===");                
        }

        Action<string> append = (prop) =>
            {
                var propInfo = exception.GetType().GetProperty(prop);
                var val = propInfo.GetValue(exception);

                if (val != null)
                {
                    builderToFill.AppendFormat("{0}{1}: {2}{3}", indent, prop, val.ToString(), Environment.NewLine);
                }
            };

        append("Message");
        append("HResult");
        append("HelpLink");
        append("Source");
        append("StackTrace");
        append("TargetSite");

        foreach (DictionaryEntry de in exception.Data)
        {
            builderToFill.AppendFormat("{0} {1} = {2}{3}", indent, de.Key, de.Value, Environment.NewLine);
        }

        if (exception.InnerException != null)
        {
            WriteExceptionDetails(exception.InnerException, builderToFill, ++level);
        }
    }

Звоните так:

        var builder = new StringBuilder();
        WriteExceptionDetails(exception, builder, 0);
        return builder.ToString();
10 голосов
/ 30 ноября 2015

Этот исчерпывающий ответ обрабатывает запись:

  1. Свойство коллекции Data, обнаруженное во всех исключениях (принятый ответ этого не делает).
  2. Добавлены любые другие пользовательские свойствак исключению.
  3. Рекурсивно записывает InnerException (Принятый ответ этого не делает).
  4. Записывает коллекцию исключений, содержащихся в AggregateException.

Он также записывает свойства исключений в более хорошем порядке.Он использует C # 6.0, но при необходимости его будет очень легко конвертировать в более старые версии.

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

Главный совет - исключения журналирования

Большинство людей будут использовать этот код для регистрации.Подумайте об использовании Serilog с моим Serilog.Exceptions NuGet пакетом, который также регистрирует все свойства исключения, но делает это быстрее и без отражения в большинстве случаев.Serilog - это очень продвинутый каркас для ведения журналов, который во время написания статьи всегда был в моде.

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

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

6 голосов
/ 08 апреля 2013

Для людей, которые не хотят возиться с переопределением, этого простого ненавязчивого метода может быть достаточно:

    public static string GetExceptionDetails(Exception exception)
    {
        return "Exception: " + exception.GetType()
            + "\r\nInnerException: " + exception.InnerException
            + "\r\nMessage: " + exception.Message
            + "\r\nStackTrace: " + exception.StackTrace;
    }

Он не показывает специфичные для SQLException детали, которые вы хотите ...

6 голосов
/ 07 ноября 2011

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

Такие вещи, как ErrorCode и Message - это просто свойства исключения, которое вы можете добавить к желаемому выводу строки.


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

2 голосов
/ 07 ноября 2011

Возможно, вам придется вручную создать эту строку, объединяя различные поля, которые вас интересуют.

2 голосов
/ 07 ноября 2011

Каждое левое имя является свойством в Исключении. Если вы хотите отобразить Сообщение поле, вы можете сделать

return ex.Message;

Довольно просто. Аналогично, StackTrace может отображаться как ссылка ниже.

Полный пример StackTrace: http://msdn.microsoft.com/en-us/library/system.exception.stacktrace.aspx

и класс исключения: http://msdn.microsoft.com/en-us/library/system.exception.aspx

2 голосов
/ 07 ноября 2011

Для отображения некоторых деталей пользователю необходимо использовать ex.Message. Для отображения разработчикам вам, вероятно, понадобятся ex.Message и ex.StackTrace.

Не существует «секретного» метода, вы можете считать, что свойство Message лучше всего подходит для удобного сообщения.

Также будьте осторожны, чтобы в некоторых случаях у вас могло быть внутреннее исключение в исключении, которое вы ловите, что также было бы полезно для регистрации.

0 голосов
/ 07 ноября 2011

Если вы вызываете ToString для объекта Exception, вы получаете имя класса, добавленное к сообщению, за которым следует внутреннее исключение, а затем трассировка стека.

className + message + InnerException + stackTrace

Учитывая, что InnerException и StackTrace добавляются, только если они не равны NULL.Кроме того, поля, которые вы упомянули на скриншоте, не являются частью стандартного класса Exception.Да, исключение предлагает открытое свойство «Данные», которое содержит дополнительную пользовательскую информацию об исключении.

0 голосов
/ 07 ноября 2011

В Visual Studio такая информация может выводиться визуализатором отладчика.

Я предполагаю, что, поскольку можно написать собственный визуализатор отладчика: http://msdn.microsoft.com/en-us/library/e2zc529c.aspx

Это теоретическиЕсли вы можете перепроектировать встроенный визуализатор отладчика для исключений (если вы можете работать там, где они хранятся), вы можете использовать ту же функциональность.

РЕДАКТИРОВАТЬ:

Вотсообщение о том, где хранятся визуализаторы отладчика: Где я могу найти Microsoft.VisualStudio.DebuggerVisualizers?

Возможно, вы сможете использовать его в своих целях.

...