Rejestrowanie i śledzenie w aplikacjach .NET

Ukończone

W miarę dalszego rozwoju i zwiększania złożoności aplikacji warto zastosować dodatkowe debugowanie.

Śledzenie to sposób monitorowania wykonywania aplikacji podczas jej działania. Instrumentację śledzenia i debugowania można dodać do aplikacji platformy .NET podczas jej opracowywania. Możesz użyć tej instrumentacji podczas tworzenia aplikacji i po jej wdrożeniu.

Ta prosta technika jest zaskakująco potężna. Można go używać w sytuacjach, w których potrzebujesz więcej niż debuger:

  • Problemy występujące w długich okresach czasu mogą być trudne do debugowania przy użyciu tradycyjnego debugera. Dzienniki umożliwiają szczegółowy przegląd pośmiertny, który obejmuje długie okresy czasu. Natomiast debugery są ograniczone do analizy w czasie rzeczywistym.
  • Aplikacje wielowątkowa i aplikacje rozproszone są często trudne do debugowania. Dołączanie debugera ma tendencję do modyfikowania zachowań. Szczegółowe dzienniki można analizować zgodnie z potrzebami, aby zrozumieć złożone systemy.
  • Problemy w aplikacjach rozproszonych mogą wynikać ze złożonej interakcji między wieloma składnikami. Połączenie debugera z każdą częścią systemu może nie być uzasadnione.
  • Wiele usług nie powinno być wstrzymanych. Dołączanie debugera często powoduje przekroczenie limitu czasu.
  • Problemy nie zawsze są przewidywane. Rejestrowanie i śledzenie są przeznaczone dla małych obciążeń, dzięki czemu programy zawsze mogą być rejestrowane w przypadku wystąpienia problemu.

Zapisywanie informacji w oknach wyjściowych

Do tego momentu używaliśmy konsoli do wyświetlania informacji użytkownikowi aplikacji. Istnieją inne typy aplikacji utworzonych za pomocą platformy .NET, które mają interfejsy użytkownika, takie jak aplikacje mobilne, internetowe i klasyczne, i nie ma widocznej konsoli. W tych aplikacjach System.Console rejestruje komunikaty "za kulisami". Te komunikaty mogą być wyświetlane w oknie danych wyjściowych w programie Visual Studio lub Visual Studio Code. Mogą one również być danymi wyjściowymi dziennika systemu, takiego jak logcatsystemu Android. Dlatego też powinieneś dokładnie rozważyć użycie System.Console.WriteLine w aplikacji innej niż konsolowa.

W tym miejscu można używać System.Diagnostics.Debug i System.Diagnostics.Trace oprócz System.Console. Zarówno Debug, jak i Trace są częścią System.Diagnostics i będą zapisywać do dzienników tylko wtedy, gdy zostanie dołączony odpowiedni odbiornik.

Wybór, którego interfejsu API stylu drukowania ma być używany, jest dla Ciebie. Najważniejsze różnice to:

  • System.Console
    • Zawsze włączone i zawsze wypisuje do konsoli.
    • Przydatne dla informacji, które klient może potrzebować zobaczyć w wydaniu.
    • Ponieważ jest to najprostsze podejście, często jest używane do tymczasowego debugowania ad hoc. Ten kod debugowania zwykle nie jest wgrywany do systemu kontroli wersji.
  • System.Diagnostics.Trace
    • Włączone tylko jeśli zdefiniowano TRACE.
    • Zapisuje do dołączonych odbiorników, domyślnie, DefaultTraceListener.
    • Użyj tego interfejsu API podczas tworzenia dzienników, które będą włączone w większości kompilacji.
  • System.Diagnostics.Debug
    • Włączono tylko wtedy, gdy DEBUG jest zdefiniowana (w trybie debugowania).
    • Zapisuje w dołączonym debugerze.
    • Użyj tego interfejsu API podczas tworzenia logów, które będą włączone tylko w wersjach debugowych.
Console.WriteLine("This message is readable by the end user.");
Trace.WriteLine("This is a trace message when tracing the app.");
Debug.WriteLine("This is a debug message just for developers.");

