Jaki jest właściwy sposób wyświetlania pełnego wyjątku InnerException?


155

Jaki jest właściwy sposób, aby pokazać moje pełne InnerException.

Okazało się, że niektóre z moich wyjątków InnerExceptions mają inny, InnerExceptionktóry jest dość głęboki.

Czy InnerException.ToString()wykona pracę za mnie, czy muszę przejść przez pętlę InnerExceptionsi zbudować Stringz StringBuilder?


Dlaczego musisz pokazać wewnętrzny wyjątek?
— Akram Shahda

26
@Akram, ponieważ przez większość czasu interesujący jest wewnętrzny wyjątek. Jednym z przykładów jest XmlSerializer, który po prostu zgłasza InvalidOperationException, gdy coś pójdzie nie tak. To, co poszło nie tak, dotyczy wewnętrznego wyjątku.
— adrianm

4
@AkramShahda A może chcesz użyć tej metody w swoim logowaniu?
— cederlof

Odpowiedzi:


239

Możesz po prostu wydrukować exception.ToString()- będzie to również zawierać pełny tekst dla wszystkich zagnieżdżonych plików InnerException.


18
Obejmuje to
— również mnóstwo

tylko dla zwięzłości w rzeczywistości nie potrzebujesz .ToString (), samo użycie wyjątku zrobi to samo.
— Alex Stephens

3
@AlexStephens masz rację, ale tylko wtedy, gdy z jakiegoś powodu masz niejawne rzutowanie „na łańcuch”, na przykład poprzedzający ciąg: „bla” + wyjątek
— oo_dev.

1
FYI: nie wywoła niestandardowych ToStringmetod dla wyjątków wewnętrznych, jak opisano szczegółowo w Dlaczego System.Exception.ToString nie wywołuje wirtualnego ToString dla wyjątków wewnętrznych? .
— Jeff B

45

Po prostu użyj exception.ToString()

http://msdn.microsoft.com/en-us/library/system.exception.tostring.aspx

Domyślna implementacja ToString uzyskuje nazwę klasy, która zgłosiła bieżący wyjątek, komunikat, wynik wywołania ToString w przypadku wyjątku wewnętrznego oraz wynik wywołania Environment.StackTrace. Jeśli którykolwiek z tych elementów członkowskich ma wartość null, jego wartość nie jest uwzględniana w zwracanym ciągu.

Jeśli nie ma komunikatu o błędzie lub jeśli jest to pusty ciąg („”), nie jest zwracany żaden komunikat o błędzie. Nazwa wewnętrznego wyjątku i ślad stosu są zwracane tylko wtedy, gdy nie są puste.

wyjątek.ToString () wywoła również funkcję .ToString () na wyjątku wewnętrznym tego wyjątku i tak dalej ...


45

Zwykle robię to, aby usunąć większość szumów:

void LogException(Exception error) {
    Exception realerror = error;
    while (realerror.InnerException != null)
        realerror = realerror.InnerException;

    Console.WriteLine(realerror.ToString())
}    

Edycja: zapomniałem o tej odpowiedzi i jestem zaskoczony, że nikt nie wskazał, że możesz to zrobić

void LogException(Exception error) {
    Console.WriteLine(error.GetBaseException().ToString())
}    

Ta metoda ukrywa wszystko z wyjątkiem najgłębszego wewnętrznego wyjątku. Gdyby to było coś przyziemnego, jak błąd „Podziel przez zero”, nie będzie jasne, gdzie to nastąpiło i co do niego doprowadziło. Oczywiście śledzenie pełnego stosu jest zwykle niechlujną przesadą, ale tylko odczytanie wewnętrznego wyjątku jest drugą skrajnością. odpowiedź user3016982 jest znacznie lepsza. Otrzymujesz każdy komunikat o wyjątku na stosie bez nieprzyjemnych śladów.
— JamesHoux

1
@JamesHoux Która z nich jest odpowiedzią „user3016982”? Nie mogę go tu znaleźć.
— sok z marakui

