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 nową niestandardową instrumentację śledzenia rozproszonego. Zobacz samouczek dotyczący kolekcji, aby dowiedzieć się więcej na temat rejestrowania danych telemetrycznych generowanych przez tę instrumentację.
Wymagania wstępne
- Zestaw .NET Core 2.1 SDK lub nowsza wersja
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 przeznaczone dla platformy .NET 5 i nowszych mają już dołączone niezbędne interfejsy API śledzenia rozproszonego. W przypadku aplikacji przeznaczonych dla starszych wersji platformy .NET dodaj pakiet NuGet System.Diagnostics.DiagnosticSource w wersji 5 lub nowszej.
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 pliku Program.cs następującym 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 informacji śledzenia do wyświetlenia:
> dotnet run
Example work done
Najlepsze rozwiązania
Tylko deweloperzy aplikacji muszą odwoływać się do opcjonalnej biblioteki innej firmy w celu zbierania rozproszonych danych telemetrycznych śledzenia, takich 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 System.Diagnostics.ActivitySource przy użyciu klas i System.Diagnostics.Activity .
ActivitySource
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 instrukcji using.
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 element ActivitySource raz, zapisz go w zmiennej statycznej i użyj tego wystąpienia tak długo, jak to konieczne. 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
Funkcja OpenTelemetry używa alternatywnych terminów "Tracer" i "Span". Na platformie .NET element "ActivitySource" jest implementacją narzędzia Tracer, a działanie to implementacja "Span". . Typ działania platformy NET ma długie daty wstępne specyfikacji OpenTelemetry i oryginalne nazewnictwo platformy .NET zostały zachowane pod kątem spójności w ekosystemie platformy .NET i zgodności aplikacji platformy .NET.
Działanie
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
Uwagi
ActivitySource.StartActivity tworzy i uruchamia działanie w tym samym czasie. Wzorzec kodu wymienionego
using
używa bloku, który automatycznie usuwa utworzony obiekt Działania 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()
zostanie zwróconynull
i uniknąć tworzenia obiektu Działania. 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 program 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, gdyactivity
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, jest wskazówką wskazującą, Activity.IsAllDataRequested czy którykolwiek kod nasłuchiwania działań zamierza odczytywać informacje pomocnicze, takie jak Tagi. Jeśli odbiornik go nie odczytuje, nie ma potrzeby, aby instrumentowany kod spędzał na nim cykle procesora CPU. 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 działania:
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 nie ma obecnie silnie typizowanego interfejsu API, ale istnieje ustanowiona konwencja używająca tagów:
otel.status_code
to nazwa tagu używana do przechowywaniaStatusCode
. Wartości tagu StatusCode muszą być jednym z ciągów "UNSET", "OK" lub "ERROR", które odpowiadają odpowiednio wyliczeniomUnset
,Ok
iError
ze statusCode.otel.status_description
to nazwa tagu używana do przechowywania opcjonalnegoDescription
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?.SetTag("otel.status_code", "ERROR");
activity?.SetTag("otel.status_description", "Use this text give more information about the error");
}
}
Opcjonalnie: Dodawanie dodatkowych działań
Działania można zagnieżdżać w celu opisania części większej jednostki 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 działania w każdej metodzie spowoduje wygenerowanie bardzo pełnych śladów, więc nie jest zalecane.
Zaktualizuj instrukcje StepOne i StepTwo, aby dodać więcej śledzenia wokół następujących 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 świetną wizualizacją zagnieżdżonych drzew pracy, ale wiele osób przeglądających graficzny interfejs użytkownika, takich jak Zipkin , może pokazać to jako wykres Gantta:
Opcjonalnie: ActivityKind
Działania mają właściwość, która opisuje relację Activity.Kind między działaniem, jego elementem nadrzędnym i elementami podrzędnym. Domyślnie wszystkie nowe działania są ustawione na Internalwartość , która jest odpowiednia dla działań, które są operacją wewnętrzną w aplikacji bez zdalnego elementu nadrzędnego lub elementów podrzędnych. Inne rodzaje można ustawić przy użyciu parametru kind w pliku ActivitySource.StartActivity. Aby uzyskać inne opcje, zobacz System.Diagnostics.ActivityKind.
Opcjonalne: łącza
W przypadku pracy w systemach przetwarzania wsadowego pojedyncze działanie może reprezentować pracę w imieniu wielu różnych żądań jednocześnie, z których każdy ma własny identyfikator śledzenia. Mimo że działanie jest ograniczone do pojedynczego elementu nadrzędnego, może łączyć się z dodatkowymi identyfikatorami śledzenia przy użyciu polecenia System.Diagnostics.ActivityLink. Każdy element ActivityLink jest wypełniany za pomocą elementu ActivityContext , który przechowuje informacje o identyfikatorze działania, z którymi 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 serializowanych informacji o identyfikatorze przy użyciu polecenia 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 muszą być dodawane podczas funkcji StartActivity() i są następnie niezmienne.