Concetti relativi alla traccia distribuita .NET

La traccia distribuita è una tecnica di diagnostica che consente ai tecnici di localizzare errori e problemi di prestazioni all'interno delle applicazioni, in particolare quelle che possono essere distribuiti in più computer o processi. Per informazioni generali sui casi in cui è utile la traccia distribuita, vedi Panoramica della traccia distribuita.

Tracce e attività

Ogni volta che una nuova richiesta viene ricevuta da un'applicazione, può essere associata a una traccia. Nei componenti dell'applicazione scritti in .NET, le unità di lavoro di una traccia sono rappresentate da istanze di System.Diagnostics.Activity e la traccia nel suo complesso costituisce un albero di queste attività, potenzialmente distribuite su molti processi distinti. La prima attività creata per una nuova richiesta costituisce la radice dell'albero della traccia e tiene traccia della durata complessiva e dell'esito positivo/negativo della gestione della richiesta. Le attività figlio possono essere create facoltativamente per suddividere il lavoro in passaggi diversi che possono essere monitorati singolarmente. Ad esempio, data un'attività che tiene traccia di una specifica richiesta HTTP in ingresso in un server Web, è possibile creare attività figlio per tenere traccia di ogni query di database necessaria per completare la richiesta. In questo modo è possibile registrare in modo indipendente la durata e l'esito positivo di ogni query. Le attività possono registrare altre informazioni per ogni unità di lavoro, ad esempio OperationName, coppie nome-valore denominate Tags e Events. Il nome identifica il tipo di lavoro eseguito, i tag possono registrare parametri descrittivi del lavoro e gli eventi sono un semplice meccanismo di registrazione dei messaggi di diagnostica con timestamp.

Nota

Un altro nome comune nel settore per indicare le unità di lavoro in una traccia distribuita è "Spans". .NET ha adottato il termine "Attività" molti anni fa, prima che il nome "Span" fosse consolidato per questo concetto.

ID attività

Le relazioni padre-figlio tra le attività dell'albero della traccia distribuita vengono stabilite usando ID univoci. L'implementazione della traccia distribuita di .NET supporta due schemi ID: TraceContext standard W3C, che è l'impostazione predefinita in .NET 5+, e una convenzione .NET precedente denominata “Gerarchica”, disponibile per la compatibilità con le versioni precedenti. Activity.DefaultIdFormat controlla quale schema ID viene usato. Nello standard TraceContext W3C, a ogni traccia viene assegnato un trace-id a 16 byte univoco globale (Activity.TraceId) e a ogni attività all'interno della traccia viene assegnato uno span-id a 8 byte univoco (Activity.SpanId). Ogni attività registra il trace-id, il relativo span-id e lo span-id dell’elemento padre (Activity.ParentSpanId). Poiché le tracce distribuite possono monitorare il lavoro oltre i limiti del processo, le attività padre e figlio potrebbero non trovarsi nello stesso processo. La combinazione di un trace-id e di uno span-id padre può identificare in modo univoco l'attività padre a livello globale, indipendentemente dal processo in cui risiede.

Activity.DefaultIdFormat controlla il formato ID usato per l'avvio di nuove tracce, ma per impostazione predefinita l'aggiunta di una nuova attività a una traccia esistente usa qualsiasi formato usato dall'attività padre. L'impostazione di Activity.ForceDefaultIdFormat su true esegue l'override di questo comportamento e crea tutte le nuove attività con il DefaultIdFormat, anche quando l'elemento padre usa un formato ID diverso.

Avviare e arrestare le attività

Ogni thread di un processo può avere un oggetto Attività corrispondente che tiene traccia del lavoro che si verifica in tale thread, accessibile tramite Activity.Current. L'attività corrente scorre automaticamente tutte le chiamate sincrone su un thread e segue le chiamate asincrone elaborate in thread diversi. Se l'attività A è l'attività corrente in un thread e il codice avvia una nuova attività B, B diventa la nuova attività corrente in tale thread. Per impostazione predefinita, l'attività B considera anche l'attività A come padre. Quando l'attività B viene arrestata in un secondo momento, l'attività A verrà ripristinata come attività corrente nel thread. Quando un'attività viene avviata, acquisisce l'ora corrente come Activity.StartTimeUtc. Quando si arresta, Activity.Duration viene calcolata come differenza tra l'ora corrente e l'ora di inizio.

