Exception.Message vs Exception.ToString ()

207

Mam kod, który się loguje Exception.Message. Przeczytałem jednak artykuł, który mówi, że lepiej go używać Exception.ToString(). W tym drugim przypadku zachowujesz ważniejsze informacje o błędzie.

Czy to prawda i czy można bezpiecznie zastąpić rejestrowanie kodu Exception.Message?

Używam również układu opartego na XML dla log4net . Czy to możliwe, że Exception.ToString()mogą zawierać nieprawidłowe znaki XML, co może powodować problemy?

J L.
źródło
1
Powinieneś także spojrzeć na ELMAH ( code.google.com/p/elmah ) - Bardzo łatwy w użyciu program do rejestrowania błędów dla ASP.NET.
Ashish Gupta,

Odpowiedzi:

278

Exception.Messagezawiera tylko komunikat (doh) powiązany z wyjątkiem. Przykład:

Odwołanie do obiektu nie jest ustawione na wystąpienie obiektu

Exception.ToString()Sposób daje znacznie więcej komunikatów wyjściu ponownie zawierającą typ wyjątku komunikatu (sprzed), ślad stosu, a wszystkie te rzeczy zagnieżdżonych / wewnętrznych wyjątkami. Dokładniej, metoda zwraca następujące elementy:

ToString zwraca reprezentację bieżącego wyjątku, który ma być zrozumiały dla ludzi. Jeżeli wyjątek zawiera dane wrażliwe na kulturę, reprezentacja ciągu zwracana przez ToString jest wymagana, aby uwzględnić bieżącą kulturę systemową. Chociaż nie ma dokładnych wymagań dotyczących formatu zwracanego ciągu, powinien on spróbować odzwierciedlić wartość obiektu postrzeganą przez użytkownika.

Domyślna implementacja ToString uzyskuje nazwę klasy, która zgłosiła bieżący wyjątek, komunikat, wynik wywołania ToString w wewnętrznym wyjątku oraz wynik wywołania Environment.StackTrace. Jeśli którykolwiek z tych elementów jest odwołaniem zerowym (Nic w języku Visual Basic), jego wartość nie jest uwzględniana w zwracanym ciągu.

Jeśli nie ma komunikatu o błędzie lub jest to pusty ciąg („”), komunikat o błędzie nie jest zwracany. Nazwa wewnętrznego wyjątku i ślad stosu są zwracane tylko wtedy, gdy nie są odwołaniem zerowym (Nic w Visual Basic).

Jørn Schou-Rode
źródło
85
+1 Bardzo boleśnie jest widzieć TYLKO, że „Odwołanie do obiektu nie jest ustawione na wystąpienie obiektu” w logach. Czujesz się naprawdę bezradny. :-)
Ashish Gupta
1
W ostatniej części są wyjątki, które nie pochodzą z wyjątkiem. W zależności od tego, co robisz w części dotyczącej obsługi błędów, możesz mieć problemy z powodu wyjątku.
Coral Doe,
49
Bardzo bolesne jest to, że napisałem kod, który zasadniczo robi dokładnie to samo, co robi ToString ().
Preston McCormick
1
@KunalGoel Jeśli dziennik pochodzi od prod i nie masz żadnego wskazania, co to było wejście, to nie, nie możesz po prostu „debugować poprzez włączenie wyjątku CLR”.
jpmc26
1
Uwaga: jest to „domyślna implementacja ToString” ... (nacisk na „default”) .. nie oznacza to, że wszyscy przestrzegali tej praktyki z dowolnymi niestandardowymi wyjątkami. #learnedTheHardWay
granadaCoder
52

Oprócz tego, co już powiedziano, nie używaj ToString()obiektu wyjątku do wyświetlania użytkownikowi. Wystarczy Messagewłaściwość lub niestandardowy komunikat wyższego poziomu.

Jeśli chodzi o rejestrowanie, zdecydowanie użyj ToString()wyjątku, a nie tylko Messagewłaściwości, ponieważ w większości scenariuszy będziesz drapał się w głowie, gdzie konkretnie wystąpił ten wyjątek i jaki był stos wywołań. Stacktrace powiedziałby ci to wszystko.

Wim Hollebrandse
źródło
Jeśli używasz ToString () w logach, nie dołączaj poufnych informacji do ToString
Michael Freidgeim
22

Konwertowanie CAŁEGO wyjątku na ciąg

