Udostępnij za pośrednictwem


Dodawanie instrumentacji śledzenia rozproszonego

Ten artykuł dotyczy: ✔️ .NET Core 2.1 i nowsze wersje ✔️ programu .NET Framework 4.5 i nowszych wersji

Aplikacje platformy .NET można instrumentować przy użyciu interfejsu System.Diagnostics.Activity API do tworzenia telemetrii śledzenia rozproszonego. Niektóre instrumentacje są wbudowane w standardowe biblioteki .NET, ale warto dodać więcej, aby ułatwić rozpoznanie kodu. W tym samouczku dodasz niestandardową, rozproszoną instrumentację śledzenia. Zobacz samouczek dotyczący zbierania danych, aby dowiedzieć się więcej na temat rejestrowania danych telemetrycznych generowanych przez tę instrumentację.

Wymagania wstępne

Tworzenie początkowej aplikacji

Najpierw utworzysz przykładową aplikację, która zbiera dane telemetryczne przy użyciu biblioteki OpenTelemetry, ale nie ma jeszcze żadnej instrumentacji.

dotnet new console

Aplikacje, które korzystają z platformy .NET 5 i nowszych, zawierają już niezbędne API śledzenia rozproszonego. W przypadku aplikacji przeznaczonych dla starszych wersji platformy .NET dodaj pakiet NuGet System.Diagnostics.DiagnosticSource w wersji 5 lub nowszej. W przypadku bibliotek docelowych netstandard zalecamy odwoływanie się do najstarszej wersji pakietu, która jest nadal obsługiwana i zawiera interfejsy API wymagane przez bibliotekę.

dotnet add package System.Diagnostics.DiagnosticSource

Dodaj pakiety NuGet OpenTelemetry i OpenTelemetry.Exporter.Console, które będą używane do zbierania danych telemetrycznych.

dotnet add package OpenTelemetry
dotnet add package OpenTelemetry.Exporter.Console

Zastąp zawartość wygenerowanego Program.cs tym przykładowym źródłem:

using OpenTelemetry;
using OpenTelemetry.Resources;
using OpenTelemetry.Trace;
using System;
using System.Threading.Tasks;

namespace Sample.DistributedTracing
{
    class Program
    {
        static async Task Main(string[] args)
        {
            using var tracerProvider = Sdk.CreateTracerProviderBuilder()
                .SetResourceBuilder(ResourceBuilder.CreateDefault().AddService("MySample"))
                .AddSource("Sample.DistributedTracing")
                .AddConsoleExporter()
                .Build();

            await DoSomeWork("banana", 8);
            Console.WriteLine("Example work done");
        }

        // All the functions below simulate doing some arbitrary work
        static async Task DoSomeWork(string foo, int bar)
        {
            await StepOne();
            await StepTwo();
        }

        static async Task StepOne()
        {
            await Task.Delay(500);
        }

        static async Task StepTwo()
        {
            await Task.Delay(1000);
        }
    }
}

Aplikacja nie ma jeszcze instrumentacji, więc nie ma żadnych danych śledzenia do wyświetlenia.

> dotnet run
Example work done

Najlepsze rozwiązania

Tylko deweloperzy aplikacji muszą odwoływać się do opcjonalnej zewnętrznej biblioteki w celu zbierania rozproszonej telemetrii śledzenia, takiej jak OpenTelemetry w tym przykładzie. Autorzy bibliotek platformy .NET mogą wyłącznie polegać na interfejsach API w pliku System.Diagnostics.DiagnosticSource, który jest częścią środowiska uruchomieniowego platformy .NET. Dzięki temu biblioteki będą działać w szerokim zakresie aplikacji platformy .NET, niezależnie od preferencji dewelopera aplikacji dotyczących biblioteki lub dostawcy do zbierania danych telemetrycznych.

Dodawanie instrumentacji podstawowej

Aplikacje i biblioteki dodają instrumentację śledzenia rozproszonego przy użyciu klas System.Diagnostics.ActivitySource i System.Diagnostics.Activity.

Źródło Aktywności

Najpierw utwórz wystąpienie elementu ActivitySource. Usługa ActivitySource udostępnia interfejsy API do tworzenia i uruchamiania obiektów działania. Dodaj statyczną zmienną ActivitySource powyżej Main() i using System.Diagnostics; do using dyrektyw.

using OpenTelemetry;
using OpenTelemetry.Resources;
using OpenTelemetry.Trace;
using System;
using System.Diagnostics;
using System.Threading.Tasks;

namespace Sample.DistributedTracing
{
    class Program
    {
        private static ActivitySource source = new ActivitySource("Sample.DistributedTracing", "1.0.0");

