Correlazione e analisi distribuita tramite la messaggistica del bus di servizio
Articolo
Uno dei problemi più comuni che si riscontrano durante lo sviluppo di microservizi riguarda la possibilità di rilevare le operazioni a partire da un client che includa tutti i servizi coinvolti nell’elaborazione. Si tratta di una caratteristica utile per il debug, l'analisi delle prestazioni, i test A/B e altri comuni scenari di diagnostica. Una parte di questo problema è il rilevamento di parti logiche di lavoro, che include il risultato dell'elaborazione dei messaggi e la latenza e le chiamate di dipendenza esterne. Un'altra parte è costituita dalla correlazione di questi eventi di diagnostica oltre i limiti del processo.
Quando un producer invia un messaggio tramite una coda, tale operazione avviene in genere nell'ambito di un'altra operazione logica, avviata da un altro client o servizio. La stessa operazione viene portata avanti dal consumer dopo che ha ricevuto un messaggio. Sia il producer che il consumer (e altri servizi che elaborano l'operazione) generano presumibilmente eventi di telemetria per analizzare il flusso e il risultato dell'operazione. Per correlare tali eventi e l'operazione di analisi end-to-end, ogni servizio che restituisce dati di telemetria deve contrassegnare tutti gli eventi con un contesto di analisi.
La messaggistica del bus di servizio di Microsoft Azure include proprietà di payload definite che producer e consumer devono usare per passare tale contesto di analisi.
Il protocollo si basa sul Trace-Context W3C.
Identificatore univoco di una chiamata esterna alla coda effettuata dal producer. Fare riferimento all'intestazione padre di traccia del contesto di traccia W3C per il formato
Rilevamento automatico del client .NET del bus di servizio
La classe ServiceBusProcessor del client del Bus del servizio di messaggistica di Azure per .NET fornisce punti di strumentazione per il rilevamento a cui possono agganciarsi sistemi di monitoraggio o parti del codice client. La strumentazione consente di verificare tutte le chiamate al servizio di messaggistica del bus di servizio dal lato client. Se l'elaborazione dei messaggi viene eseguita tramite l'utilizzo ProcessMessageAsync di ServiceBusProcessor (modello del gestore di messaggi), anche tale operazione viene instrumentata.
Verifica con Azure Application Insights
Microsoft Application Insights offre funzionalità avanzate di monitoraggio delle prestazioni, tra cui la verifica automatica delle richieste e delle dipendenze.
A seconda del tipo di progetto, installare Application Insights SDK:
ASP.NET: installare la versione 2.5-beta2 o una versione successiva
ASP.NET Core: installare la versione 2.2.0-beta2 o una versione successiva.
Questi collegamenti forniscono informazioni dettagliate su come installare l'SDK, creare risorse e, se necessario, configurare l'SDK. Per le applicazioni non ASP.NET, vedere l'articolo Azure Application Insights for Console Applications (Azure Application Insights per applicazioni console).
Se si usa ProcessMessageAsync di ServiceBusProcessor (modello del gestore di messaggi) per elaborare i messaggi, anche tale operazione viene instrumentata. Tutte le chiamate del bus di servizio eseguite dal servizio vengono rilevate automaticamente e correlate ad altri elementi di telemetria. In caso contrario vedere l'esempio seguente per la verifica manuale delle operazioni di elaborazione dei messaggi.
Analizzare l'elaborazione dei messaggi
async Task ProcessAsync(ProcessMessageEventArgs args)
{
ServiceBusReceivedMessage message = args.Message;
if (message.ApplicationProperties.TryGetValue("Diagnostic-Id", out var objectId) && objectId is string diagnosticId)
{
var activity = new Activity("ServiceBusProcessor.ProcessMessage");
activity.SetParentId(diagnosticId);
// If you're using Microsoft.ApplicationInsights package version 2.6-beta or higher, you should call StartOperation<RequestTelemetry>(activity) instead
using (var operation = telemetryClient.StartOperation<RequestTelemetry>("Process", activity.RootId, activity.ParentId))
{
telemetryClient.TrackTrace("Received message");
try
{
// process message
}
catch (Exception ex)
{
telemetryClient.TrackException(ex);
operation.Telemetry.Success = false;
throw;
}
telemetryClient.TrackTrace("Done");
}
}
}
In questo esempio, la telemetria delle richieste viene riportata per ogni messaggio elaborato, indicando il timestamp, la durata e il risultato (esito positivo). La telemetria include anche un set di proprietà di correlazione. Alle eccezioni e alle analisi nidificate restituite durante l'elaborazione dei messaggi vengono inoltre assegnate proprietà di correlazione in modo che vengano rappresentate come elementi figlio di RequestTelemetry.
Nel caso in cui durante l'elaborazione dei messaggi si eseguano chiamate a componenti esterni supportati, anche queste vengono rilevate e correlate automaticamente. Per informazioni sulla verifica e la correlazione manuale, vedere Verifica delle operazioni personalizzate con Application Insights .NET SDK.
Se si esegue codice esterno oltre all'SDK di Application Insights, è possibile rilevare una durata maggiore durante la visualizzazione dei log di Application Insights.
Questo non significa che si sia verificato un ritardo nella ricezione del messaggio. In questo scenario, il messaggio è già stato ricevuto, poiché il messaggio viene passato come parametro al codice dell'SDK. Il tag del nome nei log di App Insights (Process) indica inoltre che il messaggio viene ora elaborato dal codice di elaborazione eventi esterno. Questo problema non è correlato ad Azure. Al contrario, tali metriche fanno riferimento all'efficienza del codice esterno, dato che il messaggio è già stato ricevuto dal bus di servizio.
Rilevamento con OpenTelemetry
La libreria client .NET del bus di servizio versione 7.5.0 e successive supporta OpenTelemetry in modalità sperimentale. Per altre informazioni, vedere Rilevamento distribuito in .NET SDK.
Verifica senza sistema di analisi
Nel caso in cui il sistema di traccia non supporti il rilevamento automatico delle chiamate bus di servizio si potrebbe esaminare l'aggiunta di tale supporto in un sistema di traccia o nell'applicazione. Questa sezione illustra gli eventi di diagnostica inviati dal client .NET del bus di servizio.
Activity funge da un contesto di analisi mentre DiagnosticSource è un meccanismo di notifica.
Se non è presente alcun listener per gli eventi DiagnosticSource, la strumentazione è disattivata, quindi i costi di strumentazione sono azzerati. DiagnosticSource cede il controllo completo al listener:
il listener controlla le origini e gli eventi di cui rimanere in ascolto
il listener controlla il campionamento e la frequenza degli eventi
gli eventi vengono inviati con un payload che fornisce il contesto completo, in modo che sia possibile accedere e modificare l'oggetto Message durante l'evento
Nel codice seguente verrà creato un listener per eventi del bus di servizio nell'app ASP.NET Core che scrive i log con Microsoft.Extension.Logger.
Il listener usa la libreria System.Reactive.Core per eseguire la sottoscrizione a DiagnosticSource. È comunque possibile eseguire facilmente la sottoscrizione a DiagnosticSource anche senza la libreria.
public void Configure(IApplicationBuilder app, IHostingEnvironment env, ILoggerFactory factory, IApplicationLifetime applicationLifetime)
{
// configuration...
var serviceBusLogger = factory.CreateLogger("Azure.Messaging.ServiceBus");
IDisposable innerSubscription = null;
IDisposable outerSubscription = DiagnosticListener.AllListeners.Subscribe(delegate (DiagnosticListener listener)
{
// subscribe to the Service Bus DiagnosticSource
if (listener.Name == "Azure.Messaging.ServiceBus")
{
// receive event from Service Bus DiagnosticSource
innerSubscription = listener.Subscribe(delegate (KeyValuePair<string, object> evnt)
{
// Log operation details once it's done
if (evnt.Key.EndsWith("Stop"))
{
Activity currentActivity = Activity.Current;
serviceBusLogger.LogInformation($"Operation {currentActivity.OperationName} is finished, Duration={currentActivity.Duration}, Id={currentActivity.Id}, StartTime={currentActivity.StartTimeUtc}");
}
});
}
});
applicationLifetime.ApplicationStopping.Register(() =>
{
outerSubscription?.Dispose();
innerSubscription?.Dispose();
});
}
In questo esempio il listener registra durata, risultato, identificatore univoco e ora di inizio di ogni operazione del bus di servizio.
Callback del processore impostato su ServiceBusProcessor. Proprietà ProcessMessageAsync
ServiceBusSessionProcessor.ProcessSessionMessage
Callback del processore impostato su ServiceBusSessionProcessor. Proprietà ProcessMessageAsync
Filtro e campionamento
In alcuni casi è consigliabile registrare solo parte degli eventi per ridurre il consumo dello spazio di archiviazione o il sovraccarico delle prestazioni. È possibile registrare solo gli eventi di tipo 'Stop' (come nell'esempio precedente) o una percentuale campione degli eventi.
A questo scopo, è possibile usare DiagnosticSource con il predicato IsEnabled. Per altre informazioni, vedere Context-Based Filtering in DiagnosticSource (Filtro in base al contesto in DiagnosticSource).
IsEnabled può essere chiamato più volte per una singola operazione per ridurre al minimo l'impatto sulle prestazioni.
IsEnabled viene chiamato nella sequenza seguente:
IsEnabled(<OperationName>, string entity, null) ad esempio, IsEnabled("ServiceBusSender.Send", "MyQueue1"). Si noti che alla fine non è presente alcun evento di tipo 'Start' o 'Stop'. Usarlo per escludere determinate operazioni o code. Se il metodo di callback restituisce false, gli eventi per l'operazione non vengono inviati.
Per le operazioni di tipo 'Process' e 'ProcessSession' viene ricevuto anche il callback IsEnabled(<OperationName>, string entity, Activity activity). Usarlo per filtrare gli eventi in base alle proprietà activity.Id o Tags.
IsEnabled(<OperationName>.Start) ad esempio, IsEnabled("ServiceBusSender.Send.Start"). Controlla se deve essere attivato l'evento 'Start'. Il risultato interessa solo l'evento 'Start', ma l'ulteriore strumentazione non dipende da esso.
Non esiste alcun elemento IsEnabled per l'evento 'Stop'.
Se il risultato di alcune operazioni è un'eccezione, viene chiamato IsEnabled("ServiceBusSender.Send.Exception"). È possibile eseguire la sottoscrizione solo di eventi 'Exception' e impedire il resto della strumentazione. In questo caso sarà comunque necessario gestire tali eccezioni. Dal momento che l'altra strumentazione è disabilitata, il contesto di rilevamento con i messaggi non verrà trasmesso dall’utente al produttore.
È possibile usare IsEnabled anche per implementare strategie di campionamento. Il campionamento basato su Activity.Id o Activity.RootId assicura il campionamento coerente in tutti i livelli (purché venga propagato dal sistema di rilevamento o dal codice personalizzato).
In presenza di più listener DiagnosticSource per la stessa origine, è sufficiente che l'evento venga accettato da un solo listener. Di conseguenza, non è garantito che venga chiamato IsEnabled.
Il 30 settembre 2026 verranno ritirate le librerie dell'SDK del bus di servizio di Azure WindowsAzure.ServiceBus, Microsoft.Azure.ServiceBus e com.microsoft.azure.servicebus, che non sono conformi alle linee guida di Azure SDK. Verrà terminato anche il supporto del protocollo SBMP, quindi non sarà più possibile usare questo protocollo dopo il 30 settembre 2026. Eseguire la migrazione alle librerie più recenti di Azure SDK, che offrono aggiornamenti critici della sicurezza e funzionalità migliorate, prima di tale data.
Anche se le librerie precedenti possono ancora essere usate oltre il 30 settembre 2026, non riceveranno più il supporto e gli aggiornamenti ufficiali da Microsoft. Per altre informazioni, vedere l'annuncio del ritiro del supporto.
Nome proprietà
Descrizione
Diagnostic-Id
Identificatore univoco di una chiamata esterna alla coda effettuata dal producer. Per la logica, le considerazioni e il formato, vedere Request-Id in HTTP protocol (Request-Id nel protocollo HTTP).
Correlation-Context
Contesto dell'operazione, che viene propagato in tutti i servizi coinvolti nell'elaborazione dell'operazione. Per altre informazioni, vedere Correlation-Context in HTTP protocol (Correlation-Context nel protocollo HTTP).
Rilevamento automatico del client .NET del bus di servizio
A partire dalla versione 3.0.0, il client del bus di servizio di Microsoft Azure per .NET fornisce punti di strumentazione di analisi a cui possono agganciarsi sistemi di analisi o parti del codice client.
La strumentazione consente di verificare tutte le chiamate al servizio di messaggistica del bus di servizio dal lato client. Se per l'elaborazione dei messaggi si usa il criterio del gestore di messaggi, anche tale operazione viene instrumentata.
Verifica con Azure Application Insights
Microsoft Application Insights offre funzionalità avanzate di monitoraggio delle prestazioni, tra cui la verifica automatica delle richieste e delle dipendenze.
A seconda del tipo di progetto, installare Application Insights SDK:
ASP.NET: installare la versione 2.5-beta2 o una versione successiva
ASP.NET Core: installare la versione 2.2.0-beta2 o una versione successiva.
Questi collegamenti forniscono informazioni dettagliate su come installare l'SDK, creare risorse e, se necessario, configurare l'SDK. Per le applicazioni non ASP.NET, vedere l'articolo Azure Application Insights for Console Applications (Azure Application Insights per applicazioni console).
Se per elaborare i messaggi si usa il modello del gestore di messaggi, non ci sono altre operazioni da eseguire. Tutte le chiamate al bus di servizio eseguite dal servizio vengono automaticamente rilevate e correlate ad altri elementi di telemetria. In caso contrario vedere l'esempio seguente per la verifica manuale delle operazioni di elaborazione dei messaggi.
Analizzare l'elaborazione dei messaggi
private readonly TelemetryClient telemetryClient;
async Task ProcessAsync(Message message)
{
var activity = message.ExtractActivity();
// If you're using Microsoft.ApplicationInsights package version 2.6-beta or higher, you should call StartOperation<RequestTelemetry>(activity) instead
using (var operation = telemetryClient.StartOperation<RequestTelemetry>("Process", activity.RootId, activity.ParentId))
{
telemetryClient.TrackTrace("Received message");
try
{
// process message
}
catch (Exception ex)
{
telemetryClient.TrackException(ex);
operation.Telemetry.Success = false;
throw;
}
telemetryClient.TrackTrace("Done");
}
}
In questo esempio, per ogni messaggio elaborato viene restituito RequestTelemetry, che include un timestamp, la durata e il risultato (esito positivo). La telemetria include anche un set di proprietà di correlazione.
Alle eccezioni e alle analisi nidificate restituite durante l'elaborazione dei messaggi vengono inoltre assegnate proprietà di correlazione in modo che vengano rappresentate come elementi figlio di RequestTelemetry.
Nel caso in cui durante l'elaborazione dei messaggi si eseguano chiamate a componenti esterni supportati, anche queste vengono rilevate e correlate automaticamente. Per informazioni sulla verifica e la correlazione manuale, vedere Verifica delle operazioni personalizzate con Application Insights .NET SDK.
Se si esegue codice esterno oltre all'SDK di Application Insights, è possibile rilevare una durata maggiore durante la visualizzazione dei log di Application Insights.
Questo non significa che si sia verificato un ritardo nella ricezione del messaggio. In questo scenario, il messaggio è già stato ricevuto, poiché il messaggio viene passato come parametro al codice dell'SDK. Il tag del nome nei log di App Insights (Process) indica inoltre che il messaggio viene ora elaborato dal codice di elaborazione eventi esterno. Questo problema non è correlato ad Azure. Al contrario, tali metriche fanno riferimento all'efficienza del codice esterno, dato che il messaggio è già stato ricevuto dal bus di servizio. Vedere questo file in GitHub per individuare la posizione in cui il tag Processo viene generato e assegnato, una volta che il messaggio è stato ricevuto dal bus di servizio.
Verifica senza sistema di analisi
Nel caso in cui il sistema di traccia non supporti il rilevamento automatico delle chiamate bus di servizio si potrebbe esaminare l'aggiunta di tale supporto in un sistema di traccia o nell'applicazione. Questa sezione illustra gli eventi di diagnostica inviati dal client .NET del bus di servizio.
Activity funge da un contesto di analisi mentre DiagnosticSource è un meccanismo di notifica.
Se non è presente alcun listener per gli eventi DiagnosticSource, la strumentazione è disattivata, quindi i costi di strumentazione sono azzerati. DiagnosticSource cede il controllo completo al listener:
il listener controlla le origini e gli eventi di cui rimanere in ascolto
il listener controlla il campionamento e la frequenza degli eventi
gli eventi vengono inviati con un payload che fornisce il contesto completo, in modo che sia possibile accedere e modificare l'oggetto Message durante l'evento
Nel codice seguente verrà creato un listener per eventi del bus di servizio nell'app ASP.NET Core che scrive i log con Microsoft.Extension.Logger.
Il listener usa la libreria System.Reactive.Core per eseguire la sottoscrizione a DiagnosticSource. È comunque possibile eseguire facilmente la sottoscrizione a DiagnosticSource anche senza la libreria.
public void Configure(IApplicationBuilder app, IHostingEnvironment env, ILoggerFactory factory, IApplicationLifetime applicationLifetime)
{
// configuration...
var serviceBusLogger = factory.CreateLogger("Microsoft.Azure.ServiceBus");
IDisposable innerSubscription = null;
IDisposable outerSubscription = DiagnosticListener.AllListeners.Subscribe(delegate (DiagnosticListener listener)
{
// subscribe to the Service Bus DiagnosticSource
if (listener.Name == "Microsoft.Azure.ServiceBus")
{
// receive event from Service Bus DiagnosticSource
innerSubscription = listener.Subscribe(delegate (KeyValuePair<string, object> evnt)
{
// Log operation details once it's done
if (evnt.Key.EndsWith("Stop"))
{
Activity currentActivity = Activity.Current;
TaskStatus status = (TaskStatus)evnt.Value.GetProperty("Status");
serviceBusLogger.LogInformation($"Operation {currentActivity.OperationName} is finished, Duration={currentActivity.Duration}, Status={status}, Id={currentActivity.Id}, StartTime={currentActivity.StartTimeUtc}");
}
});
}
});
applicationLifetime.ApplicationStopping.Register(() =>
{
outerSubscription?.Dispose();
innerSubscription?.Dispose();
});
}
In questo esempio il listener registra durata, risultato, identificatore univoco e ora di inizio di ogni operazione del bus di servizio.
Eventi
Per ogni operazione vengono inviati due eventi: Start e Stop.
Molto probabilmente, sei interessato solo agli eventi Stop. Tali eventi forniscono il risultato dell'operazione, l'ora di inizio e la durata sotto forma di proprietà Activity.
Il payload dell'evento fornisce un listener con il contesto dell'operazione. Replica i parametri dell'API in ingresso e il valore restituito. Il payload dell'evento 'Stop' contiene tutte le proprietà del payload dell'evento 'Start', di conseguenza è possibile ignorare completamente l'evento 'Start'.
Tutti gli eventi dispongono anche di proprietà 'Entity' ed 'Endpoint'.
string Entity - - Nome dell'entità (coda, argomento e così via)
Uri Endpoint: URL dell'endpoint del bus di servizio
Per ogni evento 'Stop' esiste una proprietà Status con l'operazione asincrona TaskStatus con cui è stata completata. Per semplicità anche tale proprietà non figura nella tabella seguente.
Di seguito è riportato l'elenco completo delle operazioni instrumentate:
Message Message: messaggio in fase di elaborazione DateTimeOffset ScheduleEnqueueTimeUtc: differenza di ora dei messaggi pianificati long SequenceNumber: numero di sequenza del messaggio pianificato (payload dell'evento 'Stop')
int RequestedMessageCount: numero massimo di messaggi che è possibile ricevere. IList<Message> Messages: elenco dei messaggi ricevuti (payload dell'evento 'Stop')
int FromSequenceNumber: punto di partenza da cui sfogliare un batch di messaggi. int RequestedMessageCount: numero massimo di messaggi da recuperare. IList<Message> Messages: elenco dei messaggi ricevuti (payload dell'evento 'Stop')
IEnumerable<long> SequenceNumbers: elenco contenente i numeri di sequenza da ricevere. IList<Message> Messages: elenco dei messaggi ricevuti (payload dell'evento 'Stop')
string LockToken: token di blocco del messaggio corrispondente da inserire per cui rinnovare il blocco. DateTime LockedUntilUtc: nuova data e ora di scadenza del token di blocco in formato UTC. (payload dell'evento 'Stop')
string SessionId: elemento sessionId presente nei messaggi.
Microsoft.Azure.ServiceBus.Exception
qualsiasi API instrumentata
Exception Exception: istanza di eccezione
In ogni evento è possibile accedere a Activity.Current che contiene il contesto dell'operazione
Registrazione di altre proprietà
Activity.Current fornisce il contesto dettagliato dell'operazione corrente e i relativi elementi padre. Per altre informazioni, vedere Documentazione di Activity.
La strumentazione del bus di servizio fornisce maggiori informazioni nei tag Activity.Current.Tags, che contengono MessageId e SessionId qualora disponibili.
Anche le attività che tengono traccia dell'evento Receive, Peek e ReceiveDeferred potrebbero avere RelatedTo tag. Tale tag contiene un elenco distinto di elementi Diagnostic-Id di messaggi che sono stati ricevuti come risultato.
Tale operazione potrebbe comportare la ricezione di diversi messaggi non correlati. L'elemento Diagnostic-Id, inoltre, non è noto all'inizio dell'operazione, di conseguenza le operazioni di tipo 'Receive' potrebbero essere correlate a operazioni di tipo 'Process' solo usando questo tag. Il tag è utile quando si analizzano problemi di prestazioni per verificare il tempo impiegato per la ricezione del messaggio.
Un modo efficiente per registrare i tag consiste nell'eseguire l'iterazione su di essi. Ecco come aggiungere i tag all'esempio precedente
Activity currentActivity = Activity.Current;
TaskStatus status = (TaskStatus)evnt.Value.GetProperty("Status");
var tagsList = new StringBuilder();
foreach (var tags in currentActivity.Tags)
{
tagsList.Append($", {tags.Key}={tags.Value}");
}
serviceBusLogger.LogInformation($"{currentActivity.OperationName} is finished, Duration={currentActivity.Duration}, Status={status}, Id={currentActivity.Id}, StartTime={currentActivity.StartTimeUtc}{tagsList}");
Filtro e campionamento
In alcuni casi è consigliabile registrare solo parte degli eventi per ridurre il consumo dello spazio di archiviazione o il sovraccarico delle prestazioni. È possibile registrare solo gli eventi di tipo 'Stop' (come nell'esempio precedente) o una percentuale campione degli eventi.
A questo scopo, è possibile usare DiagnosticSource con il predicato IsEnabled. Per altre informazioni, vedere Context-Based Filtering in DiagnosticSource (Filtro in base al contesto in DiagnosticSource).
IsEnabled può essere chiamato più volte per una singola operazione per ridurre al minimo l'impatto sulle prestazioni.
IsEnabled viene chiamato nella sequenza seguente:
IsEnabled(<OperationName>, string entity, null) ad esempio, IsEnabled("Microsoft.Azure.ServiceBus.Send", "MyQueue1"). Si noti che alla fine non è presente alcun evento di tipo 'Start' o 'Stop'. Usarlo per escludere determinate operazioni o code. Se il metodo di callback restituisce false, gli eventi per l'operazione non vengono inviati
Per le operazioni di tipo 'Process' e 'ProcessSession' viene ricevuto anche il callback IsEnabled(<OperationName>, string entity, Activity activity). Usarlo per filtrare gli eventi in base alle proprietà activity.Id o Tags.
IsEnabled(<OperationName>.Start) ad esempio, IsEnabled("Microsoft.Azure.ServiceBus.Send.Start"). Controlla se deve essere attivato l'evento 'Start'. Il risultato interessa solo l'evento 'Start', ma l'ulteriore strumentazione non dipende da esso.
Non esiste alcun elemento IsEnabled per l'evento 'Stop'.
Se il risultato di alcune operazioni è un'eccezione, viene chiamato IsEnabled("Microsoft.Azure.ServiceBus.Exception"). È possibile eseguire la sottoscrizione solo di eventi 'Exception' e impedire il resto della strumentazione. In questo caso sarà comunque necessario gestire tali eccezioni. Dal momento che l'altra strumentazione è disabilitata, il contesto di rilevamento con i messaggi non verrà trasmesso dall’utente al produttore.
È possibile usare IsEnabled anche per implementare strategie di campionamento. Il campionamento basato su Activity.Id o Activity.RootId assicura il campionamento coerente in tutti i livelli (purché venga propagato dal sistema di rilevamento o dal codice personalizzato).
In presenza di più listener DiagnosticSource per la stessa origine, è sufficiente che l'evento venga accettato da un solo listener. Di conseguenza, non è garantito che venga chiamato IsEnabled.
Contenuto correlato
Una libreria che consente agli sviluppatori di avere i dati di telemetria generati per impostazione predefinita è NServiceBus.