《ASP.NET Core 6框架揭秘》實例演示[26]:跟蹤應用接收的每一次請求

很多人可能對ASP.NET Core框架自身記錄的診斷日志并不關心,其實這些日志對糾錯排錯和性能監控提供了很有用的信息。如果需要創建一個APM(Application Performance Management)系統來監控ASP.NET Core應用處理請求的性能及出現的異常,我們完全可以將HostingApplication對象記錄的日志作為收集的原始數據。實際上,目前很多APM(如OpenTelemetry.NET 、Elastic APM和SkyWalking APM等)針對都是利用這種方式收集分布式跟蹤日志的。[本文節選《ASP.NET Core 6框架揭秘》第17章]

[S1701]ASP.NET針對請求的診斷日志(源代碼)
[S1702]收集DiagnosticSource輸出的日志(源代碼)
[S1703]收集EventSource輸出的日志(源代碼)

[S1701]ASP.NET針對請求的診斷日志

為了確定什么樣的信息會被作為診斷日志記錄下來,我們通過一個簡單的實例演示將HostingApplication對象寫入的診斷日志輸出到控制臺上。HostingApplication對象會將相同的診斷信息以三種不同的方式進行記錄,其中包含第8章“診斷日志(中篇)”介紹的日志系統。如下的演示程序利用WebApplicationBuilder的Logging屬性得到返回的ILoggingBuilder對象,并調用它的AddSimpleConsole擴展方法為默認注冊的ConsoleLoggerProvider開啟了針對日志范圍的支持。我們最后調用IApplicationBuilder接口的Run擴展方法注冊了一個中間件,該中間件在處理請求時會利用依賴注入容器提取出用于發送日志事件的ILogger<Program>對象,并利用它寫入一條Information等級的日志。如果請求路徑為“/error”,那么該中間件會拋出一個InvalidOperationException類型的異常。

var?builder?=?WebApplication.CreateBuilder(args);
builder.Logging.AddSimpleConsole(options?=>?options.IncludeScopes?=?true);
var?app?=?builder.Build();
app.Run(HandleAsync);
app.Run();static?Task?HandleAsync(HttpContext?httpContext)
{var?logger?=?httpContext.RequestServices.GetRequiredService<ILogger<Program>>();logger.LogInformation($"Log?for?event?Foobar");if?(httpContext.Request.Path?==?new?PathString("/error")){throw?new?InvalidOperationException("Manually?throw?exception.");}return?Task.CompletedTask;
}

在啟動程序之后,我們利用瀏覽器采用不同的路徑(“/foobar”和“/error”)向應用發送了兩次請求,控制臺上會輸出如圖1所示的七條日志。由于開啟了日志范圍的支持,所以輸出的日志都會攜帶日志范圍的信息,日志范圍提供了很多有用的分布式跟蹤信息,比如Trace ID、Span ID、Parent Span ID以及請求的ID和路徑等。請求ID(Request ID),它由當前的連接ID和一個序列號組成。從圖1可以看出,兩次請求的ID分別是“0HMG97FD188VR:00000002”和“0HMG97FD188VR:00000003”。由于采用的是長連接,并且兩次請求共享同一個連接,所以它們具有相同的連接ID(“0HMG97FD188VR”)。同一連接的多次請求將一個自增的序列號(“00000002”和“00000003”)作為唯一標識。

86117a6195dbea6cd9d2506458ea62b6.png
圖1 捕捉HostingApplication記錄的診斷日志

對于兩次請求輸出的七條日志,類別為“Program”的日志是應用程序自行寫入的,HostingApplication寫入日志的類別為“Microsoft.AspNetCore.Hosting.Diagnostics”。對于第一次請求的三條日志消息,第一條是在開始處理請求時寫入的,我們利用這條日志獲知請求的HTTP版本(HTTP/1.1)、HTTP方法(GET)和請求URL。對于包含主體內容的請求,請求主體內容的媒體類型(Content-Type)和大小(Content-Length)也會一并記錄下來。當請求處理結束后第三條日志被輸出,日志承載的信息包括請求處理耗時(9.9482毫秒)和響應狀態碼(200)。如果響應具有主體內容,對應的媒體類型同樣會被記錄下來。

