顺企网宁波网站建设,阿里云上传wordpress,做网站就上凡科建站,网站建设中最重要的前言DiagnosticSource是一个非常有意思的且非常有用的API#xff0c;对于这些API它们允许不同的库发送命名事件#xff0c;并且它们也允许应用程序订阅这些事件并处理它们#xff0c;它使我们的消费者可以在运行时动态发现数据源并且订阅与其相关的数据源。DiagnosticSource… 前言DiagnosticSource是一个非常有意思的且非常有用的API对于这些API它们允许不同的库发送命名事件并且它们也允许应用程序订阅这些事件并处理它们它使我们的消费者可以在运行时动态发现数据源并且订阅与其相关的数据源。DiagnosticSource在AspNetCore、EntityFrameworkCore、HttpClient、SqlClient中被使用在我们实际的开发过程中他使我们能够进行拦截请求与响应的http请求、数据库查询、对HttpContext、DbConnection、DbCommand、HttpRequestMessageand等对象的访问甚至说在需要的时候我们可以进行修改这些对象来处理我们的业务。下面我们将通过如下的简单示例来了解它.DiagnosticSource和EventSource区别DiagnosticSource和EventSource在架构设计上很相似他们的主要区别是EventSource它记录的数据是可序列化的数据会被进程外消费所以要求记录的对象必须是可以被序列化的。而DiagnosticSource被设计为在进程内处理数据所以我们通过它拿到的数据信息会比较丰富一些它支持非序列化的对象比如HttpContext、HttpResponseMessage等。另外如果想在EventSource中获取DiagnosticSource中的事件数据可以通过DiagnosticSourceEventSource这个对象来进行数据桥接。需求来了为了更好的理解DiagnosticSource的工作方式如下这个示例将拦截数据库请求假设我们有一个简单的控制台应用程序它向数据库发出请求并将结果输出到控制台。class Program
{public const string ConnectionString Serverlocalhost;Databasemaster;Trusted_ConnectionTrue;;static async Task Main(string[] args){var result await Get();Console.WriteLine(result);}public static async Taskint Get() {using (var connectionnew SqlConnection(ConnectionString)){return await connection.QuerySingleAsyncint(SELECT 42;);}}
}
我们再来思考一下假设来了一个需求我们需要获取到所有数据库查询的执行时间或者说我们要进行获取执行的一些sql语句或者数据进行存储作为记录我们该如何处理好了下面我们将尝试使用DiagnosticSource来实现该需求。使用System.Diagnostics.DiagnosticSource来吧我们先来创建一个类作为该事件的处理程序或者说作为该事件的消费者。public sealed class ExampleDiagnosticObserver
{}
下面我们将处理该事件我们需要将这个类进行实例化并且将它注册到静态对象中的观察器中DiagnosticListener.AllListeners代码如下所示static async Task Main(string[] args)
{var observer new ExampleDiagnosticObserver();IDisposable subscription DiagnosticListener.AllListeners.Subscribe(observer);var result await Get();Console.WriteLine(result);
}
下面我们再来修改我们的ExampleDiagnosticObserver类其实如上代码片段中编译器已经提醒我们要实现接口IObserverdiagnosticsListener下面我们实现它public sealed class ExampleDiagnosticObserver : IObserverDiagnosticListener
{public void OnCompleted(){}public void OnError(Exception error){}public void OnNext(DiagnosticListener value){Console.WriteLine(value.Name);}
}
接下来我们运行该程序结果将在控制台进行打印如下所示SqlClientDiagnosticListener
SqlClientDiagnosticListener
42
看如上结果这意味着在我们当前这个应用程序中的某个地方注册了两个类型为DiagnosticListener的对象名字为SqlClientDiagnosticListener。对于应用程序中创建的每个实例diagnosticsListener在第一次使用时将调用IObserverDiagnosticListener.OnNext方法一次现在我们只是将实例的名称输出到了控制台中但实际情况中我们想一下我们应该对这个实例名称做什么对没错我们要对这些实例名称做检查那么我们如果要对这个实例中某些事件我们只需要使用subscribe方法去订阅它。下面我们来实现IObserverDiagnosticListenerpublic class ExampleDiagnosticObserver1 : IObserverDiagnosticListener,IObserverKeyValuePairstring, object
{private readonly ListIDisposable _subscriptions new ListIDisposable();public void OnCompleted(){}public void OnError(Exception error){}public void OnNext(KeyValuePairstring, object value){Write(value.Key, value.Value);}public void OnNext(DiagnosticListener value){if (value.Name SqlClientDiagnosticListener){var subscription value.Subscribe(this);_subscriptions.Add(subscription);}}private void Write(string name, object value){Console.WriteLine(name);Console.WriteLine(value);Console.WriteLine();}
}
在如上代码片段中我们实现了接口IObserverKeyValuePairstring, object的IObserverKeyValuePairstringobject.OnNext的方法参数为KeyValuePairstringobject其中Key是事件的名称而Value是一个匿名对象.运行程序输出结果如下所示System.Data.SqlClient.WriteConnectionOpenBefore
{ OperationId f5f4d4f0-7aa1-46e6-bd48-78acca3dac0a, Operation OpenAsync, Connection System.Data.SqlClient.SqlConnection, Timestamp 1755845041766 }System.Data.SqlClient.WriteCommandBefore
{ OperationId 3d8617d1-0317-4f75-bffd-5b0fddf5cc12, Operation ExecuteReaderAsync, ConnectionId 554f4ee4-47c3-44ff-a967-cc343d1d5019, Command System.Data.SqlClient.SqlCommand }System.Data.SqlClient.WriteConnectionOpenAfter
{ OperationId f5f4d4f0-7aa1-46e6-bd48-78acca3dac0a, Operation OpenAsync, ConnectionId 554f4ee4-47c3-44ff-a967-cc343d1d5019, Connection System.Data.SqlClient.SqlConnection, Statistics System.Data.SqlClient.SqlStatisticsStatisticsDictionary, Timestamp 1755851869508 }System.Data.SqlClient.WriteCommandAfter
{ OperationId 3d8617d1-0317-4f75-bffd-5b0fddf5cc12, Operation ExecuteReaderAsync, ConnectionId 554f4ee4-47c3-44ff-a967-cc343d1d5019, Command System.Data.SqlClient.SqlCommand, Statistics System.Data.SqlClient.SqlStatisticsStatisticsDictionary, Timestamp 1755853467664 }System.Data.SqlClient.WriteConnectionCloseBefore
{ OperationId ed240163-c43a-4394-aa2d-3fede4b27488, Operation Close, ConnectionId 554f4ee4-47c3-44ff-a967-cc343d1d5019, Connection System.Data.SqlClient.SqlConnection, Statistics System.Data.SqlClient.SqlStatisticsStatisticsDictionary, Timestamp 1755854169373 }System.Data.SqlClient.WriteConnectionCloseAfter
{ OperationId ed240163-c43a-4394-aa2d-3fede4b27488, Operation Close, ConnectionId 554f4ee4-47c3-44ff-a967-cc343d1d5019, Connection System.Data.SqlClient.SqlConnection, Statistics System.Data.SqlClient.SqlStatisticsStatisticsDictionary, Timestamp 1755854291040 }42
如上结果可以清楚的看到里面存在6个事件我们可以看到两个是在打开数据库之前和之后执行的两个是在执行命令之前和之后执行的还有两个是在关闭数据库连接之前和之后执行的。另外可以看到每个事件中都包含一组参数如OperationId、Operation、ConnectionId等这些参数通常作为匿名对象属性传输我们可以通过反射来获取这些属性的类型化的值。现在我们解决了我们最初的需求获取数据库中所有查询的执行时间并将其输出到控制台中我们需要进行修改代码如下所示private readonly AsyncLocalStopwatch _stopwatch new AsyncLocalStopwatch();private void Write(string name, object value)
{switch (name){case System.Data.SqlClient.WriteCommandBefore:{_stopwatch.Value Stopwatch.StartNew();break;}case System.Data.SqlClient.WriteCommandAfter:{var stopwatch _stopwatch.Value;stopwatch.Stop();var command GetPropertySqlCommand(value, Command);Console.WriteLine($CommandText: {command.CommandText});Console.WriteLine($Elapsed: {stopwatch.Elapsed});Console.WriteLine();break;}}
}private static T GetPropertyT(object value, string name)
{return (T)value.GetType().GetProperty(name).GetValue(value);
}
在这我们将拦截数据库中查询的开始和结束事件在执行之前我们创建并且启动stopwatch将其存储在AsyncLocalstopwatch中以后面将其返回在执行完成后我们获取之前启动的stopwatch停止它通过反射从参数值中获取执行命令并将结果输出到控制台。执行结果如下所示CommandText: SELECT 42;
Elapsed: 00:00:00.150908642
现在我们已经解决了我们的需求但是目前还存在一个小的问题当我们订阅事件diagnosticListener时我们从它里面将接收到所有的事件包括我们不需要的事件但是呢发送的每个事件都会创建一个带有参数的匿名对象这会在GC上造成额外的压力。我们需要解决如上的问题避免我们去处理所有的事件我们需要指定Predicatestring这个特殊的委托类型我们声明IsEnabled方法在此筛选对应名称的消费者。下面我们修改一下方法IObserverDiagnosticListener.OnNextpublic void OnNext(DiagnosticListener value)
{if (value.Name SqlClientDiagnosticListener){var subscription value.Subscribe(this, IsEnabled);_subscriptions.Add(subscription);}
}private bool IsEnabled(string name)
{return name System.Data.SqlClient.WriteCommandBefore|| name System.Data.SqlClient.WriteCommandAfter;
}
现在我们只会对事件System.Data.SqlClient.WriteCommandBefore和System.Data.SqlClient.WriteCommandAfter调用Write方法。使用Microsoft.Extensions.DiagnosticAdapter上面虽然我们实现了需求但是我们也可以发现我们从DiagnosticListener接收到的事件参数通常作为匿名对象传递因此通过反射去处理这些参数这样给我们造成了比较昂贵的消耗不过开发团队也考虑到了该问题向我们提供了Microsoft.Extensions.DiagnosticAdapter来完成我们的操作。下面我们需要将Subscribe改为SubscribeWithAdapter,另外在这种情况下我们不需要实现IObserverKeyValuePairstring, object接口相反的是我们需要为每个事件声明一个单独的方法并且使用[DiagnosticNameAttribute]特性去标注如下所示public class ExampleDiagnosticObserver4 : IObserverDiagnosticListener
{private readonly ListIDisposable _subscriptions new ListIDisposable();private readonly AsyncLocalStopwatch _stopwatch new AsyncLocalStopwatch();public void OnCompleted(){}public void OnError(Exception error){}public void OnNext(DiagnosticListener value){if (value.Name SqlClientDiagnosticListener){var subscription value.SubscribeWithAdapter(this);_subscriptions.Add(subscription);}}[DiagnosticName(System.Data.SqlClient.WriteCommandBefore)]public void OnCommandBefore(){_stopwatch.Value Stopwatch.StartNew();}[DiagnosticName(System.Data.SqlClient.WriteCommandAfter)]public void OnCommandAfter(DbCommand command){var stopwatch _stopwatch.Value;stopwatch.Stop();Console.WriteLine($CommandText: {command.CommandText});Console.WriteLine($Elapsed: {stopwatch.Elapsed});Console.WriteLine();}
}
现在我们实现了对数据执行的监控或者说拦截功能同时也能为我们的数据库执行时间做记录并且特别注意的是我们并没有对应用程序本身做修改这样也减轻了很多的冗余同时节省了大量的编码时间。这是一个很不错的编程体验。创建DiagnosticListener实例在大多数情况下我们对DiagnosticSource都会去订阅已经存在的事件基本我们都不需要去创建自己的DiagnosticListener去发送事件当然去了解一下这一特性也是比较好的请继续往下看创建自己的实例private static readonly DiagnosticSource diagnosticSource new DiagnosticListener(MyLibraty);
发送事件我们将调用Write进行写入事件if (diagnosticSource.IsEnabled(MyEvent))diagnosticSource.Write(MyEvent, new { /* parameters */ });
参考https://github.com/dotnet/corefx/blob/master/src/System.Diagnostics.DiagnosticSource/src/DiagnosticSourceUsersGuide.mdhttps://sudonull.com/post/3671-Using-the-DiagnosticSource-in-NET-Core-Theoryhttps://github.com/dotnet/runtime/issues/20992https://github.com/hueifeng/BlogSample/tree/master/src/DiagnosticDemo