Přidání instrumentace distribuovaného trasování
Tento článek se vztahuje na: ✔️ .NET Core 2.1 a novější verze ✔️ .NET Framework 4.5 a novější verze
Aplikace .NET je možné instrumentovat pomocí rozhraní API k vytváření distribuovaných System.Diagnostics.Activity telemetrických dat trasování. Některá instrumentace je integrovaná do standardních knihoven .NET, ale možná budete chtít přidat další, aby byl kód snadněji diagnostikovatelný. V tomto kurzu přidáte novou vlastní distribuovanou instrumentaci trasování. Další informace o záznamu telemetrie vytvořené touto instrumentací najdete v kurzu kolekce.
Požadavky
- Sada .NET Core 2.1 SDK nebo novější verze
Vytvoření počáteční aplikace
Nejprve vytvoříte ukázkovou aplikaci, která shromažďuje telemetrická data pomocí OpenTelemetry, ale ještě nemá žádnou instrumentaci.
dotnet new console
Aplikace, které cílí na .NET 5 a novější, už mají zahrnutá potřebná distribuovaná rozhraní API pro trasování. Pro aplikace, které cílí na starší verze .NET, přidejte balíček NuGet System.Diagnostics.DiagnosticSource verze 5 nebo vyšší.
dotnet add package System.Diagnostics.DiagnosticSource
Přidejte balíčky NuGet OpenTelemetry a OpenTelemetry.Exporter.Console, které se použijí ke shromažďování telemetrie.
dotnet add package OpenTelemetry
dotnet add package OpenTelemetry.Exporter.Console
Nahraďte obsah vygenerovaného souboru Program.cs tímto ukázkovým zdrojem:
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);
}
}
}
Aplikace zatím nemá instrumentaci, takže se nezobrazují žádné informace o trasování:
> dotnet run
Example work done
Osvědčené postupy
Pouze vývojáři aplikací potřebují odkazovat na volitelnou knihovnu třetích stran pro shromažďování distribuovaných telemetrických dat trasování, jako je OpenTelemetry v tomto příkladu. Autoři knihoven .NET můžou výhradně spoléhat na rozhraní API v System.Diagnostics.DiagnosticSource, která je součástí modulu runtime .NET. Tím se zajistí, že knihovny poběží v široké škále aplikací .NET bez ohledu na to, jaké knihovny nebo dodavatele mají vývojáři aplikací používat ke shromažďování telemetrických dat.
Přidání základní instrumentace
Aplikace a knihovny přidávají instrumentaci distribuovaného trasování pomocí a System.Diagnostics.ActivitySourceSystem.Diagnostics.Activity tříd.
ActivitySource
Nejprve vytvořte instanci ActivitySource. ActivitySource poskytuje rozhraní API pro vytváření a spouštění objektů aktivit. Přidejte statickou proměnnou ActivitySource nad Main() a using System.Diagnostics;
do příkazů 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)
{
...
Osvědčené postupy
Vytvořte zdroj aktivity jednou, uložte ho do statické proměnné a použijte tuto instanci, pokud je to potřeba. Každá knihovna nebo podkomponent knihovny (a často by měla) vytvořit vlastní zdroj. Pokud očekáváte, že vývojáři aplikací budou moct povolit a zakázat telemetrii aktivit ve zdrojích nezávisle, zvažte možnost vytvořit nový zdroj a neopakovat existující zdroj.
Název zdroje předaný konstruktoru musí být jedinečný, aby nedocházelo ke konfliktům s jinými zdroji. Pokud existuje více zdrojů ve stejném sestavení, použijte hierarchický název, který obsahuje název sestavení a volitelně název komponenty,
Microsoft.AspNetCore.Hosting
například . Pokud sestavení přidává instrumentaci pro kód v sekundě nezávislé sestavení, název by měl být založen na sestavení, které definuje ActivitySource, nikoli sestavení, jehož kód je instrumentován.Parametr verze je volitelný. Doporučujeme zadat verzi pro případ, že vydáte více verzí knihovny a provedete změny instrumentované telemetrie.
Poznámka:
OpenTelemetry používá alternativní termíny Tracer a Span. V .NET ActivitySource je implementace Tracer a Activity je implementace Span. . Typ aktivity net s dlouhými daty před datem specifikace OpenTelemetry a původní pojmenování .NET bylo zachováno kvůli konzistenci v ekosystému .NET a kompatibilitě aplikací .NET.
Aktivita
Pomocí objektu ActivitySource můžete spouštět a zastavovat objekty aktivity kolem smysluplných jednotek práce. Aktualizujte DoSomeWork() kódem uvedeným tady:
static async Task DoSomeWork(string foo, int bar)
{
using (Activity activity = source.StartActivity("SomeWork"))
{
await StepOne();
await StepTwo();
}
}
Když spustíte aplikaci, zobrazí se nová aktivita, která se protokoluje:
> 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
Notes
ActivitySource.StartActivity vytvoří a spustí aktivitu ve stejnou dobu. Uvedený vzor kódu používá
using
blok, který po spuštění bloku automaticky odstraní vytvořený objekt aktivity. Disposing the Activity object will stop it so the code t need to call Activity.Stop()explicitně . To zjednodušuje vzor kódování.ActivitySource.StartActivity interně určuje, jestli aktivita nahrává nějaké naslouchací procesy. Pokud nejsou zaregistrované naslouchací procesy nebo neexistují naslouchací procesy, které nemají zájem,
StartActivity()
vrátí senull
a zabrání vytvoření objektu Activity. Jedná se o optimalizaci výkonu, aby se vzor kódu stále mohl používat ve funkcích, které se volají často.
Volitelné: Naplnění značek
Aktivity podporují data klíč-hodnota označovaná jako Značky, která se běžně používají k ukládání jakýchkoli parametrů práce, které můžou být užitečné pro diagnostiku. Aktualizujte DoSomeWork(), abyste je zahrnuli:
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
Osvědčené postupy
- Jak je uvedeno výše,
activity
vrácený ActivitySource.StartActivity hodnotou může být null. Operátor?.
null-coalescing v jazyce C# je praktická zkratka k vyvolání Activity.SetTag pouze v případěactivity
, že není null. Chování je stejné jako při psaní:
if(activity != null)
{
activity.SetTag("foo", foo);
}
OpenTelemetry poskytuje sadu doporučených konvencí pro nastavení značek u aktivit, které představují běžné typy práce aplikace.
Pokud instrumentujete funkce s vysokými požadavky na výkon, je tip, který indikuje, jestli některý z kódů, který naslouchá aktivitám, má v úmyslu přečíst pomocné informace, Activity.IsAllDataRequested jako jsou značky. Pokud ho žádný naslouchací proces nepřečte, není potřeba instrumentovaný kód trávit cykly procesoru naplněním. Pro zjednodušení tato ukázka tuto optimalizaci nepoužije.
Volitelné: Přidání událostí
Události jsou časového razítka zpráv, které můžou k aktivitám připojit libovolný datový proud dalších diagnostických dat. Přidejte do aktivity několik událostí:
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
Osvědčené postupy
- Události jsou uloženy v seznamu v paměti, dokud je nelze přenést, což z tohoto mechanismu činí vhodný pouze pro záznam skromného počtu událostí. U velkého nebo nevázaného objemu událostí je lepší použít rozhraní API protokolování zaměřené na tuto úlohu, například ILogger. ILogger také zajišťuje, že informace o protokolování budou k dispozici bez ohledu na to, jestli vývojář aplikace rozhodne používat distribuované trasování. ILogger podporuje automatické zachytávání ID aktivních aktivit, aby zprávy zaprotokolované přes toto rozhraní API mohly být stále korelovány s distribuovaným trasováním.
Volitelné: Přidání stavu
OpenTelemetry umožňuje každé aktivitě hlásit stav , který představuje výsledek průchodu nebo selhání práce. Rozhraní .NET v současné době nemá rozhraní API silného typu pro tento účel, ale existuje zavedená konvence využívající značky:
otel.status_code
je název značky, který se používá k uloženíStatusCode
. Hodnoty pro značku StatusCode musí být jedním z řetězců "UNSET", "OK" nebo "ERROR", které odpovídají výčtůmUnset
,Ok
aError
z StatusCode.otel.status_description
je název značky použitý k uložení volitelnéhoDescription
Aktualizujte DoSomeWork() tak, aby nastavil stav:
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");
}
}
Volitelné: Přidání dalších aktivit
Aktivity je možné vnořit a popsat části větší pracovní jednotky. To může být užitečné pro části kódu, které se nemusí spustit rychle nebo kvůli lepší lokalizaci selhání, která pocházejí z konkrétních externích závislostí. I když tato ukázka používá aktivitu v každé metodě, je to výhradně proto, že byl minimalizovaný kód navíc. Ve větším a realističtějším projektu by použití aktivity v každé metodě vytvořilo extrémně podrobné trasování, takže se nedoporučuje.
Aktualizujte StepOne a StepTwo a přidejte další trasování kolem těchto samostatných kroků:
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
Všimněte si, že StepOne i StepTwo obsahují parentId, který odkazuje na SomeWork. Konzola není skvělou vizualizací vnořených stromů práce, ale mnoho prohlížečů grafického uživatelského rozhraní, jako je Zipkin , to může zobrazit jako Ganttův diagram:
Volitelné: ActivityKind
Aktivity mají Activity.Kind vlastnost, která popisuje vztah mezi aktivitou, jeho nadřazeným objektem a podřízenými objekty. Ve výchozím nastavení jsou všechny nové aktivity nastaveny na Internal, což je vhodné pro aktivity, které jsou interní operací v aplikaci bez vzdálené nadřazené nebo podřízené položky. Jiné druhy lze nastavit pomocí parametru typu na ActivitySource.StartActivity. Další možnosti najdete v tématu System.Diagnostics.ActivityKind.
Volitelné: Odkazy
Když dojde k práci v systémech dávkového zpracování, může jedna aktivita představovat práci jménem mnoha různých požadavků současně, z nichž každá má vlastní trasovací ID. I když je aktivita omezena tak, aby měla jeden nadřazený objekt, může propojit s dalšími id trasování pomocí System.Diagnostics.ActivityLink. Každý activityLink je naplněný informacemi ActivityContext o ID o aktivitě, se kterou je propojená. ActivityContext lze načíst z objektů aktivity v procesu pomocí Activity.Context nebo lze analyzovat ze serializovaných INFORMACÍ ID pomocí 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
}
}
Na rozdíl od událostí a značek, které je možné přidat na vyžádání, je potřeba přidat odkazy během startActivity() a následně se změní na neměnné.