Użytkownik3016982 to ThomazMoura, patrz: stackoverflow.com/users/3016982/thomazmoura
— Apfelkuacha

@JamesHoux, wyjątek wewnętrzny ma pełną ścieżkę stosu pokazującą, gdzie wystąpił błąd i co do niego doprowadziło. Nie rozumiem, jakie dodatkowe informacje uzyskasz z usuniętych śladów stosu. Komunikaty o wyjątkach to kolejna rzecz i może być przydatne zebranie ich wszystkich.
— adrianm

2
Dlaczego po prostu nie użyjesz error.GetBaseException(). Myślę, że to samo ...
— Robba

37

Odpowiedź @ Jona jest najlepszym rozwiązaniem, jeśli chcesz uzyskać pełne szczegóły (wszystkie wiadomości i ślad stosu) i zalecane.

Mogą się jednak zdarzyć sytuacje, w których potrzebujesz tylko komunikatów wewnętrznych, w takich przypadkach używam następującej metody rozszerzenia:

public static class ExceptionExtensions
{
    public static string GetFullMessage(this Exception ex)
    {
        return ex.InnerException == null 
             ? ex.Message 
             : ex.Message + " --> " + ex.InnerException.GetFullMessage();
    }
}

Często używam tej metody, gdy mam różnych słuchaczy do śledzenia i rejestrowania i chcę mieć różne poglądy na ich temat. W ten sposób mogę mieć jednego odbiornika, który wysyła cały błąd ze śladem stosu pocztą e-mail do zespołu programistów w celu debugowania przy użyciu .ToString()metody, oraz jednego, który zapisuje plik dziennika z historią wszystkich błędów, które zdarzały się każdego dnia bez śladu stosu z .GetFullMessage()metodą.


7
Do Twojej wiadomości Jeśli exjest a AggregateException, żaden z wewnętrznych wyjątków nie zostanie uwzględniony w tym wyniku
— kornman00

3
Powinna to być standardowa metoda .NET. Każdy powinien tego używać.
— JamesHoux

9

Aby całkiem wydrukować tylko Messageczęść głębokich wyjątków, możesz zrobić coś takiego:

public static string ToFormattedString(this Exception exception)
{
    IEnumerable<string> messages = exception
        .GetAllExceptions()
        .Where(e => !String.IsNullOrWhiteSpace(e.Message))
        .Select(e => e.Message.Trim());
    string flattened = String.Join(Environment.NewLine, messages); // <-- the separator here
    return flattened;
}

public static IEnumerable<Exception> GetAllExceptions(this Exception exception)
{
    yield return exception;

    if (exception is AggregateException aggrEx)
    {
        foreach (Exception innerEx in aggrEx.InnerExceptions.SelectMany(e => e.GetAllExceptions()))
        {
            yield return innerEx;
        }
    }
    else if (exception.InnerException != null)
    {
        foreach (Exception innerEx in exception.InnerException.GetAllExceptions())
        {
            yield return innerEx;
        }
    }
}

To rekursywnie przechodzi przez wszystkie wewnętrzne wyjątki (w tym przypadek AggregateExceptions), aby wypisać wszystkie Messagezawarte w nich właściwości, rozdzielone podziałem wiersza.

Na przykład

var outerAggrEx = new AggregateException(
    "Outer aggr ex occurred.",
    new AggregateException("Inner aggr ex.", new FormatException("Number isn't in correct format.")),
    new IOException("Unauthorized file access.", new SecurityException("Not administrator.")));
Console.WriteLine(outerAggrEx.ToFormattedString());

Wystąpiła zewnętrzna aggr ex.
Wewnętrzny aggr ex.
Numer ma nieprawidłowy format.
Nieautoryzowany dostęp do plików.
Nie administrator.


Aby uzyskać więcej informacji, należy posłuchać innych właściwości wyjątku . Na przykład Databędzie miał jakieś informacje. Mógłbyś:

foreach (DictionaryEntry kvp in exception.Data)

Aby uzyskać wszystkie właściwości pochodne (nie w Exceptionklasie bazowej ), możesz zrobić:

