1. 程式人生 > >.Net Core中的診斷日誌DiagnosticSource講解

.Net Core中的診斷日誌DiagnosticSource講解

### 前言     近期由於需要進行分散式鏈路跟蹤系統的技術選型,所以一直在研究鏈路跟蹤相關的框架。作為能在.Net Core中使用的APM,SkyWalking自然成為了首選。[SkyAPM-dotnet](https://github.com/SkyAPM/SkyAPM-dotnet)是SkyWalking在.Net Core端的探針實現,其主要的收集日誌的手段就是基於DiagnosticSource來進行診斷跟蹤的。不得不說SkyAPM-dotnet的設計還是非常優秀的,它本身定義了一套非常規範的標準,而且提供了非常良好的擴充套件性,雖然框架本身可支援的採集端有限,但是基於這套標準擴充套件起來還是非常方便的。 ### 概念介紹     關於DiagnosticSource它本身是一個基於釋出訂閱模式的工作模式,由於它本身的實現方式是非同步的,所以不僅僅可以把它用到日誌上,還可以用它實現非同步操作,或者用它簡化實現釋出訂閱的功能。DiagnosticSource本身是一個抽象類,我們最常用到的是它的子類DiagnosticListener,通過DiagnosticSource的Write方法實現釋出一條有具體名稱的訊息,然後通過IObserver去訂閱訊息。DiagnosticListener可以實現不同的例項,每個例項可以有自己的名稱,每個例項還可以釋出不同名稱的訊息,好比一個在寫程式碼的時候我們可以定義多個程式集,一個程式集下面可以包含多個名稱空間。 ### 使用方式 上面我們大致的介紹了關於DiagnosticSource相關的概念,相信大家已經有了初步的瞭解,接下來我們就來看一下在程式碼中如何使用DiagnosticSource,還說到了它一個重要的子類DiagnosticListener,基本上關於DiagnosticSource的工作方式都是圍繞著DiagnosticListener實現的,首先我們來看一下如何釋出一條訊息 ```cs //宣告DiagnosticListener並命名為MyTest DiagnosticSource diagnosticSource = new DiagnosticListener("MyTest"); string pubName = "MyTest.Log"; //判斷是否存在MyTest.Log的訂閱者 if (diagnosticSource.IsEnabled(pubName)) { //傳送名為MyTest.Log的訊息,包含Name,Address兩個屬性 diagnosticSource.Write(pubName, new { Name = "old王", Address="隔壁" }); } ``` 通過這種方式,我們就可以完成針對訊息的釋出,其中用到了IsEnabled方法,這個方法是在實際使用DiagnosticSource過程中比較常用的方法,用於判斷是夠存在對應名稱的消費者,這樣可以有效的避免傳送訊息浪費。 傳送相對還是比較簡單的,接下來我們看一下如何訂閱釋出的訊息。上面我們提到了訂閱訊息是通過IObserver介面實現的,IObserver代表了訂閱者。雖然我們通過DiagnosticSource去釋出訊息,但是真正描述釋出者身份的是IObservable介面,IObservable的唯一方法Subscribe是用來註冊訂閱者IObserver,但是預設系統並沒有為我們提供一個具體的實現類,所以我們需要定義一個IObserver訂閱者的實現類 ```cs public class MyObserver:IObserver { private Action _next; public MyObserver(Action next) { _next = next; } public void OnCompleted() { } public void OnError(Exception error) { } public void OnNext(T value) => _next(value); } ``` 有了具體的訂閱者實現類,我們就可以為釋出者註冊訂閱者了,同樣是使用DiagnosticListener,個人認為雖然操作都是通過DiagnosticSource來完成的,但它只是一個外觀類,但是並不能直接描述釋出者和訂閱者本身。接下來我們看一下具體實現 ```cs //AllListeners獲取所有釋出者,Subscribe為釋出者註冊訂閱者MyObserver DiagnosticListener.AllListeners.Subscribe(new MyObserver(listener => { //判斷髮布者的名字 if (listener.Name == "MyTest") { //獲取訂閱資訊 listener.Subscribe(new MyObserver>(listenerData => { System.Console.WriteLine($"監聽名稱:{listenerData.Key}"); dynamic data = listenerData.Value; //打印發布的訊息 System.Console.WriteLine($"獲取的資訊為:{data.Name}的地址是{data.Address}"); })); listener.SubscribeWithAdapter(new MyDiagnosticListener()); } })); ``` 具體實現可總結為兩步,首先為釋出者註冊訂閱者,然後獲取訂閱者獲取釋出的訊息。這種寫法還是比較複雜的,首先需要實現訂閱者類,然後通過一系列複雜的操作,才能完成訊息訂閱,然後還要自己獲取釋出的訊息,解析具體的訊息值,總之操作流程非常繁瑣。微軟似乎也意識到了這個問題,於是乎給我提供了一個關於實現訂閱者的便利方法,編輯專案檔案引入DiagnosticAdapter包 ```
``` 或者通過包管理器直接搜尋安裝,道路千萬條都是通羅馬。通過這個包解決了我們兩個痛點,首先是關於訂閱者的註冊難問題,其次解決了關於釋出訊息解析難的痛點。我們可以直接訂閱一個適配類來充當訂閱者的載體,其次我們可以定義方法模擬訂閱去訂閱訊息,而這個方法的引數就是我們釋出的訊息內容。說了這麼多,不如直接上程式碼 ```cs public class MyDiagnosticListener { //釋出的訊息主題名稱 [DiagnosticName("MyTest.Log")] //釋出的訊息引數名稱和釋出的屬性名稱要一致 public void MyLog(string name,string address) { System.Console.WriteLine($"監聽名稱:MyTest.Log"); System.Console.WriteLine($"獲取的資訊為:{name}的地址是{address}"); } } ``` 我們可以隨便定義一個類來充當訂閱者載體,類裡面可以自定義方法來實現獲取解析訊息的實現。想要讓方法可以訂閱訊息,需要在方法上宣告DiagnosticName,然後名稱就是你要訂閱訊息的名稱,而方法的引數就是你釋出訊息的欄位屬性名稱,這裡需要注意的是訂閱的引數名稱需要和釋出宣告屬性名稱一致。 然後我們直接可以通過這個類去接收訂閱訊息 ```cs DiagnosticListener.AllListeners.Subscribe(new MyObserver(listener => { if (listener.Name == "MyTest") { //適配訂閱 listener.SubscribeWithAdapter(new MyDiagnosticListener()); } })); ``` 可能你覺得這樣還是不夠好,因為還是沒有脫離需要自定義訂閱者,這裡還有更簡潔的實現方式。細心的你可能已經發現了SubscribeWithAdapter是DiagnosticListener的擴充套件方法,而我們宣告DiagnosticSource就是使用的DiagnosticListener例項,所以上面的程式碼可以簡化為一下方式 ```cs DiagnosticListener diagnosticListener = new DiagnosticListener("MyTest"); DiagnosticSource diagnosticSource = diagnosticListener; //直接去適配訂閱者 diagnosticListener.SubscribeWithAdapter(new MyDiagnosticListener()); string pubName = "MyTest.Log"; if (diagnosticSource.IsEnabled(pubName)) { diagnosticSource.Write(pubName, new { Name = "old王", Address="隔壁" }); } ``` 這種方式也是我們比較推薦的使用方式,極大的節省了工作的方式,而且程式碼非常的簡潔。但是存在唯一的不足,這種寫法只能針對特定的DiagnosticListener進行訂閱處理,如果你需要監聽所有釋出者,就需要使用DiagnosticListener.AllListeners.Subscribe的方式。 ### DotNetCore原始碼中診斷日誌的埋點 在.Net Core的原始碼中,微軟預設在涉及到網路請求或處理請求等許多重要的節點都使用了DiagnosticListener來發布攔截的訊息,接下來就羅列一些我知道的比較常見的埋點,通過這些操作我們就可以看出,診斷日誌還是很便利的,而且微軟在.Net Core中也非常重視它的使用。 #### 在ASP.NET Core中 當我們通過ConfigureWebHostDefaults配置Web主機的時候,程式就已經預設給我們注入了診斷名稱為Microsoft.AspNetCore的DiagnosticListener和DiagnosticSource,這樣我們就可以很方便的在程式中直接獲取DiagnosticListener例項去釋出訊息或者監聽釋出的內部訊息,具體注入邏輯位於可以去GenericWebHostBuilder類中檢視[[點選檢視原始碼