Instrumentatie voor gedistribueerde tracering toevoegen
Dit artikel is van toepassing op: ✔️ .NET Core 2.1 en latere versies ✔️ .NET Framework 4.5 en latere versies
.NET-toepassingen kunnen worden geïnstrueerd met behulp van de System.Diagnostics.Activity API voor het produceren van telemetriegegevens voor gedistribueerde tracering. Sommige instrumentatie is ingebouwd in standaard .NET-bibliotheken, maar misschien wilt u meer toevoegen om uw code gemakkelijker diagnosebaar te maken. In deze zelfstudie voegt u nieuwe aangepaste instrumentatie voor gedistribueerde tracering toe. Zie de zelfstudie voor verzamelingen voor meer informatie over het vastleggen van de telemetrie die door deze instrumentatie wordt geproduceerd.
Vereisten
- .NET Core 2.1 SDK of een latere versie
Eerste app maken
Eerst maakt u een voorbeeld-app die telemetrie verzamelt met behulp van OpenTelemetry, maar nog geen instrumentatie heeft.
dotnet new console
Toepassingen die gericht zijn op .NET 5 en hoger, hebben al de benodigde gedistribueerde tracerings-API's opgenomen. Voor apps die zijn gericht op oudere .NET-versies, voegt u het NuGet-pakket System.Diagnostics.DiagnosticSource versie 5 of hoger toe.
dotnet add package System.Diagnostics.DiagnosticSource
Voeg de NuGet-pakketten OpenTelemetry en OpenTelemetry.Exporter.Console toe, die worden gebruikt om de telemetrie te verzamelen.
dotnet add package OpenTelemetry
dotnet add package OpenTelemetry.Exporter.Console
Vervang de inhoud van het gegenereerde Program.cs door deze voorbeeldbron:
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);
}
}
}
De app heeft nog geen instrumentatie, dus er zijn geen traceringsgegevens om weer te geven:
> dotnet run
Example work done
Aanbevolen procedures
Alleen app-ontwikkelaars moeten verwijzen naar een optionele bibliotheek van derden voor het verzamelen van de gedistribueerde traceringstelemetrie, zoals OpenTelemetry in dit voorbeeld. Auteurs van .NET-bibliotheken kunnen uitsluitend afhankelijk zijn van API's in System.Diagnostics.DiagnosticSource, die deel uitmaakt van .NET Runtime. Dit zorgt ervoor dat bibliotheken worden uitgevoerd in een breed scala aan .NET-apps, ongeacht de voorkeuren van de ontwikkelaar van de app over welke bibliotheek of leverancier moet worden gebruikt voor het verzamelen van telemetrie.
Basis instrumentatie toevoegen
Toepassingen en bibliotheken voegen gedistribueerde traceringsinstrumentatie toe met behulp van de System.Diagnostics.ActivitySource en System.Diagnostics.Activity klassen.
ActivitySource
Maak eerst een exemplaar van ActivitySource. ActivitySource biedt API's voor het maken en starten van activiteitsobjecten. Voeg de statische ActivitySource-variabele boven Main() en using System.Diagnostics;
de using-instructies toe.
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)
{
...
Aanbevolen procedures
Maak de ActivitySource eenmaal, sla deze op in een statische variabele en gebruik dat exemplaar zolang nodig. Elke bibliotheek of bibliotheeksubcomponent kan (en vaak) een eigen bron maken. Overweeg om een nieuwe bron te maken in plaats van een bestaande bron opnieuw te gebruiken als u verwacht dat app-ontwikkelaars de telemetrie van activiteit in de bronnen onafhankelijk kunnen in- en uitschakelen.
De bronnaam die aan de constructor is doorgegeven, moet uniek zijn om conflicten met andere bronnen te voorkomen. Als er meerdere bronnen binnen dezelfde assembly zijn, gebruikt u een hiërarchische naam die de assemblynaam en eventueel een onderdeelnaam bevat, bijvoorbeeld
Microsoft.AspNetCore.Hosting
. Als een assembly instrumentatie voor code toevoegt in een tweede, onafhankelijke assembly, moet de naam zijn gebaseerd op de assembly die de ActivitySource definieert, niet de assembly waarvan de code wordt geïnstrueerd.De versieparameter is optioneel. U wordt aangeraden de versie op te geven voor het geval u meerdere versies van de bibliotheek vrijgeeft en wijzigingen aanbrengt in de geïnstrueerde telemetrie.
Notitie
OpenTelemetry maakt gebruik van alternatieve termen 'Tracer' en 'Span'. In .NET ActivitySource is de implementatie van Tracer en Activity de implementatie van 'Span'. . Het activiteitstype van NET lange voordatums de OpenTelemetry-specificatie en de oorspronkelijke .NET-naamgeving is behouden voor consistentie binnen het .NET-ecosysteem en .NET-toepassingscompatibiliteit.
Activiteit
Gebruik het ActivitySource-object om activiteitsobjecten te starten en stoppen rond zinvolle werkeenheden. Werk DoSomeWork() bij met de code die hier wordt weergegeven:
static async Task DoSomeWork(string foo, int bar)
{
using (Activity activity = source.StartActivity("SomeWork"))
{
await StepOne();
await StepTwo();
}
}
Als u de app uitvoert, wordt nu de nieuwe activiteit weergegeven die wordt geregistreerd:
> 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
Opmerkingen
ActivitySource.StartActivity maakt en start de activiteit tegelijkertijd. Het vermelde codepatroon maakt gebruik van het
using
blok, waarmee het gemaakte activiteitsobject automatisch wordt verwijderd nadat het blok is uitgevoerd. Als u het activiteitsobject opstelt, wordt het gestopt, zodat de code niet expliciet hoeft aan te roepen Activity.Stop(). Dat vereenvoudigt het coderingspatroon.ActivitySource.StartActivity bepaalt intern of er listeners zijn die de activiteit opnemen. Als er geen geregistreerde listeners zijn of als er listeners zijn die niet geïnteresseerd zijn,
StartActivity()
wordt het activiteitsobject geretourneerdnull
en voorkomen dat het object Activiteit wordt gemaakt. Dit is een optimalisatie van prestaties, zodat het codepatroon nog steeds kan worden gebruikt in functies die vaak worden aangeroepen.
Optioneel: tags vullen
Activiteiten ondersteunen sleutelwaardegegevens met de naam Tags, die vaak worden gebruikt om parameters van het werk op te slaan die nuttig kunnen zijn voor diagnostische gegevens. Werk DoSomeWork() bij om ze op te nemen:
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
Aanbevolen procedures
- Zoals hierboven vermeld,
activity
kan het ActivitySource.StartActivity resultaat null zijn. De operator?.
null-coalescing in C# is een handige korte hand om alleen aan te roepen Activity.SetTag alsactivity
deze niet null is. Het gedrag is identiek aan het schrijven:
if(activity != null)
{
activity.SetTag("foo", foo);
}
OpenTelemetry biedt een set aanbevolen conventies voor het instellen van tags voor activiteiten die algemene typen toepassingswerk vertegenwoordigen.
Als u functies met hoge prestatievereisten instrumenteert, Activity.IsAllDataRequested is dit een hint die aangeeft of een van de code die luistert naar Activiteiten hulpinformatie zoals Tags wil lezen. Als er geen listener deze leest, is het niet nodig dat de geïnstrueerde code CPU-cycli doorgeeft die deze vullen. Ter vereenvoudiging past dit voorbeeld die optimalisatie niet toe.
Optioneel: Gebeurtenissen toevoegen
Gebeurtenissen zijn tijdstempelberichten die een willekeurige stroom van aanvullende diagnostische gegevens aan activiteiten kunnen koppelen. Voeg enkele gebeurtenissen toe aan de activiteit:
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
Aanbevolen procedures
- Gebeurtenissen worden opgeslagen in een in-memory lijst totdat ze kunnen worden verzonden, waardoor dit mechanisme alleen geschikt is voor het opnemen van een bescheiden aantal gebeurtenissen. Voor een groot of niet-gebonden volume aan gebeurtenissen is het beter om een logboekregistratie-API te gebruiken die is gericht op deze taak, zoals ILogger. ILogger zorgt er ook voor dat de logboekgegevens beschikbaar zijn, ongeacht of de app-ontwikkelaar ervoor kiest gedistribueerde tracering te gebruiken. ILogger biedt ondersteuning voor het automatisch vastleggen van de actieve activiteit-id's, zodat berichten die via die API zijn vastgelegd, nog steeds kunnen worden gecorreleerd met de gedistribueerde trace.
Optioneel: Status toevoegen
Met OpenTelemetry kan elke activiteit een status rapporteren die het geslaagde/mislukte resultaat van het werk aangeeft. .NET heeft momenteel geen sterk getypte API voor dit doel, maar er is een vastgestelde conventie met behulp van tags:
otel.status_code
is de tagnaam die wordt gebruikt om op te slaanStatusCode
. Waarden voor de StatusCode-tag moeten een van de tekenreeksen UNSET, OK of ERROR zijn, die respectievelijk overeenkomen met de opsommingenUnset
,Ok
enError
van StatusCode.otel.status_description
is de tagnaam die wordt gebruikt om de optionele op te slaanDescription
Werk DoSomeWork() bij om de status in te stellen:
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");
}
}
Optioneel: Extra activiteiten toevoegen
Activiteiten kunnen worden genest om gedeelten van een grotere werkeenheid te beschrijven. Dit kan waardevol zijn rond delen van code die mogelijk niet snel worden uitgevoerd of om fouten die afkomstig zijn van specifieke externe afhankelijkheden beter te lokaliseren. Hoewel in dit voorbeeld in elke methode een activiteit wordt gebruikt, is dat alleen omdat extra code is geminimaliseerd. In een groter en realistischer project zou het gebruik van een activiteit in elke methode extreem uitgebreide traceringen produceren, dus het wordt niet aanbevolen.
Werk StepOne en StepTwo bij om meer tracering toe te voegen rond deze afzonderlijke stappen:
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
U ziet dat zowel StepOne als StepTwo een ParentId bevatten die verwijst naar SomeWork. De console is geen geweldige visualisatie van geneste werkstructuren, maar veel GUI-viewers, zoals Zipkin , kunnen dit weergeven als een Gantt-diagram:
Optioneel: ActivityKind
Activiteiten hebben een Activity.Kind eigenschap, die de relatie beschrijft tussen de activiteit, het bovenliggende item en de onderliggende items. Standaard zijn alle nieuwe activiteiten ingesteld op Internal, wat geschikt is voor activiteiten die een interne bewerking binnen een toepassing zijn zonder externe bovenliggende of onderliggende elementen. Andere soorten kunnen worden ingesteld met behulp van de soortparameter op ActivitySource.StartActivity. Zie voor andere opties System.Diagnostics.ActivityKind.
Optioneel: Koppelingen
Wanneer werk plaatsvindt in batchverwerkingssystemen, kan één activiteit werk vertegenwoordigen namens veel verschillende aanvragen tegelijk, die elk een eigen tracerings-id hebben. Hoewel activiteit is beperkt tot één bovenliggend element, kan deze een koppeling maken naar aanvullende trace-id's met behulp van System.Diagnostics.ActivityLink. Elke ActivityLink wordt gevuld met een ActivityContext id-informatie over de activiteit waaraan wordt gekoppeld. ActivityContext kan worden opgehaald uit in-process activity-objecten met behulp van Activity.Context of kan worden geparseerd uit geserialiseerde id-informatie met behulp van 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
}
}
In tegenstelling tot gebeurtenissen en tags die on-demand kunnen worden toegevoegd, moeten koppelingen worden toegevoegd tijdens StartActivity() en daarna onveranderbaar zijn.