<kbd id="afajh"><form id="afajh"></form></kbd>
<strong id="afajh"><dl id="afajh"></dl></strong>
    <del id="afajh"><form id="afajh"></form></del>
        1. <th id="afajh"><progress id="afajh"></progress></th>
          <b id="afajh"><abbr id="afajh"></abbr></b>
          <th id="afajh"><progress id="afajh"></progress></th>

          診斷日志知多少 | DiagnosticSource 在.NET上的應(yīng)用

          共 15390字,需瀏覽 31分鐘

           ·

          2020-10-13 11:48



          1. 引言

          最近為了解決ABP集成CAP時無法通過攔截器啟用工作單元的問題,從小伙伴那里學(xué)了一招。借助DiagnossticSource,可以最小改動完成需求。關(guān)于DiagnosticSource曉東大佬18年在文章 在 .NET Core 中使用 Diagnostics (Diagnostic Source) 記錄跟蹤信息就有介紹,文章開頭就說明了Diagnostics 一直是一個被大多數(shù)開發(fā)者忽視的東西。是的,我也忽略了,這個好東西,有必要學(xué)習(xí)一下,下面就和大家簡單聊一聊System.Diagnostics.DiagnosticSource在.NET上的應(yīng)用。

          2. System.Diagnostics.DiagnosticSource

          Diagnostics位于System命名空間下,由此可見Diagnostics在.NET 運行時中的地位不可小覷。其中System.Diagnostics命名空間下又包含不同類庫,提供了允許與系統(tǒng)進(jìn)程,事件日志和性能計數(shù)器進(jìn)行交互的類。如下圖所示:

          Diagnostic Namespace

          其中System.Diagnostics.DiagnosticSource模塊,它允許對代碼進(jìn)行檢測,以在生產(chǎn)時記錄豐富的數(shù)據(jù)負(fù)載(可以傳遞不可序列化的數(shù)據(jù)類型),以便在進(jìn)程內(nèi)進(jìn)行消耗。消費者可以在運行時動態(tài)發(fā)現(xiàn)數(shù)據(jù)源并訂閱感興趣的數(shù)據(jù)源。

          在展開之前,有必要先梳理下涉及的以下核心概念:

          • IObservable:可觀測對象
          • IObserver:觀察者
          • DiagnosticSource :診斷來源
          • DiagnosticListener:診斷監(jiān)聽器
          • Activity:活動

          3. 觀察者模式(IObservable & IObserver)

          IObservable?和IObserver位于System命名空間下,是.NET中對觀察者模式的抽象。

          觀察者設(shè)計模式使觀察者能夠從可觀察對象訂閱并接收通知。?它適用于需要基于推送通知的任何方案。?此模式定義可觀察對象,以及零個、一個或多個觀察者。?觀察者訂閱可觀察對象,并且每當(dāng)預(yù)定義的條件、事件或狀態(tài)發(fā)生更改時,該可觀察對象會通過調(diào)用其方法之一來自動通知所有觀察者。?在此方法調(diào)用中,該可觀察對象還可向觀察者提供當(dāng)前狀態(tài)信息。?在 .NET Framework 中,通過實現(xiàn)泛型 System.IObservable 和 System.IObserver 接口來應(yīng)用觀察者設(shè)計模式。泛型類型參數(shù)表示提供通知信息的類型。?泛型類型參數(shù)表示提供通知信息的類型。

          第一次學(xué)習(xí)觀察者模式,應(yīng)該是大學(xué)課本中基于事件燒水的例子,咱們就基于此實現(xiàn)個簡單的Demo吧。首先執(zhí)行dotnet new web -n Dotnet.Diagnostic.Demo創(chuàng)建示例項目。

          3.1. 定義可觀察對象(實現(xiàn)IObservable接口)

          對于燒水的示例,主要關(guān)注水溫的變化,因此先定義Temperature來表示溫度變化:

          public?class?Temperature
          {
          ????public?Temperature(decimal?temperature,?DateTime?date)
          ????{
          ????????Degree?=?temperature;
          ????????Date?=?date;
          ????}
          ????public?decimal?Degree?{?get;??}
          ????public?DateTime?Date?{?get;??}
          }

          接下來通過實現(xiàn)IObservable接口來定義可觀察對象。

          public?interface?IObservable
          {
          ??IDisposable?Subscribe(IObserver?observer);
          }

          從接口申明來看,只定義了一個Subscribe方法,從觀察者模式講,觀察者應(yīng)該既能訂閱又能取消訂閱消息。為什么沒有定義一個UnSubscribe方法呢?其實這里方法申明已經(jīng)說明,期望通過返回IDisposable對象的Dispose方法來達(dá)到這個目的。

          ///?
          ///?熱水壺
          ///?

          public?class?Kettle?:?IObservable
          {
          ????private?List>?observers;
          ????private?decimal?temperature?=?0;

          ????public?Kettle()
          ????{
          ????????observers?=?new?List>();
          ????}

          ????public?decimal?Temperature
          ????{
          ????????get?=>?temperature;
          ????????private?set
          ????????{
          ????????????temperature?=?value;
          ????????????observers.ForEach(observer?=>?observer.OnNext(new?Temperature(temperature,?DateTime.Now)));
          ????????????
          ????????????if?(temperature?==?100)
          ????????????????observers.ForEach(observer?=>?observer.OnCompleted());
          ????????}
          ????}
          ????public?IDisposable?Subscribe(IObserver?observer)
          ????{
          ????????if?(!observers.Contains(observer))
          ????????{
          ????????????Console.WriteLine("Subscribed!");
          ????????????observers.Add(observer);
          ????????}
          ????????//使用UnSubscriber包裝,返回IDisposable對象,用于觀察者取消訂閱
          ????????return?new?UnSubscriber(observers,?observer);
          ????}
          ????///?
          ????///?燒水方法
          ????///?

          ????public?async?Task?StartBoilWaterAsync()
          ????{
          ????????var?random?=?new?Random(DateTime.Now.Millisecond);
          ????????while?(Temperature?????????{
          ????????????Temperature?+=?10;
          ????????????await?Task.Delay(random.Next(5000));
          ????????}
          ????}
          }

          //定義泛型取消訂閱對象,用于取消訂閱
          internal?class?UnSubscriber?:?IDisposable
          {
          ????private?List>?_observers;
          ????private?IObserver?_observer;
          ????internal?UnSubscriber(List>?observers,?IObserver?observer)
          ????{
          ????????this._observers?=?observers;
          ????????this._observer?=?observer;
          ????}
          ????public?void?Dispose()
          ????{
          ????????if?(_observers.Contains(_observer))
          ????????{
          ????????????Console.WriteLine("Unsubscribed!");
          ????????????_observers.Remove(_observer);
          ????????}
          ????}
          }

          以上代碼中List>存在線程安全問題,因為簡單Demo,就不予優(yōu)化了。

          3.2. 定義觀察者(實現(xiàn)IObserver接口)

          比如定義一個報警器,實時播報溫度。

          public?class?Alter?:?IObserver
          {
          ????public?void?OnCompleted()
          ????{
          ????????Console.WriteLine("du?du?du?!!!");
          ????}
          ????public?void?OnError(Exception?error)
          ????{
          ????????//Nothing?to?do
          ????}
          ????public?void?OnNext(Temperature?value)
          ????{
          ????????Console.WriteLine($"{value.Date.ToString()}:?Current?temperature?is?{value.Degree}.");
          ????}
          }

          添加測試代碼,訪問localhost:5000/subscriber控制臺輸出結(jié)果如下:

          endpoints.MapGet("/subscriber",?async?context?=>
          {
          ????var?kettle?=?new?Kettle();//初始化熱水壺
          ????var?subscribeRef?=?kettle.Subscribe(new?Alter());//訂閱
          ????
          ????var?boilTask?=?kettle.StartBoilWaterAsync();//啟動開始燒水任務(wù)
          ????var?timoutTask?=?Task.Delay(TimeSpan.FromSeconds(15));//定義15s超時任務(wù)
          ????//等待,如果超時任務(wù)先返回則取消訂閱
          ????var?firstReturnTask?=?await?Task.WhenAny(boilTask,?timoutTask);
          ????if?(firstReturnTask?==?timoutTask)
          ????????subscribeRef.Dispose();
          ????await?context.Response.WriteAsync("Hello?subscriber!");
          });

          ------------------------------------------------------------------

          Subscribed!
          10/2/2020?4:53:20?PM:?Current?temperature?is?10.
          10/2/2020?4:53:20?PM:?Current?temperature?is?20.
          10/2/2020?4:53:21?PM:?Current?temperature?is?30.
          10/2/2020?4:53:21?PM:?Current?temperature?is?40.
          10/2/2020?4:53:24?PM:?Current?temperature?is?50.
          10/2/2020?4:53:25?PM:?Current?temperature?is?60.
          10/2/2020?4:53:26?PM:?Current?temperature?is?70.
          10/2/2020?4:53:30?PM:?Current?temperature?is?80.
          Unsubscribed!

          4. DiagnosticSource & DiagnosticListener

          4.1. 概念講解

          DiagnosticSource直譯就是診斷源,也就是它是診斷日志的來源入口。DiagnosticSource是一個抽象類主要定義了以下方法:

          //Provides?a?generic?way?of?logging?complex?payloads
          public?abstract?void?Write(string?name,?object?value);
          //Verifies?if?the?notification?event?is?enabled.
          public?abstract?bool?IsEnabled(string?name);

          DiagnosticListener直譯就是診斷監(jiān)聽器,繼承自DiagnosticSource,同時實現(xiàn)了IObservable>接口,因此其本質(zhì)是一個可觀察對象。小結(jié)以下:

          1. DiagnosticSource?作為診斷日志來源,提供接口,用于寫入診斷日志。
          2. 診斷日志的可觀察數(shù)據(jù)類型為KeyValuePair
          3. DiagnosticListener?繼承自DiagnosticSource,作為可觀察對象,可由其他觀察者訂閱,以獲取診斷日志。

          DiagnosticListener?其構(gòu)造函數(shù)接收一個name參數(shù)。

          private?static?DiagnosticSource?httpLogger?=?new?DiagnosticListener("System.Net.Http");

          可以通過下面這種方式記錄診斷日志:

          if?(httpLogger.IsEnabled("RequestStart"))
          ????httpLogger.Write("RequestStart",?new?{?Url="http://clr",?Request=aRequest?});

          然后需要實現(xiàn)IObserver>接口,以便消費診斷數(shù)據(jù)。定義DiagnosticObserver,進(jìn)行診斷日志消費:

          public?class?DiagnosticObserver?:?IObserver>
          {
          ????public?void?OnCompleted()
          ????{
          ????????//Noting?to?do
          ????}
          ????public?void?OnError(Exception?error)
          ????{
          ????????Console.WriteLine($"{error.Message}");
          ????}
          ????public?void?OnNext(KeyValuePair?pair)
          ????{?
          ????????//?這里消費診斷數(shù)據(jù)
          ????????Console.WriteLine($"{pair.Key}-{pair.Value}");
          ????}
          }

          ASP.NET Core 項目中默認(rèn)就依賴了System.Diagnostics.DiagnosticSourceNuget包,同時在構(gòu)建通用Web主機(jī)時,就注入了名為Microsoft.AspNetCoreDiagnosticListener

          //GenericWebHostBuilder.cs
          DiagnosticListener?instance?=?new?DiagnosticListener("Microsoft.AspNetCore");
          services.TryAddSingleton(instance);
          services.TryAddSingleton((DiagnosticSource)?instance);

          因此我們可以直接通過注入DiagnosticListener進(jìn)行診斷日志的訂閱:

          public?void?Configure(IApplicationBuilder?app,?IWebHostEnvironment?env,?DiagnosticListener?diagnosticListener)
          {
          ????diagnosticListener.Subscribe(new?DiagnosticObserver());//訂閱診斷日志
          }

          當(dāng)然也可以直接使用DiagnosticListener.AllListeners.Subscribe(IObserver observer);進(jìn)行訂閱,不過區(qū)別是,接收的參數(shù)類型為IObserver

          運行項目輸出:

          Microsoft.AspNetCore.Hosting.HttpRequestIn.Start-Microsoft.AspNetCore.Http.DefaultHttpContext
          Microsoft.AspNetCore.Hosting.BeginRequest-{?httpContext?=?Microsoft.AspNetCore.Http.DefaultHttpContext,?timestamp?=?7526300014352?}
          Microsoft.AspNetCore.Routing.EndpointMatched-Microsoft.AspNetCore.Http.DefaultHttpContext
          Microsoft.AspNetCore.Hosting.EndRequest-{?httpContext?=?Microsoft.AspNetCore.Http.DefaultHttpContext,?timestamp?=?7526300319214?}
          Microsoft.AspNetCore.Hosting.HttpRequestIn.Stop-Microsoft.AspNetCore.Http.DefaultHttpContext

          從中可以看出,ASP.NET Core Empty Web Project在一次正常的Http請求過程中分別在請求進(jìn)入、請求處理、路由匹配都埋了點,除此之外還有請求異常、Action處理都有埋點。因此,根據(jù)需要,可以實現(xiàn)比如請求攔截、耗時統(tǒng)計等系列操作。

          4.2. 耗時統(tǒng)計

          基于以上知識,下面嘗試完成一個簡單的耗時統(tǒng)計。從上面的內(nèi)容可知,ASP.NET Core在BeginRequest和EndRequest返回的診斷數(shù)據(jù)類型如下所示:

          Microsoft.AspNetCore.Hosting.BeginRequest-{?httpContext?=?Microsoft.AspNetCore.Http.DefaultHttpContext,?timestamp?=?7526300014352?}
          Microsoft.AspNetCore.Hosting.EndRequest-{?httpContext?=?Microsoft.AspNetCore.Http.DefaultHttpContext,?timestamp?=?7526300319214?}

          因此只要拿到兩個timestamp就可以直接計算耗時,修改DiagnosticObserverOnNext方法如下:

          private?ConcurrentDictionary?startTimes?=?new?ConcurrentDictionary();
          public?void?OnNext(KeyValuePair?pair)
          {
          ????//Console.WriteLine($"{pair.Key}-{pair.Value}");
          ????//獲取httpContext
          ????var?context?=?pair.Value.GetType().GetTypeInfo().GetDeclaredProperty("httpContext")
          ?????????.GetValue(pair.Value)?as?DefaultHttpContext;
          ????//獲取timestamp
          ????var?timestamp?=?pair.Value.GetType().GetTypeInfo().GetDeclaredProperty("timestamp")
          ?????????.GetValue(pair.Value)?as?long?;
          ????switch?(pair.Key)
          ????{
          ????????case?"Microsoft.AspNetCore.Hosting.BeginRequest":
          ????????????Console.WriteLine($"Request?{context.TraceIdentifier}?Begin:{context.Request.GetUri()}");
          ????????????startTimes.TryAdd(context.TraceIdentifier,?timestamp.Value);//記錄請求開始時間
          ????????????break;
          ????????case?"Microsoft.AspNetCore.Hosting.EndRequest":
          ????????????startTimes.TryGetValue(context.TraceIdentifier,?out?long?startTime);
          ????????????var?elapsedMs?=?(timestamp?-?startTime)?/?TimeSpan.TicksPerMillisecond;//計算耗時
          ????????????Console.WriteLine(
          ????????????????$"Request?{context.TraceIdentifier}?End:?Status?Code?is?{context.Response.StatusCode},Elapsed?{elapsedMs}ms");
          ????????????startTimes.TryRemove(context.TraceIdentifier,?out?_);
          ????????????break;
          ????}
          }

          輸出如下,大功告成:

          Request?0HM37UNERKGF0:00000001?Begin:https://localhost:44330
          Request?0HM37UNERKGF0:00000001?End:?Status?Code?is?200,Elapsed?38ms

          上面有通過反射去獲取診斷數(shù)據(jù)屬性的代碼(var timestamp = pair.Value.GetType().GetTypeInfo().GetDeclaredProperty("timestamp") ?.GetValue(pair.Value) as long?;),非常不優(yōu)雅。但我們可以安裝**Microsoft.Extensions.DiagnosticAdapter**包來簡化診斷數(shù)據(jù)的消費。安裝后,添加HttpContextDiagnosticObserver,通過添加DiagnosticName指定監(jiān)聽的診斷名稱,即可進(jìn)行診斷數(shù)據(jù)消費。

          public?sealed?class?HttpContextDiagnosticObserver
          {
          ????private?ConcurrentDictionary?startTimes?=?new?ConcurrentDictionary();
          ????
          ????[DiagnosticName("Microsoft.AspNetCore.Hosting.BeginRequest")]
          ????public?void?BeginRequest(HttpContext?httpContext,long?timestamp)
          ????{
          ????????Console.WriteLine($"Request?{httpContext.TraceIdentifier}?Begin:{httpContext.Request.GetUri()}");
          ????????startTimes.TryAdd(httpContext.TraceIdentifier,?timestamp);//記錄請求開始時間
          ????}
          ????
          ????[DiagnosticName("Microsoft.AspNetCore.Hosting.EndRequest")]
          ????public?void?EndRequest(HttpContext?httpContext,long?timestamp)
          ????{
          ????????startTimes.TryGetValue(httpContext.TraceIdentifier,?out?long?startTime);
          ????????var?elapsedMs?=?(timestamp?-?startTime)?/?TimeSpan.TicksPerMillisecond;//計算耗時
          ????????Console.WriteLine(
          ????????????$"Request?{httpContext.TraceIdentifier}?End:?Status?Code?is?{httpContext.Response.StatusCode},Elapsed?{elapsedMs}ms");
          ????????startTimes.TryRemove(httpContext.TraceIdentifier,?out?_);
          ????}
          }

          然后使用SubscribeWithAdapter進(jìn)行訂閱即可。

          public?void?Configure(IApplicationBuilder?app,?IWebHostEnvironment?env,?DiagnosticListener?diagnosticListener)
          {
          ????//?diagnosticListener.Subscribe(new?DiagnosticObserver());
          ????diagnosticListener.SubscribeWithAdapter(new?HttpContextDiagnosticObserver());
          }

          到這里可能也有小伙伴說,我用ActionFilter也可以實現(xiàn),沒錯,但這兩種方式是完全不同的,DiagnosticSource是完全異步的。

          4.3. 應(yīng)用場景思考

          根據(jù)DiagnosticSource的特性,可以運用于以下場景 :

          1. AOP:因為Diagnostics命名事件一般是成對出現(xiàn)的,因此可以做些攔截操作。比如在Abp集成Cap時,若想默認(rèn)啟用Uow,就可以消費DotNetCore.CAP.WriteSubscriberInvokeBefore命名事件,創(chuàng)建Uow,再在命名事件DotNetCore.CAP.WriteSubscriberInvokeAfter中提交事務(wù),并Dispose。

          2. APM:SkyAPM-dotnet的實現(xiàn)就是通過消費診斷日志,進(jìn)行鏈路跟蹤。?

          3. EventBus:充分利用其發(fā)布訂閱模式,可將其用于進(jìn)程內(nèi)事件的發(fā)布與消費。

          5. Activity(活動)

          5.1. Activity 概述

          那Activity又是何方神圣,用于解決什么問題呢?關(guān)于Activity官方只有一句簡要介紹:Represents an operation with context to be used for logging。(表示包含上下文的操作,用于日志記錄。)

          Activity用來存儲和訪問診斷上下文,并由日志系統(tǒng)進(jìn)行消費。當(dāng)應(yīng)用程序開始處理操作時,例如HTTP請求或隊列中的任務(wù),它會在處理請求時創(chuàng)建Activity以在系統(tǒng)中跟蹤該Activity。Activity中存儲的上下文可以是HTTP請求路徑,方法,用戶代理或關(guān)聯(lián)ID:所有重要信息都應(yīng)與每個跟蹤一起記錄。當(dāng)應(yīng)用程序調(diào)用外部依賴關(guān)系以完成操作時,它可能需要傳遞一些上下文(例如,關(guān)聯(lián)ID)以及依賴關(guān)系調(diào)用,以便能夠關(guān)聯(lián)來自多個服務(wù)的日志。

          先來看下Activity主要以下核心屬性:

          1. Tags(標(biāo)簽)IEnumerable> Tags { get; }?- 表示與活動一起記錄的信息。標(biāo)簽的好例子是實例/機(jī)器名稱,傳入請求HTTP方法,路徑,用戶/用戶代理等。標(biāo)簽不傳遞給子活動。典型的標(biāo)簽用法包括添加一些自定義標(biāo)簽,并通過它們進(jìn)行枚舉以填充日志事件的有效負(fù)載。可通過Activity AddTag(string key, string value)添加Tag,但不支持通過Key檢索標(biāo)簽。

          2. Baggage(行李)IEnumerable> Baggage { get; }?- 表示要與活動一起記錄并傳遞給其子項的信息。行李的例子包括相關(guān)ID,采樣和特征標(biāo)記。Baggage被序列化并與外部依賴項請求一起傳遞。典型的Baggage用法包括添加一些Baggage屬性,并通過它們進(jìn)行枚舉以填充日志事件的有效負(fù)載。可通過Activity AddBaggage(string key, string value)添加Baggage。并通過string GetBaggageItem(string key)獲取指定Key的Baggage。

          3. OperationName(操作名稱)string OperationName { get; }?- 活動名稱,必須在構(gòu)造函數(shù)中指定。

          4. StartTimeUtcDateTime StartTimeUtc { get; private set; }??- UTC格式的啟動時間,如果不指定,則在啟動時默認(rèn)指定為DateTime.UtcNow。可通過Activity SetStartTime(DateTime startTimeUtc)指定。

          5. DurationTimeSpan Duration { get; private set; }?- 如果活動已停止,則代表活動持續(xù)時間,否則為0。

          6. Idstring Id { get; private set; }?- 表示特定的活動標(biāo)識符。過濾特定ID可確保您僅獲得與操作中特定請求相關(guān)的日志記錄。該Id在活動開始時生成。Id傳遞給外部依賴項,并被視為新的外部活動的[ParentId]。

          7. ParentIdstring ParentId { get; private set; }?- 如果活動是根據(jù)請求反序列化的,則該活動可能具有進(jìn)程中的[Parent]或外部Parent。ParentId和Id代表日志中的父子關(guān)系,并允許您關(guān)聯(lián)傳出和傳入請求。

          8. RootIdstring RootId { get; private set; }?- 代表根Id

          9. Currentstatic Activity Current { get; }?- 返回在異步調(diào)用之間流動的當(dāng)前Activity。

          10. ParentActivity Parent { get; private set; }?- 如果活動是在同一過程中從另一個活動創(chuàng)建的,則可以使用Partent獲得該活動。但是,如果“活動”是根活動或父項來自流程外部,則此字段可能為null。

          11. Start()Activity Start()?- 啟動活動:設(shè)置活動的Activity.Current和Parent,生成唯一的ID并設(shè)置StartTimeUtc(如果尚未設(shè)置)。

          12. Stop()void Stop()?- 停止活動:設(shè)置活動的Activity.Current,并使用Activity SetEndTime(DateTime endTimeUtc)或DateTime.UtcNow中提供的時間戳計算Duration。

          另外DiagnosticSource中也定義了兩個相關(guān)方法:

          1. StartActivityActivity StartActivity(Activity activity, object args)?- 啟動給定的Activity,并將DiagnosticSource事件消息寫入OperationName.Start格式的命名事件中。
          2. StopActivityvoid StopActivity(Activity activity, object args)?- 停止給定的Activity,并將DiagnosticSource事件消息寫入{OperationName}.Stop格式的命名事件中。

          5.2. Activity在ASP.NET Core中的應(yīng)用

          要想弄懂Activity,我們還是得向源碼學(xué)習(xí),看一下HostingApplicationDiagnostics的實現(xiàn)。首先來看下BeginRequst中的StartActivity方法。

          private?Activity?StartActivity(HttpContext?httpContext,?out?bool?hasDiagnosticListener)
          {
          ??Activity?activity?=?new?Activity("Microsoft.AspNetCore.Hosting.HttpRequestIn");
          ??hasDiagnosticListener?=?false;
          ??IHeaderDictionary?headers?=?httpContext.Request.Headers;
          ??StringValues?stringValues1;
          ??if?(!headers.TryGetValue(HeaderNames.TraceParent,?out?stringValues1))
          ????headers.TryGetValue(HeaderNames.RequestId,?out?stringValues1);
          ??if?(!StringValues.IsNullOrEmpty(stringValues1))
          ??{
          ????activity.SetParentId((string)?stringValues1);
          ????StringValues?stringValues2;
          ????if?(headers.TryGetValue(HeaderNames.TraceState,?out?stringValues2))
          ??????activity.TraceStateString?=?(string)?stringValues2;
          ????string[]?commaSeparatedValues?=?headers.GetCommaSeparatedValues(HeaderNames.CorrelationContext);
          ????if?(commaSeparatedValues.Length?!=?0)
          ????{
          ??????foreach?(string?str?in?commaSeparatedValues)
          ??????{
          ????????NameValueHeaderValue?parsedValue;
          ????????if?(NameValueHeaderValue.TryParse((StringSegment)?str,?out?parsedValue))
          ??????????activity.AddBaggage(parsedValue.Name.ToString(),?parsedValue.Value.ToString());
          ??????}
          ????}
          ??}
          ??this._diagnosticListener.OnActivityImport(activity,?(object)?httpContext);
          ??if?(this._diagnosticListener.IsEnabled("Microsoft.AspNetCore.Hosting.HttpRequestIn.Start"))
          ??{
          ????hasDiagnosticListener?=?true;
          ????this.StartActivity(activity,?httpContext);
          ??}
          ??else
          ????activity.Start();
          ??return?activity;
          }

          從中可以看出,在ASP.NET Core 開始處理請求之前:

          1. 首先,創(chuàng)建了名為Microsoft.AspNetCore.Hosting.HttpRequestIn的Activity,該Activity首先嘗試從HTTP請求頭中獲取TraceParent/euqstId作為當(dāng)前Activity的ParentId,這個很顯然,是用來鏈路跟蹤的。
          2. 其次,嘗試從CorrelationContext中獲取關(guān)聯(lián)上下文信息,然后將其添加到創(chuàng)建的Activity的Baggage中,進(jìn)行關(guān)聯(lián)上下文的繼續(xù)傳遞。
          3. 然后,啟動Activity,然后向Name為Microsoft.AspNetCore.Hosting.HttpRequestIn.Start中寫入診斷日志。

          這里大家可能有個疑問,這個關(guān)聯(lián)上下文信息CorrelationContext又是何時添加到Http請求頭中的呢?在System.Net.Http中的DiagnosticsHandler中添加的。因此我們應(yīng)該明白了,整個關(guān)聯(lián)上下文的傳遞機(jī)制。

          緊接著再來看一看RequestEnd中的StopActivity方法。

          private?void?StopActivity(Activity?activity,?HttpContext?httpContext)
          {
          ??if?(activity.Duration?==?TimeSpan.Zero)
          ????activity.SetEndTime(DateTime.UtcNow);
          ??this._diagnosticListener.Write("Microsoft.AspNetCore.Hosting.HttpRequestIn.Stop",?(object)?httpContext);
          ??activity.Stop();
          }

          從中可以看出主要是先SetEndTime,再寫入Microsoft.AspNetCore.Hosting.HttpRequestIn.Stop命名事件;最后調(diào)用Stop方法停止當(dāng)前Activity。

          簡單總結(jié)一下,借助Activity中附加的Baggage信息可以實現(xiàn)請求鏈路上上下文數(shù)據(jù)的共享。

          5.3. 應(yīng)用場景思考

          從上面的命名事件中可以看出,其封送的數(shù)據(jù)類型是特定的,因此可以借助Activity的Tags或Baggage添加自定義的數(shù)據(jù)進(jìn)行共享。

          按照上面我們的耗時統(tǒng)計,只能統(tǒng)計到整個http請求的耗時,但對于我們定位問題來說還是有困難,比如,某個api即有調(diào)用redis,又操作了消息隊列,同時又訪問了數(shù)據(jù)庫,那到底是那一段超時了呢?顯然不好直接定位,借助activity,我們就可以很好的實現(xiàn)細(xì)粒度的鏈路跟蹤。通過activity攜帶的信息,可以將一系列的操作關(guān)聯(lián)起來,記錄日志,再借助AMP進(jìn)行可視化快速定位跟蹤。

          6. 參考資料

          1. 在 .NET Core 中使用 Diagnostics (Diagnostic Source) 記錄跟蹤信息
          2. Logging using DiagnosticSource in ASP.NET Core
          3. .Net Core中的診斷日志DiagnosticSource講解
          4. Observer Design Pattern
          5. DiagnosticSource User Guide
          6. Activity User Guide
          7. DiagnosticSourcery 101 - Mark Rendle
          8. Improvements in .NET Core 3.0 for troubleshooting and monitoring distributed apps?



          往期精彩回顧




          【推薦】.NET Core開發(fā)實戰(zhàn)視頻課程?★★★

          .NET Core實戰(zhàn)項目之CMS 第一章 入門篇-開篇及總體規(guī)劃

          【.NET Core微服務(wù)實戰(zhàn)-統(tǒng)一身份認(rèn)證】開篇及目錄索引

          Redis基本使用及百億數(shù)據(jù)量中的使用技巧分享(附視頻地址及觀看指南)

          .NET Core中的一個接口多種實現(xiàn)的依賴注入與動態(tài)選擇看這篇就夠了

          10個小技巧助您寫出高性能的ASP.NET Core代碼

          用abp vNext快速開發(fā)Quartz.NET定時任務(wù)管理界面

          在ASP.NET Core中創(chuàng)建基于Quartz.NET托管服務(wù)輕松實現(xiàn)作業(yè)調(diào)度

          現(xiàn)身說法:實際業(yè)務(wù)出發(fā)分析百億數(shù)據(jù)量下的多表查詢優(yōu)化

          關(guān)于C#異步編程你應(yīng)該了解的幾點建議

          C#異步編程看這篇就夠了


          瀏覽 81
          點贊
          評論
          收藏
          分享

          手機(jī)掃一掃分享

          分享
          舉報
          評論
          圖片
          表情
          推薦
          點贊
          評論
          收藏
          分享

          手機(jī)掃一掃分享

          分享
          舉報
          <kbd id="afajh"><form id="afajh"></form></kbd>
          <strong id="afajh"><dl id="afajh"></dl></strong>
            <del id="afajh"><form id="afajh"></form></del>
                1. <th id="afajh"><progress id="afajh"></progress></th>
                  <b id="afajh"><abbr id="afajh"></abbr></b>
                  <th id="afajh"><progress id="afajh"></progress></th>
                  国产人妻绿帽3p国语对白 | 干B网| 狼友自拍 | 国产成人自拍偷拍 | 激情国产综合 |