對于第二次請求,由于我們人為拋出了異常,所以異常的信息被寫入日志。如果足夠仔細,就會發現這條等級為Error的日志并不是由HostingApplication對象寫入的,而是作為服務器的KestrelServer寫入的,因為該日志采用的類別為“Microsoft.AspNetCore.Server.Kestrel”。

[S1702]收集DiagnosticSource輸出的日志

HostingApplication采用的三種日志形式還包括基于DiagnosticSource對象的診斷日志,所以我們可以通過注冊診斷監聽器來收集診斷信息。如果通過這種方式獲取診斷信息,就需要預先知道診斷日志事件的名稱和內容荷載的數據結構。通過查看HostingApplication類型的源代碼,我們會發現它針對“開始請求”、“結束請求”和“未處理異常”這三類診斷日志事件會采用如下的命名方式。

  • 開始請求:Microsoft.AspNetCore.Hosting.BeginRequest。

  • 結束請求:Microsoft.AspNetCore.Hosting.EndRequest。

  • 未處理異常:Microsoft.AspNetCore.Hosting.UnhandledException。

至于針對診斷日志消息的內容荷載(Payload)的結構,上述三類診斷事件具有兩個相同的成員,分別是表示當前請求上下文的HttpContext和通過一個Int64整數表示的當前時間戳,對應的數據成員的名稱分別為“httpContext”和“timestamp”。對于未處理異常診斷事件,它承載的內容荷載還包括拋出異常,對應的成員名稱為“exception”。我們的演示程序定義了如下這個的DiagnosticCollector類型作為診斷監聽器,它定義針對上述三個診斷事件的監聽方法。

public?class?DiagnosticCollector
{[DiagnosticName("Microsoft.AspNetCore.Hosting.BeginRequest")]public?void?OnRequestStart(HttpContext?httpContext,?long?timestamp){var?request?=?httpContext.Request;Console.WriteLine($"\nRequest?starting?{request.Protocol}?{request.Method}?{request.Scheme}://{request.Host}{request.PathBase}{request.Path}");httpContext.Items["StartTimestamp"]?=?timestamp;}[DiagnosticName("Microsoft.AspNetCore.Hosting.EndRequest")]public?void?OnRequestEnd(HttpContext?httpContext,?long?timestamp){var?startTimestamp?=?long.Parse(httpContext.Items["StartTimestamp"]!.ToString());var?timestampToTicks?=?TimeSpan.TicksPerSecond?/?(double)Stopwatch.Frequency;var?elapsed?=?new?TimeSpan((long)(timestampToTicks?*?(timestamp?-?startTimestamp)));Console.WriteLine($"Request?finished?in?{elapsed.TotalMilliseconds}ms?{httpContext.Response.StatusCode}");}[DiagnosticName("Microsoft.AspNetCore.Hosting.UnhandledException")]public?void?OnException(HttpContext?httpContext,?long?timestamp,?Exception?exception){OnRequestEnd(httpContext,?timestamp);Console.WriteLine($"{exception.Message}\nType:{exception.GetType()}\nStacktrace:?{exception.StackTrace}");}
}

針對“開始請求”事件的OnRequestStart方法輸出了當前請求的HTTP版本、HTTP方法和URL。為了能夠計算整個請求處理的耗時,它將當前時間戳保存在HttpContext上下文的Items集合中。針對“結束請求”事件的OnRequestEnd方法將這個時間戳從HttpContext上下文中提取出來,結合當前時間戳計算出請求處理耗時,該耗時和響應的狀態碼最終會被寫入控制臺。針對“未處理異常”診斷事件的OnException方法則在調用OnRequestEnd方法之后將異常的消息、類型和跟蹤堆棧輸出到控制臺上。如下所示的演示程序中利用WebApplication的Services提供的依賴注入容器提取出注冊的DiagnosticListener對象,并調用它的SubscribeWithAdapter擴展方法將DiagnosticCollector對象注冊為訂閱者。我們調用Run擴展方法注冊了一個中間件,該中間件會在請求路徑為“/error”的情況下拋出異常。

