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

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.Hostingtill 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() returneras null 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 om activity 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 lagra StatusCode. Värden för StatusCode-taggen måste vara en av strängarna "UNSET", "OK" eller "ERROR", som motsvarar uppräkningar Unset, Okoch Error från StatusCode.
  • otel.status_description är taggnamnet som används för att lagra det valfria Description

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:

Zipkin Gantt chart

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.

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.