        static async Task Main(string[] args)
        {
            // ...

Najlepsze rozwiązania

  • Utwórz ActivitySource raz, zapisz go w zmiennej statycznej i używaj tego wystąpienia tak długo, jak będzie potrzebne. Każda biblioteka lub podkomponent biblioteki może (i często powinna) utworzyć własne źródło. Rozważ utworzenie nowego źródła zamiast ponownego korzystania z istniejącego źródła, jeśli przewidujesz, że deweloperzy aplikacji docenią możliwość włączenia i wyłączenia telemetrii działania w źródłach niezależnie.

  • Nazwa źródła przekazana do konstruktora musi być unikatowa, aby uniknąć konfliktów z innymi źródłami. Jeśli istnieje wiele źródeł w tym samym zestawie, użyj nazwy hierarchicznej zawierającej nazwę zestawu i opcjonalnie nazwę składnika, na przykład Microsoft.AspNetCore.Hosting. Jeśli zestaw dodaje instrumentację dla kodu w drugim, niezależnym zestawie, nazwa powinna być oparta na zestawie, który definiuje element ActivitySource, a nie zestaw, którego kod jest instrumentowany.

  • Parametr wersji jest opcjonalny. Zalecamy podanie wersji w przypadku wydania wielu wersji biblioteki i wprowadzenia zmian w instrumentowanej telemetrii.

Uwaga

OpenTelemetry stosuje alternatywne terminy "Tracer" i "Span". Na platformie .NET "ActivitySource" jest implementacją Tracera, a "Activity" jest implementacją "Span". Typ Activity w .NET znacznie poprzedza specyfikację OpenTelemetry, a zachowane oryginalne nazewnictwo ma na celu spójność w ekosystemie .NET i zgodność aplikacji .NET.

Aktywność

Użyj obiektu ActivitySource, aby uruchomić i zatrzymać obiekty działania wokół znaczących jednostek pracy. Zaktualizuj aplikację DoSomeWork() przy użyciu kodu pokazanego tutaj:

static async Task DoSomeWork(string foo, int bar)
{
    using (Activity activity = source.StartActivity("SomeWork"))
    {
        await StepOne();
        await StepTwo();
    }
}

Uruchomienie aplikacji spowoduje teraz wyświetlenie nowego zarejestrowanego działania:

> dotnet run
Activity.Id:          00-f443e487a4998c41a6fd6fe88bae644e-5b7253de08ed474f-01
Activity.DisplayName: SomeWork
Activity.Kind:        Internal
Activity.StartTime:   2021-03-18T10:36:51.4720202Z
Activity.Duration:    00:00:01.5025842
Resource associated with Activity:
    service.name: MySample
    service.instance.id: 067f4bb5-a5a8-4898-a288-dec569d6dbef

Notatki

  • ActivitySource.StartActivity tworzy i uruchamia działanie w tym samym czasie. Wymieniony wzorzec kodu używa bloku using, który automatycznie usuwa utworzony obiekt Activity po wykonaniu bloku. Usuwanie obiektu Activity spowoduje zatrzymanie go, aby kod nie musiał jawnie wywoływać elementu Activity.Stop(). Upraszcza to wzorzec kodowania.

  • ActivitySource.StartActivity wewnętrznie określa, czy istnieją odbiorniki rejestrujące działanie. Jeśli nie ma zarejestrowanych odbiorników lub istnieją odbiorniki, które nie są zainteresowane, StartActivity() zwróci null i uniknie tworzenia obiektu Activity. Jest to optymalizacja wydajności, dzięki czemu wzorzec kodu może być nadal używany w funkcjach, które są często wywoływane.

Opcjonalne: Wypełnianie tagów

Działania obsługują dane klucz-wartość o nazwie Tagi, często używane do przechowywania dowolnych parametrów pracy, które mogą być przydatne do diagnostyki. Zaktualizuj DoSomeWork() , aby uwzględnić je:

static async Task DoSomeWork(string foo, int bar)
{
    using (Activity activity = source.StartActivity("SomeWork"))
    {
        activity?.SetTag("foo", foo);
        activity?.SetTag("bar", bar);
        await StepOne();
        await StepTwo();
    }
}
> dotnet run
Activity.Id:          00-2b56072db8cb5a4496a4bfb69f46aa06-7bc4acda3b9cce4d-01
Activity.DisplayName: SomeWork
Activity.Kind:        Internal
Activity.StartTime:   2021-03-18T10:37:31.4949570Z
Activity.Duration:    00:00:01.5417719
Activity.TagObjects:
    foo: banana
    bar: 8
Resource associated with Activity:
    service.name: MySample
    service.instance.id: 25bbc1c3-2de5-48d9-9333-062377fea49c

Example work done

Najlepsze rozwiązania

  • Jak wspomniano powyżej, activity zwracane przez ActivitySource.StartActivity może mieć wartość null. Operator ?. łączenia wartości null w języku C# jest wygodnym skrótem do wywoływania Activity.SetTag tylko wtedy, gdy activity nie ma wartości null. Zachowanie jest identyczne z pisaniem:
if(activity != null)
{
    activity.SetTag("foo", foo);
}
  • Funkcja OpenTelemetry udostępnia zestaw zalecanych konwencji ustawiania tagów dla działań reprezentujących typowe typy pracy aplikacji.

  • Jeśli instrumentujesz funkcje z wymaganiami o wysokiej wydajności, Activity.IsAllDataRequested jest wskazówką, która wskazuje, czy którykolwiek z kodów nasłuchujących Aktywności zamierza odczytywać informacje pomocnicze, takie jak tagi. Jeśli odbiornik go nie odczytuje, nie ma potrzeby, aby instrumentowany kod zużywał cykle procesora. Dla uproszczenia ten przykład nie stosuje tej optymalizacji.

Opcjonalne: Dodawanie zdarzeń

Zdarzenia to komunikaty ze znacznikami czasu, które mogą dołączać dowolny strumień dodatkowych danych diagnostycznych do działań. Dodaj kilka zdarzeń do aktywności.

static async Task DoSomeWork(string foo, int bar)
{
    using (Activity activity = source.StartActivity("SomeWork"))
    {
        activity?.SetTag("foo", foo);
        activity?.SetTag("bar", bar);
        await StepOne();
        activity?.AddEvent(new ActivityEvent("Part way there"));
        await StepTwo();
        activity?.AddEvent(new ActivityEvent("Done now"));
    }
}
> dotnet run
Activity.Id:          00-82cf6ea92661b84d9fd881731741d04e-33fff2835a03c041-01
Activity.DisplayName: SomeWork
Activity.Kind:        Internal
Activity.StartTime:   2021-03-18T10:39:10.6902609Z
Activity.Duration:    00:00:01.5147582
Activity.TagObjects:
    foo: banana
    bar: 8
Activity.Events:
    Part way there [3/18/2021 10:39:11 AM +00:00]
    Done now [3/18/2021 10:39:12 AM +00:00]
Resource associated with Activity:
    service.name: MySample
    service.instance.id: ea7f0fcb-3673-48e0-b6ce-e4af5a86ce4f

Example work done

Najlepsze rozwiązania

  • Zdarzenia są przechowywane na liście w pamięci do momentu ich przesyłania, co sprawia, że ten mechanizm nadaje się tylko do rejestrowania niewielkiej liczby zdarzeń. W przypadku dużej lub niezwiązanej liczby zdarzeń lepiej użyć interfejsu API rejestrowania skoncentrowanego na tym zadaniu, takim jak ILogger. Usługa ILogger zapewnia również, że informacje rejestrowania będą dostępne niezależnie od tego, czy deweloper aplikacji zdecyduje się na korzystanie z śledzenia rozproszonego. Aplikacja ILogger obsługuje automatyczne przechwytywanie aktywnych identyfikatorów działań, dzięki czemu komunikaty rejestrowane za pośrednictwem tego interfejsu API mogą być nadal skorelowane ze śladem rozproszonym.

Opcjonalnie: Dodawanie stanu

Funkcja OpenTelemetry umożliwia każdemu z działań zgłaszanie stanu reprezentującego wynik powodzenia/niepowodzenia pracy. W tym celu platforma .NET ma silnie typizowany interfejs API:

Wartości ActivityStatusCode są reprezentowane jako , Unset, Oki Error.

Zaktualizuj program DoSomeWork(), aby ustawić stan:

static async Task DoSomeWork(string foo, int bar)
{
    using (Activity activity = source.StartActivity("SomeWork"))
    {
        activity?.SetTag("foo", foo);
        activity?.SetTag("bar", bar);
        await StepOne();
        activity?.AddEvent(new ActivityEvent("Part way there"));
        await StepTwo();
        activity?.AddEvent(new ActivityEvent("Done now"));

        // Pretend something went wrong
        activity?.SetStatus(ActivityStatusCode.Error, "Use this text give more information about the error");
    }
}

Opcjonalnie: Dodawanie dodatkowych działań

Działania można zagnieżdżać, aby opisać fragmenty większej całości pracy. Może to być przydatne w przypadku fragmentów kodu, które mogą nie być wykonywane szybko lub lepiej lokalizować błędy pochodzące z określonych zależności zewnętrznych. Mimo że w tym przykładzie użyto działania w każdej metodzie, jest to wyłącznie dlatego, że dodatkowy kod został zminimalizowany. W większym i bardziej realistycznym projekcie, użycie Activity w każdej metodzie spowodowałoby nadmiernie szczegółowe ślady, dlatego nie jest to zalecane.

Zaktualizuj StepOne i StepTwo, aby dodać więcej śledzenia wokół tych oddzielnych kroków.

static async Task StepOne()
{
    using (Activity activity = source.StartActivity("StepOne"))
    {
        await Task.Delay(500);
    }
}

static async Task StepTwo()
{
    using (Activity activity = source.StartActivity("StepTwo"))
    {
        await Task.Delay(1000);
    }
}
> dotnet run
Activity.Id:          00-9d5aa439e0df7e49b4abff8d2d5329a9-39cac574e8fda44b-01
Activity.ParentId:    00-9d5aa439e0df7e49b4abff8d2d5329a9-f16529d0b7c49e44-01
Activity.DisplayName: StepOne
Activity.Kind:        Internal
Activity.StartTime:   2021-03-18T10:40:51.4278822Z
Activity.Duration:    00:00:00.5051364
Resource associated with Activity:
    service.name: MySample
    service.instance.id: e0a8c12c-249d-4bdd-8180-8931b9b6e8d0

Activity.Id:          00-9d5aa439e0df7e49b4abff8d2d5329a9-4ccccb6efdc59546-01
Activity.ParentId:    00-9d5aa439e0df7e49b4abff8d2d5329a9-f16529d0b7c49e44-01
Activity.DisplayName: StepTwo
Activity.Kind:        Internal
Activity.StartTime:   2021-03-18T10:40:51.9441095Z
Activity.Duration:    00:00:01.0052729
Resource associated with Activity:
    service.name: MySample
    service.instance.id: e0a8c12c-249d-4bdd-8180-8931b9b6e8d0

Activity.Id:          00-9d5aa439e0df7e49b4abff8d2d5329a9-f16529d0b7c49e44-01
Activity.DisplayName: SomeWork
Activity.Kind:        Internal
Activity.StartTime:   2021-03-18T10:40:51.4256627Z
Activity.Duration:    00:00:01.5286408
Activity.TagObjects:
    foo: banana
    bar: 8
    otel.status_code: ERROR
    otel.status_description: Use this text give more information about the error
Activity.Events:
    Part way there [3/18/2021 10:40:51 AM +00:00]
    Done now [3/18/2021 10:40:52 AM +00:00]
Resource associated with Activity:
    service.name: MySample
    service.instance.id: e0a8c12c-249d-4bdd-8180-8931b9b6e8d0

Example work done

Zwróć uwagę, że zarówno StepOne, jak i StepTwo zawierają identyfikator ParentId, który odnosi się do SomeWork. Konsola nie jest najlepszą wizualizacją zagnieżdżonych struktur pracy, ale wiele narzędzi z graficznym interfejsem użytkownika, takich jak Zipkin, może to pokazać jako wykres Gantta.

Wykres Gantta Zipkin

Opcjonalnie: ActivityKind

Działania mają właściwość Activity.Kind, która opisuje relację między działaniem, jego elementem nadrzędnym i elementami podrzędnymi. Domyślnie wszystkie nowe Aktywności są ustawione na wartość Internal, co jest odpowiednie dla Aktywności będących operacją wewnętrzną w aplikacji, bez zdalnego rodzica lub dzieci. Inne rodzaje można ustawić przy użyciu parametru kind w pliku ActivitySource.StartActivity. Aby uzyskać inne opcje, zobacz System.Diagnostics.ActivityKind.

W przypadku pracy w systemach przetwarzania wsadowego pojedyncza aktywność może reprezentować pracę na rzecz wielu różnych żądań jednocześnie, z których każde ma własny identyfikator śledzenia. Mimo że aktywność jest ograniczona do pojedynczego elementu nadrzędnego, może łączyć się z dodatkowymi identyfikatorami śledzenia przy użyciu System.Diagnostics.ActivityLink. Każdy element ActivityLink jest wypełniany za pomocą elementu ActivityContext, który przechowuje informacje o identyfikatorze aktywności, do której jest połączony. Element ActivityContext można pobrać z obiektów działań w procesie przy użyciu Activity.Context lub można go przeanalizować z zserializowanych danych identyfikacyjnych przy użyciu ActivityContext.Parse(String, String).

void DoBatchWork(ActivityContext[] requestContexts)
{
    // Assume each context in requestContexts encodes the trace-id that was sent with a request
    using(Activity activity = s_source.StartActivity(name: "BigBatchOfWork",
                                                     kind: ActivityKind.Internal,
                                                     parentContext: default,
                                                     links: requestContexts.Select(ctx => new ActivityLink(ctx))
    {
        // do the batch of work here
    }
}

W przeciwieństwie do zdarzeń i tagów, które można dodać na żądanie, linki należy dodawać podczas StartActivity() i są następnie niezmienne.

Ważne

Zgodnie ze specyfikacją OpenTelemetry sugerowany limit liczby łączy wynosi 128. Należy jednak pamiętać, że ten limit nie jest wymuszany.