Dzwonienie Exception.ToString()daje więcej informacji niż tylko korzystanie z Exception.Messagewłaściwości. Jednak nawet to wciąż pomija wiele informacji, w tym:

  1. The Data kolekcji znaleziona we wszystkich wyjątkach.
  2. Wszelkie inne właściwości niestandardowe dodane do wyjątku.

Są chwile, kiedy chcesz uchwycić te dodatkowe informacje. Poniższy kod obsługuje powyższe scenariusze. Zapisuje także właściwości wyjątków w ładnej kolejności. Używa C # 7, ale w razie potrzeby powinna być bardzo łatwa do konwersji na starsze wersje. Zobacz także pokrewną odpowiedź.

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

Najważniejsza wskazówka - wyjątki dotyczące rejestrowania

Większość osób będzie używać tego kodu do logowania. Rozważ użycie Serilog z moim pakietem Serilog.Exceptions NuGet, który rejestruje także wszystkie właściwości wyjątku, ale robi to szybciej i bez refleksji w większości przypadków. Serilog to bardzo zaawansowana platforma rejestrowania, która jest w modzie w momencie pisania.

Najlepsza wskazówka - ślady stosu czytelne dla człowieka

Możesz użyć pakietu Ben.Demystifier NuGet, aby uzyskać czytelne dla człowieka ślady stosu dla twoich wyjątków lub pakietu NuGet serilog-enrichers-demystify , jeśli używasz Serilog.

Muhammad Rehan Saeed
źródło
9

Powiedziałbym, że @Wim ma rację. Powinieneś używać ToString()do plików logów - zakładając odbiorców technicznych - i Message, jeśli w ogóle, do wyświetlania użytkownikowi. Można argumentować, że nawet to nie jest odpowiednie dla użytkownika, dla każdego typu wyjątku i każdego zdarzenia (pomyśl o ArgumentExceptions itp.).

Oprócz StackTrace ToString()będzie również zawierać informacje, których inaczej nie uzyskasz. Na przykład wyjście syntezy jądrowej, jeśli jest włączone aby dołączyć komunikaty dziennika do wyjątku „wiadomości”.

Niektóre typy wyjątków zawierają nawet dodatkowe informacje (na przykład z właściwości niestandardowych) w komunikacie ToString(), ale nie w nim.

Christian.K
źródło
8

Zależy od potrzebnych informacji. Do debugowania przydatne są śledzenie stosu i wyjątek wewnętrzny:

    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
źródło
12
To mniej więcej to Exception.ToString(), co ci da, prawda?
Jørn Schou-Rode
5
@Matt: Konstruowanie instancji StringBuilderw tym scenariuszu może być droższe niż dwa nowe przydziały ciągów, jest wysoce dyskusyjne, byłoby tu bardziej wydajne. To nie tak, że mamy do czynienia z iteracjami. Konie na kursy.
Wim Hollebrandse
2
Problem polega na tym, że dostaniesz tylko „InnerException” najbardziej zewnętrznego wyjątku. IOW, jeśli sam InnerException ma zestaw InnerException, nie zrzucisz go (zakładając, że chcesz). Naprawdę trzymałbym się ToString ().
Christian.K
6
Wystarczy użyć ex.ToString. Daje ci wszystkie szczegóły.
John Saunders
3
@Christian: Kompilator jest rozsądny z wieloma + s. Zobacz na przykład „Operator + jest łatwy w użyciu i zapewnia intuicyjny kod. Nawet jeśli używasz kilku operatorów + w jednej instrukcji, treść ciągu jest kopiowana tylko raz”. from msdn.microsoft.com/en-us/library/ms228504.aspx
David Eison
3

Jeśli chodzi o format XML dla log4net, nie musisz się martwić ex.ToString () dla dzienników. Po prostu przekaż sam obiekt wyjątku, a log4net zajmie się resztą, podając wszystkie szczegóły w jego wstępnie skonfigurowanym formacie XML. Jedyną rzeczą, na którą czasem się natknęłam, jest formatowanie nowego wiersza, ale wtedy czytam pliki na surowo. W przeciwnym razie parsowanie XML działa świetnie.

Dillie-O
źródło
0

Powiedziałbym, że to zależy od tego, co chcesz zobaczyć w logach, prawda? Jeśli jesteś zadowolony z tego, co zapewnia ex.Message, skorzystaj z tego. W przeciwnym razie użyj ex.toString () lub nawet zarejestruj ślad stosu.

Thorsten Dittmar
źródło
5
ex.ToString zawiera ślad stosu
John Saunders