exception
    .GetType()
    .GetProperties()
    .Where(p => p.CanRead)
    .Where(p => p.GetMethod.GetBaseDefinition().DeclaringType != typeof(Exception));

+1, to jest prawie dokładnie to samo co ja. Zastanów się nad poszukaniem właściwości implementującej IEnumerable<Exception>zamiast twardego kodu AggregrateExceptiondo obsługi innych podobnych typów. Wykluczaj p.IsSpecialNamei pi.GetIndexParameters().Length != 0unikaj problemów.
— Dołączenie

@adrianm dobra uwaga na temat sprawdzania informacji o nieruchomościach. Jeśli chodzi o sprawdzanie kolekcji wyjątków, chodzi o to, gdzie chcesz narysować linię. Jasne, że to też da się zrobić ...
— nawfal

4

Ja robię:

namespace System {
  public static class ExtensionMethods {
    public static string FullMessage(this Exception ex) {
      if (ex is AggregateException aex) return aex.InnerExceptions.Aggregate("[ ", (total, next) => $"{total}[{next.FullMessage()}] ") + "]";
      var msg = ex.Message.Replace(", see inner exception.", "").Trim();
      var innerMsg = ex.InnerException?.FullMessage();
      if (innerMsg is object && innerMsg!=msg) msg = $"{msg} [ {innerMsg} ]";
      return msg;
    }
  }
}

To „ładnie wypisuje” wszystkie wewnętrzne wyjątki, a także obsługuje AggregateExceptions i przypadki, w których InnerException.Message jest takie samo jak Message


3

Jeśli chcesz uzyskać informacje o wszystkich wyjątkach, użyj exception.ToString(). Będzie zbierać dane ze wszystkich wyjątków wewnętrznych.

Jeśli chcesz tylko oryginalny wyjątek, użyj exception.GetBaseException().ToString(). W ten sposób otrzymasz pierwszy wyjątek, np. Najgłębszy wyjątek wewnętrzny lub bieżący wyjątek, jeśli nie ma wyjątku wewnętrznego.

Przykład:

try {
    Exception ex1 = new Exception( "Original" );
    Exception ex2 = new Exception( "Second", ex1 );
    Exception ex3 = new Exception( "Third", ex2 );
    throw ex3;
} catch( Exception ex ) {
    // ex => ex3
    Exception baseEx = ex.GetBaseException(); // => ex1
}

2

narastanie odpowiedzi nawfala.

używając jego odpowiedzi, brakowało zmiennej aggrEx, dodałem ją.

plik ExceptionExtenstions.class:

// example usage:
// try{ ... } catch(Exception e) { MessageBox.Show(e.ToFormattedString()); }

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace YourNamespace
{
    public static class ExceptionExtensions
    {

        public static IEnumerable<Exception> GetAllExceptions(this Exception exception)
        {
            yield return exception;

            if (exception is AggregateException )
            {
                var aggrEx = exception as AggregateException;
                foreach (Exception innerEx in aggrEx.InnerExceptions.SelectMany(e => e.GetAllExceptions()))
                {
                    yield return innerEx;
                }
            }
            else if (exception.InnerException != null)
            {
                foreach (Exception innerEx in exception.InnerException.GetAllExceptions())
                {
                    yield return innerEx;
                }
            }
        }


        public static string ToFormattedString(this Exception exception)
        {
            IEnumerable<string> messages = exception
                .GetAllExceptions()
                .Where(e => !String.IsNullOrWhiteSpace(e.Message))
                .Select(exceptionPart => exceptionPart.Message.Trim() + "\r\n" + (exceptionPart.StackTrace!=null? exceptionPart.StackTrace.Trim():"") );
            string flattened = String.Join("\r\n\r\n", messages); // <-- the separator here
            return flattened;
        }
    }
}

Miałem wyjątek, ponieważ:e.StackTrace == null
— Andrei Krasutski

1
Zaktualizowałem .Select (e => e.Message.Trim () + "\ r \ n" + (e.StackTrace! = Null? StackTrace.Trim (): "")); może to pomoże
— Shimon Doodkin
Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.