using?App;
using?System.Diagnostics;var?builder?=?WebApplication.CreateBuilder(args);
builder.Logging.ClearProviders();
var?app?=?builder.Build();
var?listener?=?app.Services.GetRequiredService<DiagnosticListener>();
listener.SubscribeWithAdapter(new?DiagnosticCollector());
app.Run(HandleAsync);
app.Run();static?Task?HandleAsync(HttpContext?httpContext)
{var?listener?=?httpContext.RequestServices.GetRequiredService<DiagnosticListener>();if?(httpContext.Request.Path?==?new?PathString("/error")){throw?new?InvalidOperationException("Manually?throw?exception.");}return?Task.CompletedTask;
}

待演示實例正常啟動后,可以采用不同的路徑(“/foobar”和“/error”)對應用程序發送兩個請求,服務端控制臺會以圖2所示的形式輸出DiagnosticCollector對象收集的診斷信息。

01a984c86d0cf32284f0d0af7fe23773.png
圖2 利用注冊的診斷監聽器獲取診斷日志

[S1703]收集EventSource輸出的日志

HostingApplication在處理每個請求的過程中還會利用名稱為“Microsoft.AspNetCore.Hosting”EventSource對象發出相應的日志事件。這個EventSource對象來回在在啟動和關閉應用程序時發出相應的事件。涉及的五個日志事件對應的名稱如下:

  • 啟動應用程序:HostStart。

  • 開始處理請求:RequestStart。

  • 請求處理結束:RequestStop。

  • 未處理異常:UnhandledException。

  • 關閉應用程序:HostStop。

如下所示的演示程序利用創建的EventListener對象來監聽上述五個日志事件。如代碼片段所示,我們定義了派生于抽象類EventListener的DiagnosticCollector類型,并在啟動應用前創建了這個對象,我們通過注冊它的EventSourceCreated事件開啟了針對上述EventSource的監聽。注冊的EventWritten事件會將監聽到的事件名稱的負載內容輸出到控制臺上。

using?System.Diagnostics.Tracing;var?listener?=?new?DiagnosticCollector();
listener.EventSourceCreated?+=?(sender,?args)?=>
{if?(args.EventSource?.Name?==?"Microsoft.AspNetCore.Hosting"){listener.EnableEvents(args.EventSource,?EventLevel.LogAlways);}
};
listener.EventWritten?+=?(sender,?args)?=>
{Console.WriteLine(args.EventName);for?(int?index?=?0;?index?<?args.PayloadNames?.Count;?index++){Console.WriteLine($"\t{args.PayloadNames[index]}?=?{args.Payload?[index]}");}
};var?builder?=?WebApplication.CreateBuilder(args);
builder.Logging.ClearProviders();
var?app?=?builder.Build();
app.Run(HandleAsync);
app.Run();static?Task?HandleAsync(HttpContext?httpContext)
{if?(httpContext.Request.Path?==?new?PathString("/error")){throw?new?InvalidOperationException("Manually?throw?exception.");}return?Task.CompletedTask;
}public?class?DiagnosticCollector?:?EventListener?{?}

以命令行的形式啟動這個演示程序后,從圖3所示的輸出結果可以看到名為HostStart的事件被發出。然后我們采用目標地址“http://localhost:5000/foobar”和“http:// http://localhost:5000/error”對應用程序發送兩個請求,從輸出結果可以看出,應用程序針對前者的處理過程會發出RequestStart事件和RequestStop事件,針對后者的處理則會因為拋出的異常發出額外的事件UnhandledException。輸入“Ctrl+C”關閉應用后,名稱為HostStop的事件被發出。對于通過EventSource發出的五個事件,只有RequestStart事件會將請求的HTTP方法(GET)和路徑(“/foobar”和“/error”)作為負載內容,其他事件都不會攜帶任何負載內容。

