在.NET中使用DiagnosticSource的方法
前言
DiagnosticSource是一個非常有意思的且非常有用的API,對于這些API它們允許不同的庫發(fā)送命名事件,并且它們也允許應(yīng)用程序訂閱這些事件并處理它們,它使我們的消費(fèi)者可以在運(yùn)行時動態(tài)發(fā)現(xiàn)數(shù)據(jù)源并且訂閱與其相關(guān)的數(shù)據(jù)源。
DiagnosticSource在AspNetCore、EntityFrameworkCore、HttpClient、SqlClient中被使用,在我們實際的開發(fā)過程中他使我們能夠進(jìn)行攔截請求與響應(yīng)的http請求、數(shù)據(jù)庫查詢、對HttpContext、DbConnection、DbCommand、HttpRequestMessageand等對象的訪問,甚至說在需要的時候我們可以進(jìn)行修改這些對象來處理我們的業(yè)務(wù)。
下面我們將通過如下的簡單示例來了解它.
DiagnosticSource和EventSource區(qū)別
DiagnosticSource和EventSource在架構(gòu)設(shè)計上很相似,他們的主要區(qū)別是EventSource它記錄的數(shù)據(jù)是可序列化的數(shù)據(jù),會被進(jìn)程外消費(fèi),所以要求記錄的對象必須是可以被序列化的。而DiagnosticSource被設(shè)計為在進(jìn)程內(nèi)處理數(shù)據(jù),所以我們通過它拿到的數(shù)據(jù)信息會比較豐富一些,它支持非序列化的對象,比如HttpContext、HttpResponseMessage等。另外如果想在EventSource中獲取DiagnosticSource中的事件數(shù)據(jù),可以通過DiagnosticSourceEventSource這個對象來進(jìn)行數(shù)據(jù)橋接。
需求來了
為了更好的理解DiagnosticSource的工作方式,如下這個示例將攔截數(shù)據(jù)庫請求,假設(shè)我們有一個簡單的控制臺應(yīng)用程序,它向數(shù)據(jù)庫發(fā)出請求并將結(jié)果輸出到控制臺。
class Program
{
public const string ConnectionString =
@"Server=localhost;Database=master;Trusted_Connection=True;";
static async Task Main(string[] args)
{
var result = await Get();
Console.WriteLine(result);
}
public static async Task<int> Get() {
using (var connection=new SqlConnection(ConnectionString))
{
return await connection.QuerySingleAsync<int>("SELECT 42;");
}
}
}
我們再來思考一下,假設(shè)來了一個需求:我們需要獲取到所有數(shù)據(jù)庫查詢的執(zhí)行時間,或者說我們要進(jìn)行獲取執(zhí)行的一些sql語句或者數(shù)據(jù)進(jìn)行存儲作為記錄我們該如何處理?
好了下面我們將嘗試使用DiagnosticSource來實現(xiàn)該需求。
使用System.Diagnostics.DiagnosticSource
來吧,我們先來創(chuàng)建一個類作為該事件的處理程序或者說作為該事件的消費(fèi)者。
public sealed class ExampleDiagnosticObserver
{}
下面我們將處理該事件,我們需要將這個類進(jìn)行實例化,并且將它注冊到靜態(tài)對象中的觀察器中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類,其實如上代碼片段中編譯器已經(jīng)提醒我們要實現(xiàn)接口IObserver<diagnosticsListener>,下面我們實現(xiàn)它
public sealed class ExampleDiagnosticObserver : IObserver<DiagnosticListener>
{
public void OnCompleted()
{
}
public void OnError(Exception error)
{
}
public void OnNext(DiagnosticListener value)
{
Console.WriteLine(value.Name);
}
}
接下來我們運(yùn)行該程序,結(jié)果將在控制臺進(jìn)行打印如下所示:
SqlClientDiagnosticListener
SqlClientDiagnosticListener
42
看如上結(jié)果,這意味著在我們當(dāng)前這個應(yīng)用程序中的某個地方注冊了兩個類型為DiagnosticListener的對象,名字為SqlClientDiagnosticListener。
對于應(yīng)用程序中創(chuàng)建的每個實例diagnosticsListener,在第一次使用時將調(diào)用IObserver<DiagnosticListener>.OnNext方法一次,現(xiàn)在我們只是將實例的名稱輸出到了控制臺中,但實際情況中我們想一下,我們應(yīng)該對這個實例名稱做什么?對,沒錯,我們要對這些實例名稱做檢查,那么我們?nèi)绻獙@個實例中某些事件,我們只需要使用subscribe方法去訂閱它。
下面我們來實現(xiàn)IObserver<DiagnosticListener>:
public class ExampleDiagnosticObserver1 : IObserver<DiagnosticListener>,
IObserver<KeyValuePair<string, object>>
{
private readonly List<IDisposable> _subscriptions = new List<IDisposable>();
public void OnCompleted()
{
}
public void OnError(Exception error)
{
}
public void OnNext(KeyValuePair<string, 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();
}
}
在如上代碼片段中我們實現(xiàn)了接口IObserver<KeyValuePair<string, object>>的IObserver<KeyValuePair<string,object>>.OnNext的方法,參數(shù)為KeyValuePair<string,object>,其中Key是事件的名稱,而Value是一個匿名對象.
運(yùn)行程序輸出結(jié)果如下所示:
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.SqlStatistics+StatisticsDictionary, 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.SqlStatistics+StatisticsDictionary, 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.SqlStatistics+StatisticsDictionary, 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.SqlStatistics+StatisticsDictionary, Timestamp = 1755854291040 }42
如上結(jié)果可以清楚的看到里面存在6個事件,我們可以看到兩個是在打開數(shù)據(jù)庫之前和之后執(zhí)行的,兩個是在執(zhí)行命令之前和之后執(zhí)行的,還有兩個是在關(guān)閉數(shù)據(jù)庫連接之前和之后執(zhí)行的。
另外可以看到每個事件中都包含一組參數(shù),如OperationId、Operation、ConnectionId等,這些參數(shù)通常作為匿名對象屬性傳輸,我們可以通過反射來獲取這些屬性的類型化的值。
現(xiàn)在我們解決了我們最初的需求,獲取數(shù)據(jù)庫中所有查詢的執(zhí)行時間,并將其輸出到控制臺中,我們需要進(jìn)行修改,代碼如下所示:
private readonly AsyncLocal<Stopwatch> _stopwatch = new AsyncLocal<Stopwatch>();
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 = GetProperty<SqlCommand>(value, "Command");
Console.WriteLine($"CommandText: {command.CommandText}");
Console.WriteLine($"Elapsed: {stopwatch.Elapsed}");
Console.WriteLine();
break;
}
}
}
private static T GetProperty<T>(object value, string name)
{
return (T)value.GetType()
.GetProperty(name)
.GetValue(value);
}
在這我們將攔截數(shù)據(jù)庫中查詢的開始和結(jié)束事件,在執(zhí)行之前我們創(chuàng)建并且啟動stopwatch,將其存儲在AsyncLocal<stopwatch>中,以后面將其返回,在執(zhí)行完成后,我們獲取之前啟動的stopwatch,停止它,通過反射從參數(shù)值中獲取執(zhí)行命令,并將結(jié)果輸出到控制臺。
執(zhí)行結(jié)果如下所示:
CommandText: SELECT 42;
Elapsed: 00:00:00.150908642
現(xiàn)在我們已經(jīng)解決了我們的需求,但是目前還存在一個小的問題,當(dāng)我們訂閱事件diagnosticListener時,我們從它里面將接收到所有的事件,包括我們不需要的事件,但是呢發(fā)送的每個事件都會創(chuàng)建一個帶有參數(shù)的匿名對象,這會在GC上造成額外的壓力。
我們需要解決如上的問題,避免我們?nèi)ヌ幚硭械氖录?,我們需要指定Predicate<string>這個特殊的委托類型,我們聲明IsEnabled方法,在此篩選對應(yīng)名稱的消費(fèi)者。
下面我們修改一下方法IObserver<DiagnosticListener>.OnNext
public 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";
}
現(xiàn)在我們只會對事件System.Data.SqlClient.WriteCommandBefore和System.Data.SqlClient.WriteCommandAfter調(diào)用Write方法。
使用Microsoft.Extensions.DiagnosticAdapter
上面雖然我們實現(xiàn)了需求,但是我們也可以發(fā)現(xiàn)我們從DiagnosticListener接收到的事件參數(shù)通常作為匿名對象傳遞,因此通過反射去處理這些參數(shù)這樣給我們造成了比較昂貴的消耗,不過開發(fā)團(tuán)隊也考慮到了該問題向我們提供了Microsoft.Extensions.DiagnosticAdapter來完成我們的操作。
下面我們需要將Subscribe改為SubscribeWithAdapter,另外在這種情況下我們不需要實現(xiàn)IObserver<KeyValuePair<string, object>>接口,相反的是我們需要為每個事件聲明一個單獨的方法,并且使用[DiagnosticNameAttribute]特性去標(biāo)注
如下所示:
public class ExampleDiagnosticObserver4 : IObserver<DiagnosticListener>
{
private readonly List<IDisposable> _subscriptions = new List<IDisposable>();
private readonly AsyncLocal<Stopwatch> _stopwatch = new AsyncLocal<Stopwatch>();
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();
}
}
現(xiàn)在我們實現(xiàn)了對數(shù)據(jù)執(zhí)行的監(jiān)控或者說攔截功能,同時也能為我們的數(shù)據(jù)庫執(zhí)行時間做記錄,并且特別注意的是我們并沒有對應(yīng)用程序本身做修改,這樣也減輕了很多的冗余,同時節(jié)省了大量的編碼時間。這是一個很不錯的編程體驗。
創(chuàng)建DiagnosticListener實例
在大多數(shù)情況下,我們對DiagnosticSource都會去訂閱已經(jīng)存在的事件,基本我們都不需要去創(chuàng)建自己的DiagnosticListener去發(fā)送事件,當(dāng)然去了解一下這一特性也是比較好的,請繼續(xù)往下看
創(chuàng)建自己的實例
private static readonly DiagnosticSource diagnosticSource =
new DiagnosticListener("MyLibraty");
發(fā)送事件,我們將調(diào)用Write進(jìn)行寫入事件
if (diagnosticSource.IsEnabled("MyEvent"))
diagnosticSource.Write("MyEvent", new { /* parameters */ });
參考
https://sudonull.com/post/3671-Using-the-DiagnosticSource-in-NET-Core-Theory
https://github.com/dotnet/runtime/issues/20992
https://github.com/hueifeng/BlogSample/tree/master/src/DiagnosticDemo
到此這篇關(guān)于在.NET中使用DiagnosticSource的方法的文章就介紹到這了,更多相關(guān).NET使用DiagnosticSource內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
- Maven項目讀取resources文件路徑問題解決方案
- java(包括springboot)讀取resources下文件方式實現(xiàn)
- 怎么修改Ubuntu的源列表(source list)詳解
- ubuntu20.04 LTS系統(tǒng)默認(rèn)源sources.list文件的修改
- 修改ubuntu 18.04的sources.list源為阿里或清華鏡像的方法
- 解決SpringBoot打成jar運(yùn)行后無法讀取resources里的文件問題
- 基于Springboot2.3訪問本地路徑下靜態(tài)資源的方法(解決報錯:Not allowed to load local resource)
- IDEA下因Lombok插件產(chǎn)生的Library source does not match the bytecode報錯問題及解決方法(親測可用)
- Source Insight基礎(chǔ)配置相關(guān)代碼實例
相關(guān)文章
微信公眾平臺開發(fā)之發(fā)送圖文消息.Net代碼解析
這篇文章主要為大家詳細(xì)解析了微信公眾平臺開發(fā)之發(fā)送圖文消息.Net代碼,感興趣的小伙伴們可以參考一下2016-06-06
asp.net 枚舉文件里面的數(shù)字綁定到DropDownList里面去
將枚舉文件里面的數(shù)字綁定到DropDownList里面去的實現(xiàn)方法,大家可以借鑒下。2009-07-07
ASP.NET MVC使用EasyUI的datagrid多選提交保存教程
ASP.NET MVC使用EasyUI的datagrid多選提交保存教程,需要的朋友可以參考下。2011-12-12
用戶控件(ASCX)向網(wǎng)頁(ASPX)傳值使用反射實現(xiàn)
用戶控件向網(wǎng)頁傳遞值,網(wǎng)上的方法有很多,本文嘗試一下使用反射來實現(xiàn),感興趣的朋友可以參考下哈,希望可以幫助到你2013-03-03
Visual Studio 2017開發(fā)環(huán)境的安裝圖文教程
Visual Studio 2017是微軟于2017年3月8日正式推出的新版本,是迄今為止 最具生產(chǎn)力 的 Visual Studio 版本。這篇文章主要介紹了Visual Studio 2017開發(fā)環(huán)境的安裝,需要的朋友可以參考下2017-11-11
ASP.NET實現(xiàn)數(shù)據(jù)的添加(第10節(jié))
這篇文章主要介紹了ASP.NET如何實現(xiàn)數(shù)據(jù)的添加,文中示例代碼介紹的非常詳細(xì),具有一定的參考價值,感興趣的小伙伴們可以參考一下2015-08-08
詳解ASP.NET Core 2.0 路由引擎之網(wǎng)址生成(譯)
這篇文章主要介紹了詳解ASP.NET Core 2.0 路由引擎之網(wǎng)址生成(譯),小編覺得挺不錯的,現(xiàn)在分享給大家,也給大家做個參考。一起跟隨小編過來看看吧2017-11-11