Podczas projektowania strategii śledzenia i debugowania zastanów się, jak mają wyglądać dane wyjściowe. Wiele instrukcji Write wypełnionych niepowiązanymi informacjami tworzy dziennik, który jest trudny do odczytania. Z drugiej strony, użycie funkcji WriteLine do umieszczania powiązanych instrukcji na osobnych liniach może utrudnić rozróżnienie, które informacje są ze sobą powiązane. Ogólnie rzecz biorąc, użyj wielu instrukcji Write, jeśli chcesz połączyć informacje z wielu źródeł, aby utworzyć pojedynczy komunikat informacyjny. Użyj instrukcji WriteLine, jeśli chcesz utworzyć pojedynczy pełny komunikat.

Debug.Write("Debug - ");
Debug.WriteLine("This is a full line.");
Debug.WriteLine("This is another full line.");

Te dane wyjściowe pochodzą z poprzedniego rejestrowania przy użyciu Debug:

Debug - This is a full line.
This is another full line.

Definiowanie stałych TRACE i DEBUG

Domyślnie, gdy aplikacja jest uruchomiona w ramach debugowania, zdefiniowana jest stała DEBUG. Możesz to kontrolować, dodając wpis DefineConstants w pliku projektu w grupie właściwości. Oto przykład włączania TRACE zarówno dla konfiguracji Debug, jak i Release, a także DEBUG dla konfiguracji Debug.

<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|AnyCPU'">
    <DefineConstants>DEBUG;TRACE</DefineConstants>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|AnyCPU'">
    <DefineConstants>TRACE</DefineConstants>
</PropertyGroup>

Jeśli używasz Trace, gdy nie jest on podłączony do debugera, musisz skonfigurować odbiornik śledzenia, taki jak dotnet-trace.

Śledzenie warunkowe

Oprócz prostych metod Write i WriteLine istnieje również możliwość dodawania warunków za pomocą WriteIf i WriteLineIf. Na przykład poniższa logika sprawdza, czy liczba jest równa zero, a następnie zapisuje komunikat debugowania:

if(count == 0)
{
    Debug.WriteLine("The count is 0 and this may cause an exception.");
}

Można to napisać ponownie w jednym wierszu kodu:

Debug.WriteLineIf(count == 0, "The count is 0 and this may cause an exception.");

Możesz również użyć tych warunków z Trace i flagami zdefiniowanymi w aplikacji:

bool errorFlag = false;  
System.Diagnostics.Trace.WriteIf(errorFlag, "Error in AppendData procedure.");  
System.Diagnostics.Debug.WriteIf(errorFlag, "Transaction abandoned.");  
System.Diagnostics.Trace.Write("Invalid value for data request");

Sprawdź, czy istnieją pewne warunki

Instrukcja asercji lub instrukcja Assert testuje warunek, który określasz jako argument do instrukcji Assert. Jeśli warunek ma wartość true, nie ma żadnej akcji. Jeśli warunek zwróci wartość false, asercja zakończy się niepowodzeniem. Jeśli używasz wersji debugowej, program przechodzi w tryb debugowania.

Możesz użyć metody Assert z Debug lub Trace, które znajdują się w przestrzeni nazw System.Diagnostics. Debug metody klas nie są uwzględniane w wersji produkcyjnej programu, więc nie zwiększają rozmiaru ani nie zmniejszają szybkości kodu produkcyjnego.

Użyj metody System.Diagnostics.Debug.Assert swobodnie, aby przetestować warunki, które powinny być prawdziwe, jeśli kod jest poprawny. Załóżmy na przykład, że utworzono funkcję dzielenia liczb całkowitych. Zgodnie z zasadami matematyki podział nigdy nie może być zerowy. Ten warunek można przetestować przy użyciu asercji:

int IntegerDivide(int dividend, int divisor)
{
    Debug.Assert(divisor != 0, $"{nameof(divisor)} is 0 and will cause an exception.");

    return dividend / divisor;
}

Podczas uruchamiania tego kodu w debugerze, asercja jest sprawdzana. Jednak porównanie nie jest wykonywane w wersji wydania, więc nie ma dodatkowych obciążeń.

Notatka

Jeśli używasz System.Diagnostics.Debug.Assert, upewnij się, że żaden kod w Assert nie zmienia wyników programu, jeśli asercja zostanie usunięta. W przeciwnym razie możesz przypadkowo wprowadzić usterkę, która pojawi się tylko w wersji release twojego programu. Zachowaj szczególną ostrożność w przypadku asercji zawierających wywołania funkcji lub procedury.

Używanie Debug i Trace z przestrzeni nazw System.Diagnostics to doskonały sposób zapewnienia dodatkowego kontekstu podczas uruchamiania i debugowania aplikacji.