f74988b8ddebc9b07af305e2d0dd5d6d.png
圖3 利用注冊EventListener監聽器獲取診斷日志

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/news/282832.shtml
繁體地址,請注明出處:http://hk.pswp.cn/news/282832.shtml
英文地址,請注明出處:http://en.pswp.cn/news/282832.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

C語言循環為1404的循環,考試,求大神幫忙,C語言,小弟感激不盡

若有定義語句&#xff1a;int a10; double b3.14;&#xff0c;則表達式Aab值的類型是___________。  (1)A).char B)int C) double D)float(2)若有定義語句&#xff1a;int x12,y8,z;&#xff0c;在其后執行語句z0.9x/y;&#xff0c;則z的值為___________。A)1.9 B)1 C)2 D)2.…

js題集19

1.實現斐波那契數列。達到題目中的效果。不知道斐波那契數列是啥的請自行百度。 function fibonacci(){ } var ffibonacci(); for(var i0;i<10;i){ console.log(f()); } //output:按順序輸出斐波那契數列的數字。 eg&#xff1a; 1 2 3 5 8 13 21 34 55 89轉載于:https://ww…

阿里云Maven鏡像配置

2019獨角獸企業重金招聘Python工程師標準>>> <mirror><id>alimaven</id> <name>aliyun maven</name> <url>http://maven.aliyun.com/nexus/content/groups/public/</url> <mirrorOf>central</mirrorOf> …

c語言中有12個球,數學老師做不出來的一道邏輯推理題

同志們 那個球不一定輕啊正確的是平分三份 取兩分稱if(平)。。。。。。在未稱過的4球中取兩個放左邊 和標準的球稱(稱過的球一定標準)。。。。。。if(平)。。。。。。。。。。。。在兩次都未稱過的球中取一個 和標準的稱。。。。。。。。。。。。if(平)。。。。。。。。。。。。…

WPF 實現彈幕效果

WPF 實現彈幕效果控件名&#xff1a;BarrageExample作者&#xff1a;WPFDevelopersOrg原文鏈接&#xff1a; https://github.com/WPFDevelopersOrg/WPFDevelopers框架使用大于等于.NET40&#xff1b;Visual Studio 2022;項目使用 MIT 開源許可協議&#xff1b;此篇代碼目的只…

js題集23

1.實現函數--defaultArguments 功能如下&#xff1a; function add(a,b) { return ab;}; var add_ defaultArguments(add,{b:9}); add_(10); // returns 19 add_(10,7); // returns 17 add_(); // returns NaN add_ defaultArguments(add_,{b:3, a:2}); add_(10); // returns…

iteritems()與items()

iteritems&#xff1a;以迭代器對象返回字典鍵值對 item:以列表形式返回字典鍵值對 >>> dic {a:3,c:1,b:2} >>> print dic.iteritems() <dictionary-itemiterator object at 0x7fa381599628> >>> print dic.items() [(a, 3), (c, 1), (b, 2)…

WPF效果第一百九十八篇之模塊對比

前面效果中分享了彩色馬蹄圖的效果和范圍內拖拽;這不大假期的時間反正沒啥事就在家擼代碼;今天又是LisBox實現的效果,看最終效果:1、剛開始一朋友說用DataGrid來實現.首先把行對象轉換成列對象,至于控制列的話,就后臺重新賦值對象來控制前臺.我是覺得太費勁直接放棄了;還是首選…

android 與后臺通信,Android后臺線程和UI線程通訊實例

本節向你展示如何在任務中發送數據給UI線程里的對象&#xff0c;這個特性允許你在后臺線程工作&#xff0c;完了在UI線程展示結果。在UI線程定義一個HandlerHandler是Android系統線程管理框架里的一部分。一個Handler對象接收消息&#xff0c;并且運行代碼來處理消息。正常情況…

saltstack的狀態文件

saltstack狀態文件設定&#xff1a;編輯/etc/salt/master&#xff0c;修改其中關于“設置文件的目錄”的設置&#xff1a;說明&#xff1a;注意語法格式&#xff0c;頂格/冒號/兩個空格state_top: top.sls # The state system uses a "top" file to tell the minions…

