Lägga till distribuerad spårningsinstrumentation
Den här artikeln gäller för: ✔️ .NET Core 2.1 och senare versioner✔️ .NET Framework 4.5 och senare versioner
.NET-program kan instrumenteras med hjälp av API:et System.Diagnostics.Activity för att producera distribuerad spårningstelemetri. En del instrumentation är inbyggd i .NET-standardbibliotek, men du kanske vill lägga till mer för att göra koden enklare att diagnostisera. I den här självstudien lägger du till nya anpassade distribuerade spårningsinstrumentation. Se samlingsguiden för att lära dig mer om att spela in telemetrin som produceras av den här instrumentationen.
Förutsättningar
- .NET Core 2.1 SDK eller en senare version
Skapa en första app
Först skapar du en exempelapp som samlar in telemetri med OpenTelemetry, men som ännu inte har någon instrumentation.
dotnet new console
Program som riktar in sig på .NET 5 och senare har redan de nödvändiga distribuerade spårnings-API:erna inkluderade. För appar som riktar sig till äldre .NET-versioner lägger du till NuGet-paketet System.Diagnostics.DiagnosticSource version 5 eller senare.
dotnet add package System.Diagnostics.DiagnosticSource
Lägg till NuGet-paketen OpenTelemetry och OpenTelemetry.Exporter.Console , som används för att samla in telemetrin.
dotnet add package OpenTelemetry
dotnet add package OpenTelemetry.Exporter.Console
Ersätt innehållet i det genererade Program.cs med den här exempelkällan:
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);
}
}
}
Appen har ingen instrumentation ännu, så det finns ingen spårningsinformation att visa:
> dotnet run
Example work done
Bästa praxis
Endast apputvecklare behöver referera till ett valfritt bibliotek från tredje part för att samla in den distribuerade spårningstelemetrin, till exempel OpenTelemetry i det här exemplet. .NET-biblioteksförfattare kan uteslutande förlita sig på API:er i System.Diagnostics.DiagnosticSource, som är en del av .NET-körningen. Detta säkerställer att bibliotek körs i en mängd olika .NET-appar, oavsett apputvecklarens inställningar för vilket bibliotek eller vilken leverantör som ska användas för insamling av telemetri.
Lägg till grundläggande instrumentation
Program och bibliotek lägger till distribuerade spårningsinstrumentation med hjälp av klasserna System.Diagnostics.ActivitySource och System.Diagnostics.Activity .
ActivitySource
Skapa först en instans av ActivitySource. ActivitySource tillhandahåller API:er för att skapa och starta aktivitetsobjekt. Lägg till variabeln static ActivitySource ovanför Main() och using System.Diagnostics;
till using-uttrycken.
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)
{
...
Bästa praxis
Skapa ActivitySource en gång, lagra den i en statisk variabel och använd den instansen så länge som det behövs. Varje bibliotek eller biblioteksunderkomponent kan (och bör ofta) skapa en egen källa. Överväg att skapa en ny källa i stället för att återanvända en befintlig om du förväntar dig att apputvecklare skulle uppskatta att kunna aktivera och inaktivera telemetrin Aktivitet i källorna oberoende av varandra.
Källnamnet som skickas till konstruktorn måste vara unikt för att undvika konflikter med andra källor. Om det finns flera källor inom samma sammansättning använder du ett hierarkiskt namn som innehåller sammansättningsnamnet och eventuellt ett komponentnamn,
Microsoft.AspNetCore.Hosting
till exempel . Om en sammansättning lägger till instrumentation för kod i en andra oberoende sammansättning bör namnet baseras på den sammansättning som definierar ActivitySource, inte den sammansättning vars kod instrumenteras.Versionsparametern är valfri. Vi rekommenderar att du anger versionen om du släpper flera versioner av biblioteket och gör ändringar i den instrumenterade telemetrin.
Kommentar
OpenTelemetry använder alternativa termer "Tracer" och "Span". I .NET "ActivitySource" är implementeringen av Tracer och Activity är implementeringen av "Span". . NET:s aktivitetstyp daterar länge OpenTelemetry-specifikationen och den ursprungliga .NET-namngivningen har bevarats för konsekvens i .NET-ekosystemet och .NET-programkompatibiliteten.
Aktivitet
Använd ActivitySource-objektet för att starta och stoppa aktivitetsobjekt runt meningsfulla arbetsenheter. Uppdatera DoSomeWork() med koden som visas här:
static async Task DoSomeWork(string foo, int bar)
{
using (Activity activity = source.StartActivity("SomeWork"))
{
await StepOne();
await StepTwo();
}
}
När appen körs visas nu den nya aktiviteten som loggas:
> 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
Kommentar
ActivitySource.StartActivity skapar och startar aktiviteten samtidigt. Det angivna kodmönstret använder
using
blocket, som automatiskt bortskaffar det skapade aktivitetsobjektet när blocket har körts. Om du tar bort aktivitetsobjektet stoppas det så att koden inte uttryckligen behöver anropa Activity.Stop(). Det förenklar kodningsmönstret.ActivitySource.StartActivity internt avgör om det finns lyssnare som spelar in aktiviteten. Om det inte finns några registrerade lyssnare eller om det finns lyssnare som inte är intresserade,
StartActivity()
returnerasnull
och undviker att skapa aktivitetsobjektet. Det här är en prestandaoptimering så att kodmönstret fortfarande kan användas i funktioner som anropas ofta.
Valfritt: Fyll i taggar
Aktiviteter stöder nyckelvärdesdata som kallas Taggar, som ofta används för att lagra alla parametrar i arbetet som kan vara användbara för diagnostik. Uppdatera DoSomeWork() för att inkludera dem:
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
Bästa praxis
- Som nämnts ovan
activity
kan returneras av ActivitySource.StartActivity vara null. Operatorn?.
null-coalescing i C# är en praktisk kort hand för att endast anropa Activity.SetTag omactivity
den inte är null. Beteendet är identiskt med att skriva:
if(activity != null)
{
activity.SetTag("foo", foo);
}
OpenTelemetry innehåller en uppsättning rekommenderade konventioner för att ange taggar för aktiviteter som representerar vanliga typer av programarbete.
Om du instrumenterar funktioner med höga prestandakrav är Activity.IsAllDataRequested ett tips som anger om någon av koden som lyssnar på Aktiviteter avser att läsa extra information, till exempel Taggar. Om ingen lyssnare läser den behöver den instrumenterade koden inte spendera CPU-cykler på att fylla den. För enkelhetens skull tillämpar det här exemplet inte den optimeringen.
Valfritt: Lägg till händelser
Händelser är tidsstämplade meddelanden som kan koppla en godtycklig ström med ytterligare diagnostikdata till Aktiviteter. Lägg till några händelser i aktiviteten:
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
Bästa praxis
- Händelser lagras i en minnesintern lista tills de kan överföras, vilket gör den här mekanismen endast lämplig för att registrera ett blygsamt antal händelser. För stora eller obundna händelser är det bättre att använda ett loggnings-API som fokuserar på den här uppgiften, till exempel ILogger. ILogger ser också till att loggningsinformationen blir tillgänglig oavsett om apputvecklaren väljer att använda distribuerad spårning. ILogger stöder automatisk insamling av aktiva aktivitets-ID:n så att meddelanden som loggas via api:et fortfarande kan korreleras med den distribuerade spårningen.
Valfritt: Lägg till status
Med OpenTelemetry kan varje aktivitet rapportera en status som representerar resultatet av arbetets pass/fail. .NET har för närvarande inte ett starkt skrivet API för detta ändamål, men det finns en etablerad konvention med taggar:
otel.status_code
är taggnamnet som används för att lagraStatusCode
. Värden för StatusCode-taggen måste vara en av strängarna "UNSET", "OK" eller "ERROR", som motsvarar uppräkningarUnset
,Ok
ochError
från StatusCode.otel.status_description
är taggnamnet som används för att lagra det valfriaDescription
Uppdatera DoSomeWork() för att ange status:
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");
}
}
Valfritt: Lägg till ytterligare aktiviteter
Aktiviteter kan kapslas för att beskriva delar av en större arbetsenhet. Detta kan vara värdefullt kring delar av kod som kanske inte körs snabbt eller för att bättre lokalisera fel som kommer från specifika externa beroenden. Även om det här exemplet använder en aktivitet i varje metod beror det bara på att extra kod har minimerats. I ett större och mer realistiskt projekt skulle användning av en aktivitet i varje metod ge extremt utförliga spårningar, så det rekommenderas inte.
Uppdatera StepOne och StepTwo för att lägga till mer spårning kring dessa separata steg:
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
Observera att både StepOne och StepTwo innehåller ett ParentId som refererar till SomeWork. Konsolen är inte en bra visualisering av kapslade arbetsträd, men många GUI-användare som Zipkin kan visa detta som ett Gantt-schema:
Valfritt: ActivityKind
Aktiviteter har en Activity.Kind egenskap som beskriver relationen mellan aktiviteten, dess överordnade och dess underordnade. Som standard är alla nya aktiviteter inställda på Internal, vilket är lämpligt för Aktiviteter som är en intern åtgärd i ett program utan fjärrförälder eller underordnade. Andra typer kan anges med hjälp av typparametern på ActivitySource.StartActivity. Andra alternativ finns i System.Diagnostics.ActivityKind.
Valfritt: Länkar
När arbete utförs i batchbearbetningssystem kan en enskild aktivitet representera arbete för många olika begäranden samtidigt, som var och en har ett eget spårnings-ID. Även om Aktiviteten är begränsad till att ha en enda överordnad kan den länka till ytterligare spårnings-ID:er med hjälp av System.Diagnostics.ActivityLink. Varje ActivityLink fylls i med en ActivityContext som lagrar ID-information om aktiviteten som länkas till. ActivityContext kan hämtas från pågående aktivitetsobjekt med eller Activity.Context så kan det parsas från serialiserad ID-information med hjälp av 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
}
}
Till skillnad från händelser och taggar som kan läggas till på begäran måste länkar läggas till under StartActivity() och kan inte ändras efteråt.