Coordinare i limiti dei processi

Per tenere traccia del lavoro oltre i limiti dei processi, gli ID padre delle attività devono essere trasmessi attraverso la rete in modo che il processo di ricezione possa creare le attività che vi fanno riferimento. Quando si usa il formato ID TraceContext W3C, .NET usa anche le intestazioni HTTP consigliate dallo standard per trasmettere queste informazioni. Quando si usa il formato ID Hierarchical, .NET usa un'intestazione HTTP request-id personalizzata per trasmettere l'ID. A differenza di molti altri runtime del linguaggio, le librerie in-box di .NET, come il server Web ASP.NET e System.Net.Http, comprendono in modo nativo come decodificare e codificare gli ID attività nei messaggi HTTP. Il runtime comprende anche come trasmettere l’ID tramite chiamate sincrone e asincrone. Ciò significa che le applicazioni .NET che ricevono e generano messaggi HTTP partecipano automaticamente alla trasmissione degli ID di traccia distribuita, senza codice speciale da parte dello sviluppatore dell'app o delle dipendenze di librerie di terze parti. Le librerie di terze parti possono aggiungere supporto per la trasmissione di ID su protocolli di messaggi non HTTP o supporto per convenzioni di codifica personalizzate per HTTP.

Raccogliere tracce

Il codice instrumentato può creare oggetti Activity come parte di una traccia distribuita, ma le informazioni in questi oggetti devono essere trasmesse e serializzate in un archivio permanente centralizzato in modo che l'intera traccia possa essere esaminata in un secondo momento. Sono disponibili diverse librerie per la raccolta di dati di telemetria che possono eseguire questa attività, ad esempio Application Insights, OpenTelemetry o la libreria fornita da un provider di dati di telemetria di terze parti o da un fornitore di APM. In alternativa, gli sviluppatori possono creare la propria raccolta di dati di telemetria delle attività personalizzata usando System.Diagnostics.ActivityListener o System.Diagnostics.DiagnosticListener. ActivityListener supporta l'osservazione di qualsiasi attività indipendentemente dal fatto che lo sviluppatore abbia conoscenze precedenti su di esso. Ciò rende ActivityListener una soluzione semplice e flessibile per un utilizzo generico. Al contrario, l'uso di DiagnosticListener è uno scenario più complesso che richiede il consenso esplicito del codice instrumentato richiamando DiagnosticSource.StartActivity e la libreria di raccolte deve conoscere le informazioni di denominazione esatte usate dal codice instrumentato durante l'avvio. L'uso di DiagnosticSource e DiagnosticListener consente al creator e al listener di scambiare oggetti .NET arbitrari e stabilire convenzioni personalizzate per il passaggio delle informazioni.

Campionamento

Per migliorare le prestazioni nelle applicazioni a velocità effettiva elevata, la traccia distribuita in .NET supporta il campionamento di un solo subset di tracce anziché registrarle tutte. Per le attività create con l'API ActivitySource.StartActivity consigliata, le librerie di raccolta dati di telemetria possono controllare il campionamento con il callback ActivityListener.Sample. La libreria di registrazione può scegliere di non creare affatto l'attività, di crearla con informazioni minime necessarie per propagare gli ID di traccia di distribuzione, oppure di popolarla con informazioni di diagnostica complete. Queste scelte sono un compromesso tra l’aumento del sovraccarico delle prestazioni e l’aumento dell’utilità diagnostica. Le attività avviate usando il modello precedente che richiamava Activity.Activity e DiagnosticSource.StartActivity possono supportare anche il campionamento DiagnosticListener chiamando prima DiagnosticSource.IsEnabled. Anche quando si acquisiscono informazioni di diagnostica complete, l'implementazione di .NET è progettata per essere veloce: abbinata a un agente di raccolta efficiente, è possibile creare, popolare e trasmettere un’attività in circa un microsecondo sull'hardware moderno. Il campionamento può ridurre il costo di strumentazione a meno di 100 nanosecondi per ogni attività non registrata.

Passaggi successivi

Per un esempio di codice con cui iniziare a usare la traccia distribuita nelle applicazioni .NET, vedi la Strumentazione traccia distribuita.