POJ 2798:二進制轉換十六進制

很郁悶&#xff0c;這道題一直WA&#xff0c;然而本地我測了好幾組數據都是通過的&#xff0c;上網找了網友陳宇龍加油加油加油的AC的代碼&#xff0c;http://blog.csdn.net/Since_natural_ran/article/details/51742149&#xff0c;發現沒有什么不同。。。很無語。。 #include…

【Shashlik.EventBus】.NET 事件總線,分布式事務最終一致性簡介

分布式事務、CAP定理、事件總線&#xff0c;在當前微服務、分布式、集群大行其道的架構前提下&#xff0c;是不可逃避的幾個關鍵字&#xff0c;在此不會過多闡述相關的理論知識。Shashlik.EventBus就是一個基于.NET6的開源事件總線解決方案&#xff0c;同時也是分布式事務最終一…

5個超實用的Visual Studio插件

工欲善其事&#xff0c;必先利其器,整理的一些我必裝的5款Visual Studio插件&#xff0c;希望你們能get到。01 CodeMaidCodeMaid快速整理代碼文件&#xff0c;規范你的代碼&#xff0c;提高代碼閱讀體驗。代碼自動對齊&#xff0c;格式化代碼&#xff08;ps&#xff1a;不用再按…

BZOJ1509: [NOI2003]逃學的小孩(樹的直徑)

Time Limit: 5 Sec Memory Limit: 64 MBSubmit: 1126 Solved: 567[Submit][Status][Discuss]Description Input 第一行是兩個整數N&#xff08;3 ? N ? 200000&#xff09;和M&#xff0c;分別表示居住點總數和街道總數。以下M行&#xff0c;每行給出一條街道的信息。第i1行…

Blazor University (52)依賴注入 —— 擁有多個依賴項:正確的方式

原文鏈接&#xff1a;https://blazor-university.com/dependency-injection/component-scoped-dependencies/owning-multiple-dependencies-the-right-way/擁有多個依賴項&#xff1a;正確的方式在上一節[1]中&#xff0c;我們看到了將多個擁有的依賴項注入組件的錯誤方法。本節…

Gradle 1.12用戶指南翻譯——第五十四章. 構建原生二進制文件

其他章節的翻譯請參見&#xff1a;http://blog.csdn.net/column/details/gradle-translation.html翻譯項目請關注Github上的地址&#xff1a;https://github.com/msdx/gradledoc本文翻譯所在分支&#xff1a;https://github.com/msdx/gradledoc/tree/1.12。直接瀏覽雙語版的文檔…

android 調用c wcf服務,如何使用命名管道從c調用WCF方法?

更新&#xff1a;通過協議here,我無法弄清楚未知的信封記錄.我在網上找不到任何例子.原版的&#xff1a;我有以下WCF服務static void Main(string[] args){var inst new PlusFiver();using (ServiceHost host new ServiceHost(inst,new Uri[] { new Uri("net.pipe://loc…

VK Cup 2015 - Qualification Round 1 A. Reposts(樹)

傳送門 Description One day Polycarp published a funny picture in a social network making a poll about the color of his handle. Many of his friends started reposting Polycarps joke to their news feed. Some of them reposted the reposts and so on. These event…

Lombok@Builder和@NoArgsConstructor沖突

問題 今天在使用lombok簡化model類時。使用Builder建造者模式。報以下異常 解決辦法。 去掉NoArgsConstructor添加AllArgsConstructor源碼分析 下圖是編譯后的源碼 只使用Builder會自動創建全參構造器。而添加上NoArgsConstructor后就不會自動產生全參構造器

現在商業有種競爭叫“跨界打擊”

隨著互聯網的發展&#xff0c;“跨界打擊”的事情可謂是無處不在。行業跨界打擊會搶占某個行業的市場份額&#xff0c;甚至可能淘汰一個行業。跨界打擊者可能是某個行業的新進入者&#xff0c;也可能是現有競爭者&#xff0c;更可能是徹底的替代者或顛覆者。跨界打擊&#xff0…