gebeurtenis
17 mrt, 21 - 21 mrt, 10
Neem deel aan de meetup-serie om schaalbare AI-oplossingen te bouwen op basis van praktijkgebruiksvoorbeelden met collega-ontwikkelaars en experts.
Nu registrerenDeze browser wordt niet meer ondersteund.
Upgrade naar Microsoft Edge om te profiteren van de nieuwste functies, beveiligingsupdates en technische ondersteuning.
Dit artikel is van toepassing op: ✔️ .NET Core 3.1 en latere versies ✔️ .NET Framework 4.5 en latere versies
System.Diagnostics.DiagnosticSource is een module waarmee code kan worden geïnstrumenteerd voor logboekregistratie van uitgebreide gegevenspayloads voor gebruik binnen het proces dat is geïnstrumenteerd. Tijdens runtime kunnen consumenten gegevensbronnen dynamisch detecteren en zich abonneren op de bronnen die van belang zijn. System.Diagnostics.DiagnosticSource is ontworpen om in-process tools toegang te geven tot uitgebreide gegevens. Bij gebruik System.Diagnostics.DiagnosticSourcewordt ervan uitgegaan dat de consument zich binnen hetzelfde proces bevindt en daardoor niet-serialiseerbare typen (bijvoorbeeld HttpResponseMessage
of HttpContext
) kunnen worden doorgegeven, zodat klanten voldoende gegevens kunnen gebruiken om mee te werken.
In dit scenario ziet u hoe u een DiagnosticSource-gebeurtenis en instrumentcode maakt met System.Diagnostics.DiagnosticSource. Vervolgens wordt uitgelegd hoe u de gebeurtenis kunt gebruiken door interessante DiagnosticListeners te vinden, zich te abonneren op hun gebeurtenissen en de nettoladingen van gebeurtenisgegevens te decoderen. Het wordt voltooid door het filteren te beschrijven, waardoor alleen specifieke gebeurtenissen door het systeem kunnen worden doorgegeven.
U werkt met de volgende code. Deze code is een HttpClient-klasse met een SendWebRequest
methode die een HTTP-aanvraag naar de URL verzendt en een antwoord ontvangt.
using System.Diagnostics;
MyListener TheListener = new MyListener();
TheListener.Listening();
HTTPClient Client = new HTTPClient();
Client.SendWebRequest("https://learn.microsoft.com/dotnet/core/diagnostics/");
class HTTPClient
{
private static DiagnosticSource httpLogger = new DiagnosticListener("System.Net.Http");
public byte[] SendWebRequest(string url)
{
if (httpLogger.IsEnabled("RequestStart"))
{
httpLogger.Write("RequestStart", new { Url = url });
}
//Pretend this sends an HTTP request to the url and gets back a reply.
byte[] reply = new byte[] { };
return reply;
}
}
class Observer<T> : IObserver<T>
{
public Observer(Action<T> onNext, Action onCompleted)
{
_onNext = onNext ?? new Action<T>(_ => { });
_onCompleted = onCompleted ?? new Action(() => { });
}
public void OnCompleted() { _onCompleted(); }
public void OnError(Exception error) { }
public void OnNext(T value) { _onNext(value); }
private Action<T> _onNext;
private Action _onCompleted;
}
class MyListener
{
IDisposable networkSubscription;
IDisposable listenerSubscription;
private readonly object allListeners = new();
public void Listening()
{
Action<KeyValuePair<string, object>> whenHeard = delegate (KeyValuePair<string, object> data)
{
Console.WriteLine($"Data received: {data.Key}: {data.Value}");
};
Action<DiagnosticListener> onNewListener = delegate (DiagnosticListener listener)
{
Console.WriteLine($"New Listener discovered: {listener.Name}");
//Subscribe to the specific DiagnosticListener of interest.
if (listener.Name == "System.Net.Http")
{
//Use lock to ensure the callback code is thread safe.
lock (allListeners)
{
if (networkSubscription != null)
{
networkSubscription.Dispose();
}
IObserver<KeyValuePair<string, object>> iobserver = new Observer<KeyValuePair<string, object>>(whenHeard, null);
networkSubscription = listener.Subscribe(iobserver);
}
}
};
//Subscribe to discover all DiagnosticListeners
IObserver<DiagnosticListener> observer = new Observer<DiagnosticListener>(onNewListener, null);
//When a listener is created, invoke the onNext function which calls the delegate.
listenerSubscription = DiagnosticListener.AllListeners.Subscribe(observer);
}
// Typically you leave the listenerSubscription subscription active forever.
// However when you no longer want your callback to be called, you can
// call listenerSubscription.Dispose() to cancel your subscription to the IObservable.
}
Het uitvoeren van de opgegeven implementatie wordt afgedrukt naar de console.
New Listener discovered: System.Net.Http
Data received: RequestStart: { Url = https://learn.microsoft.com/dotnet/core/diagnostics/ }
Het DiagnosticSource
type is een abstracte basisklasse die de methoden definieert die nodig zijn om gebeurtenissen te registreren. De klasse die de implementatie bevat, is DiagnosticListener
.
De eerste stap bij het instrumenteren van code is DiagnosticSource
het maken van een DiagnosticListener
. Voorbeeld:
private static DiagnosticSource httpLogger = new DiagnosticListener("System.Net.Http");
U ziet dat httpLogger
is getypt als een DiagnosticSource
.
Dat komt doordat deze code alleen gebeurtenissen schrijft en dus alleen betrekking heeft op de DiagnosticSource
methoden die door de DiagnosticListener
code worden geïmplementeerd. DiagnosticListeners
worden namen gegeven wanneer ze worden gemaakt en deze naam moet de naam zijn van een logische groepering van gerelateerde gebeurtenissen (meestal het onderdeel).
Later wordt deze naam gebruikt om de listener te vinden en u te abonneren op een van de gebeurtenissen.
De gebeurtenisnamen hoeven dus alleen uniek te zijn binnen een onderdeel.
De DiagnosticSource
logboekregistratie-interface bestaat uit twee methoden:
bool IsEnabled(string name)
void Write(string name, object value);
Dit is instrumentsitespecifiek. U moet de instrumentatiesite controleren om te zien welke typen worden doorgegeven IsEnabled
. Dit biedt u de informatie om te weten waarnaar u de nettolading moet casten.
Een typische oproepsite ziet er als volgt uit:
if (httpLogger.IsEnabled("RequestStart"))
{
httpLogger.Write("RequestStart", new { Url = url });
}
Elke gebeurtenis heeft een string
naam (bijvoorbeeld RequestStart
) en precies één object
als nettolading.
Als u meer dan één item wilt verzenden, kunt u dit doen door een object
met eigenschappen te maken om alle gegevens ervan weer te geven. De functie anoniem type van C# wordt meestal gebruikt om een type te maken dat 'on the fly' wordt doorgegeven en maakt dit schema erg handig. Op de instrumentatiesite moet u de aanroep bewaken Write()
met een IsEnabled()
controle op dezelfde gebeurtenisnaam. Zonder deze controle, zelfs als de instrumentatie inactief is, vereisen de regels van de C#-taal dat alle werkzaamheden voor het maken van de nettolading object
en het aanroepen Write()
worden uitgevoerd, zelfs als er niets daadwerkelijk naar de gegevens luistert. Door de Write()
aanroep te bewaken, maakt u deze efficiënt wanneer de bron niet is ingeschakeld.
Alles combineren wat u hebt:
class HTTPClient
{
private static DiagnosticSource httpLogger = new DiagnosticListener("System.Net.Http");
public byte[] SendWebRequest(string url)
{
if (httpLogger.IsEnabled("RequestStart"))
{
httpLogger.Write("RequestStart", new { Url = url });
}
//Pretend this sends an HTTP request to the url and gets back a reply.
byte[] reply = new byte[] { };
return reply;
}
}
De eerste stap bij het ontvangen van gebeurtenissen is om te ontdekken waar DiagnosticListeners
u geïnteresseerd in bent. DiagnosticListener
ondersteunt een manier om te detecteren DiagnosticListeners
dat actief is in het systeem tijdens runtime. De API om dit te bereiken is de AllListeners eigenschap.
Implementeer een Observer<T>
klasse die wordt overgenomen van de IObservable
interface. Dit is de callback-versie van de IEnumerable
interface. Meer informatie hierover vindt u op de site reactieve extensies .
Een IObserver
heeft drie callbacks, OnNext
, en OnComplete
OnError
. Een IObservable
heeft één methode die wordt aangeroepen Subscribe
die wordt doorgegeven aan een van deze waarnemers. Zodra de verbinding is gemaakt, krijgt de waarnemer callbacks (meestal OnNext
callbacks) wanneer er iets gebeurt.
Een typisch gebruik van de AllListeners
statische eigenschap ziet er als volgt uit:
class Observer<T> : IObserver<T>
{
public Observer(Action<T> onNext, Action onCompleted)
{
_onNext = onNext ?? new Action<T>(_ => { });
_onCompleted = onCompleted ?? new Action(() => { });
}
public void OnCompleted() { _onCompleted(); }
public void OnError(Exception error) { }
public void OnNext(T value) { _onNext(value); }
private Action<T> _onNext;
private Action _onCompleted;
}
class MyListener
{
IDisposable networkSubscription;
IDisposable listenerSubscription;
private readonly object allListeners = new();
public void Listening()
{
Action<KeyValuePair<string, object>> whenHeard = delegate (KeyValuePair<string, object> data)
{
Console.WriteLine($"Data received: {data.Key}: {data.Value}");
};
Action<DiagnosticListener> onNewListener = delegate (DiagnosticListener listener)
{
Console.WriteLine($"New Listener discovered: {listener.Name}");
//Subscribe to the specific DiagnosticListener of interest.
if (listener.Name == "System.Net.Http")
{
//Use lock to ensure the callback code is thread safe.
lock (allListeners)
{
if (networkSubscription != null)
{
networkSubscription.Dispose();
}
IObserver<KeyValuePair<string, object>> iobserver = new Observer<KeyValuePair<string, object>>(whenHeard, null);
networkSubscription = listener.Subscribe(iobserver);
}
}
};
//Subscribe to discover all DiagnosticListeners
IObserver<DiagnosticListener> observer = new Observer<DiagnosticListener>(onNewListener, null);
//When a listener is created, invoke the onNext function which calls the delegate.
listenerSubscription = DiagnosticListener.AllListeners.Subscribe(observer);
}
// Typically you leave the listenerSubscription subscription active forever.
// However when you no longer want your callback to be called, you can
// call listenerSubscription.Dispose() to cancel your subscription to the IObservable.
}
Met deze code maakt u een callback-gemachtigde en vraagt u met behulp van de AllListeners.Subscribe
methode aan dat de gemachtigde wordt aangeroepen voor elke actieve in DiagnosticListener
het systeem. De beslissing of de listener al dan niet op de listener moet worden geabonneerd door de naam ervan te inspecteren. De bovenstaande code zoekt naar de listener 'System.Net.Http' die u eerder hebt gemaakt.
Zoals alle aanroepen Subscribe()
, retourneert deze een IDisposable
die het abonnement zelf vertegenwoordigt.
Callbacks blijven plaatsvinden zolang er niets wordt aangeroepen Dispose()
voor dit abonnementsobject.
In het codevoorbeeld worden nooit aangeroepen Dispose()
, zodat er altijd callbacks worden ontvangen.
Wanneer u zich abonneert AllListeners
, krijgt u een callback voor ALL ACTIVE DiagnosticListeners
.
Wanneer u zich abonneert, krijgt u dus een flurry callback voor alle bestaande DiagnosticListeners
, en als er nieuwe worden gemaakt, ontvangt u ook een callback voor die. U ontvangt een volledige lijst met alles waarop u zich kunt abonneren.
Een DiagnosticListener
implementeert de IObservable<KeyValuePair<string, object>>
interface, zodat u deze ook kunt aanroepen Subscribe()
. De volgende code laat zien hoe u het vorige voorbeeld invult:
IDisposable networkSubscription;
IDisposable listenerSubscription;
private readonly object allListeners = new();
public void Listening()
{
Action<KeyValuePair<string, object>> whenHeard = delegate (KeyValuePair<string, object> data)
{
Console.WriteLine($"Data received: {data.Key}: {data.Value}");
};
Action<DiagnosticListener> onNewListener = delegate (DiagnosticListener listener)
{
Console.WriteLine($"New Listener discovered: {listener.Name}");
//Subscribe to the specific DiagnosticListener of interest.
if (listener.Name == "System.Net.Http")
{
//Use lock to ensure the callback code is thread safe.
lock (allListeners)
{
if (networkSubscription != null)
{
networkSubscription.Dispose();
}
IObserver<KeyValuePair<string, object>> iobserver = new Observer<KeyValuePair<string, object>>(whenHeard, null);
networkSubscription = listener.Subscribe(iobserver);
}
}
};
//Subscribe to discover all DiagnosticListeners
IObserver<DiagnosticListener> observer = new Observer<DiagnosticListener>(onNewListener, null);
//When a listener is created, invoke the onNext function which calls the delegate.
listenerSubscription = DiagnosticListener.AllListeners.Subscribe(observer);
}
In dit voorbeeld wordt na het vinden van system.Net.Http DiagnosticListener
een actie gemaakt waarmee de naam van de listener, gebeurtenis en payload.ToString()
.
Notitie
DiagnosticListener
implementeert IObservable<KeyValuePair<string, object>>
. Dit betekent dat we voor elke callback een KeyValuePair
. De sleutel van dit paar is de naam van de gebeurtenis en de waarde is de nettolading object
. In het voorbeeld wordt deze informatie gewoon in de console opgeslagen.
Het is belangrijk om abonnementen bij te houden op de DiagnosticListener
. In de vorige code onthoudt de networkSubscription
variabele dit. Als u nog een creation
formulier opgeeft, moet u de vorige listener afmelden en u abonneren op de nieuwe.
De DiagnosticSource
/DiagnosticListener
code is thread veilig, maar de callback-code moet ook thread-veilig zijn. Om ervoor te zorgen dat de callback-code veilig is, worden vergrendelingen gebruikt. Het is mogelijk om twee DiagnosticListeners
met dezelfde naam tegelijk te maken. Om racevoorwaarden te voorkomen, worden updates van gedeelde variabelen uitgevoerd onder de beveiliging van een vergrendeling.
Zodra de vorige code is uitgevoerd, wordt de volgende keer dat de Write()
informatie op System.Net.Http DiagnosticListener
wordt uitgevoerd, geregistreerd bij de console.
Abonnementen zijn onafhankelijk van elkaar. Als gevolg hiervan kan andere code precies hetzelfde doen als het codevoorbeeld en twee 'pipes' genereren van de logboekgegevens.
De KeyvaluePair
naam en nettolading die aan de callback worden doorgegeven, hebben de gebeurtenisnaam en nettolading, maar de nettolading wordt gewoon als een object
. Er zijn twee manieren om specifiekere gegevens op te halen:
Als de nettolading een bekend type is (bijvoorbeeld een string
of een HttpMessageRequest
), kunt u gewoon het object
verwachte type casten (met behulp van de as
operator zodat er geen uitzondering ontstaat als u verkeerd bent) en vervolgens toegang tot de velden krijgt. Dit is zeer efficiënt.
Reflectie-API gebruiken. Stel dat de volgende methode aanwezig is.
/// Define a shortcut method that fetches a field of a particular name.
static class PropertyExtensions
{
static object GetProperty(this object _this, string propertyName)
{
return _this.GetType().GetTypeInfo().GetDeclaredProperty(propertyName)?.GetValue(_this);
}
}
Als u de nettolading volledig wilt decoderen, kunt u de listener.Subscribe()
aanroep vervangen door de volgende code.
networkSubscription = listener.Subscribe(delegate(KeyValuePair<string, object> evnt) {
var eventName = evnt.Key;
var payload = evnt.Value;
if (eventName == "RequestStart")
{
var url = payload.GetProperty("Url") as string;
var request = payload.GetProperty("Request");
Console.WriteLine("Got RequestStart with URL {0} and Request {1}", url, request);
}
});
Houd er rekening mee dat het gebruik van weerspiegeling relatief duur is. Het gebruik van weerspiegeling is echter de enige optie als de nettoladingen zijn gegenereerd met behulp van anonieme typen. Deze overhead kan worden verminderd door snelle, gespecialiseerde eigenschaps fetchers te maken met behulp van PropertyInfo.GetMethod.CreateDelegate() of System.Reflection.Emit naamruimte, maar dat valt buiten het bereik van dit artikel.
(Voor een voorbeeld van een snelle, op gedelegeerden gebaseerde eigenschap ophalen, raadpleegt u de PropertySpec-klasse gebruikt in de DiagnosticSourceEventSource
.)
In het vorige voorbeeld gebruikt de code de IObservable.Subscribe()
methode om de callback te koppelen. Dit zorgt ervoor dat alle gebeurtenissen aan de callback worden gegeven. DiagnosticListener
Er zijn echter overbelastingen Subscribe()
waardoor de controller kan bepalen welke gebeurtenissen worden gegeven.
De listener.Subscribe()
aanroep in het vorige voorbeeld kan worden vervangen door de volgende code om te demonstreren.
// Create the callback delegate.
Action<KeyValuePair<string, object>> callback = (KeyValuePair<string, object> evnt) =>
Console.WriteLine("From Listener {0} Received Event {1} with payload {2}", networkListener.Name, evnt.Key, evnt.Value.ToString());
// Turn it into an observer (using the Observer<T> Class above).
Observer<KeyValuePair<string, object>> observer = new Observer<KeyValuePair<string, object>>(callback);
// Create a predicate (asks only for one kind of event).
Predicate<string> predicate = (string eventName) => eventName == "RequestStart";
// Subscribe with a filter predicate.
IDisposable subscription = listener.Subscribe(observer, predicate);
// subscription.Dispose() to stop the callbacks.
Dit abonneert zich efficiënt op alleen de 'RequestStart'-gebeurtenissen. Alle andere gebeurtenissen zorgen ervoor dat de DiagnosticSource.IsEnabled()
methode wordt geretourneerd false
en dus efficiënt wordt gefilterd.
Notitie
Filteren is alleen ontworpen als optimalisatie van prestaties. Het is mogelijk dat een listener gebeurtenissen ontvangt, zelfs wanneer deze niet voldoen aan het filter. Dit kan gebeuren omdat een andere listener zich heeft geabonneerd op de gebeurtenis of omdat de bron van de gebeurtenis IsEnabled() niet heeft gecontroleerd voordat deze werd verzonden. Als u zeker wilt zijn dat een bepaalde gebeurtenis voldoet aan het filter, moet u dit controleren in de callback. Voorbeeld:
Action<KeyValuePair<string, object>> callback = (KeyValuePair<string, object> evnt) =>
{
if(predicate(evnt.Key)) // only print out events that satisfy our filter
{
Console.WriteLine("From Listener {0} Received Event {1} with payload {2}", networkListener.Name, evnt.Key, evnt.Value.ToString());
}
};
Voor sommige scenario's is geavanceerd filteren vereist op basis van uitgebreide context. Producenten kunnen overbelastingen aanroepen DiagnosticSource.IsEnabled en aanvullende gebeurteniseigenschappen leveren, zoals wordt weergegeven in de volgende code.
//aRequest and anActivity are the current request and activity about to be logged.
if (httpLogger.IsEnabled("RequestStart", aRequest, anActivity))
httpLogger.Write("RequestStart", new { Url="http://clr", Request=aRequest });
In het volgende codevoorbeeld ziet u dat consumenten dergelijke eigenschappen kunnen gebruiken om gebeurtenissen nauwkeuriger te filteren.
// Create a predicate (asks only for Requests for certain URIs)
Func<string, object, object, bool> predicate = (string eventName, object context, object activity) =>
{
if (eventName == "RequestStart")
{
if (context is HttpRequestMessage request)
{
return IsUriEnabled(request.RequestUri);
}
}
return false;
}
// Subscribe with a filter predicate
IDisposable subscription = listener.Subscribe(observer, predicate);
Producenten zijn niet op de hoogte van het filter dat een consument heeft verstrekt. DiagnosticListener
roept het opgegeven filter aan en laat indien nodig aanvullende argumenten weg, waardoor het filter een null
context verwacht.
Als een producent aanroept IsEnabled()
met de gebeurtenisnaam en -context, worden deze aanroepen ingesloten in een overbelasting die alleen de naam van de gebeurtenis accepteert. Consumenten moeten ervoor zorgen dat hun filter gebeurtenissen zonder context toestaat door te geven.
.NET-feedback
.NET is een open source project. Selecteer een koppeling om feedback te geven:
gebeurtenis
17 mrt, 21 - 21 mrt, 10
Neem deel aan de meetup-serie om schaalbare AI-oplossingen te bouwen op basis van praktijkgebruiksvoorbeelden met collega-ontwikkelaars en experts.
Nu registrerenTraining
Leertraject
Use advance techniques in canvas apps to perform custom updates and optimization - Training
Use advance techniques in canvas apps to perform custom updates and optimization