一、服務(wù)器(IServer)
由于服務(wù)器是整個(gè)請(qǐng)求處理管道的“龍頭”外永,所以啟動(dòng)和關(guān)閉應(yīng)用的最終目的是啟動(dòng)和關(guān)閉服務(wù)器。ASP.NET Core框架中的服務(wù)器通過IServer接口來表示拧咳,該接口具有如下所示的3個(gè)成員伯顶,其中由服務(wù)器提供的特性就保存在其Features屬性表示的IFeatureCollection集合中。IServer接口的StartAsync<TContext>方法與StopAsync方法分別用來啟動(dòng)和關(guān)閉服務(wù)器。
publicinterface IServer : IDisposable
{
? ? IFeatureCollection Features { get; }
? ? Task StartAsync(IHttpApplication application, CancellationToken cancellationToken);
? ? Task StopAsync(CancellationToken cancellationToken);
}
服務(wù)器在開始監(jiān)聽請(qǐng)求之前總是綁定一個(gè)或者多個(gè)監(jiān)聽地址祭衩,這個(gè)地址是應(yīng)用程序從外部指定的灶体。具體來說,應(yīng)用程序指定的監(jiān)聽地址會(huì)封裝成一個(gè)特性掐暮,并且在服務(wù)器啟動(dòng)之前被添加到它的特性集合中蝎抽。這個(gè)承載了監(jiān)聽地址列表的特性通過如下所示的IServerAddressesFeature接口來表示,該接口除了有一個(gè)表示地址列表的Addresses屬性路克,還有一個(gè)布爾類型的PreferHostingUrls屬性樟结,該屬性表示如果監(jiān)聽地址同時(shí)設(shè)置到承載系統(tǒng)配置和服務(wù)器上,是否優(yōu)先考慮使用前者精算。
publicinterface IServerAddressesFeature
{
? ? ICollection Addresses {get; }
? ? boolPreferHostingUrls {get;set; }
}
正如前面所說瓢宦,服務(wù)器將用來處理由它接收請(qǐng)求的處理器會(huì)被視為一個(gè)通過IHttpApplication<TContext>接口表示的應(yīng)用,所以可以將ASP.NET Core的請(qǐng)求處理管道視為IServer對(duì)象和IHttpApplication<TContext>對(duì)象的組合灰羽。當(dāng)調(diào)用IServer對(duì)象的StartAsync<TContext>方法啟動(dòng)服務(wù)器時(shí)驮履,我們需要提供這個(gè)用來處理請(qǐng)求的IHttpApplication<TContext>對(duì)象。IHttpApplication<TContext>采用基于上下文的請(qǐng)求處理方式廉嚼,泛型參數(shù)TContext代表的就是上下文的類型玫镐。在IHttpApplication<TContext>處理請(qǐng)求之前,它需要先創(chuàng)建一個(gè)上下文對(duì)象前鹅,該上下文會(huì)在請(qǐng)求處理結(jié)束之后被釋放摘悴。上下文的創(chuàng)建、釋放和自身對(duì)請(qǐng)求的處理實(shí)現(xiàn)在該接口3個(gè)對(duì)應(yīng)的方法(CreateContext舰绘、DisposeContext和ProcessRequestAsync)中蹂喻。
publicinterfaceIHttpApplication{
? ? TContext CreateContext(IFeatureCollection contextFeatures);
? ? void DisposeContext(TContext context, Exception exception);
? ? Task ProcessRequestAsync(TContext context);
}
二、承載應(yīng)用( HostingApplication)
ASP.NET Core框架利用如下所示的HostingApplication類型作為IHttpApplication<TContext>接口的默認(rèn)實(shí)現(xiàn)捂寿,它使用一個(gè)內(nèi)嵌的Context類型來表示處理請(qǐng)求的上下文口四。一個(gè)Context對(duì)象是對(duì)一個(gè)HttpContext對(duì)象的封裝,同時(shí)承載了一些與診斷相關(guān)的信息秦陋。
publicclassHostingApplication : IHttpApplication{
? ? ...
? ? publicstruct Context
? ? {
? ? ? ? publicHttpContext HttpContext {get;set; }
? ? ? ? publicIDisposable Scope {get;set; }
? ? ? ? publiclongStartTimestamp {get;set; }
? ? ? ? publicboolEventLogEnabled {get;set; }
? ? ? ? publicActivity Activity {get;set; }
? ? }
}
HostingApplication對(duì)象會(huì)在開始和完成請(qǐng)求處理蔓彩,以及在請(qǐng)求過程中出現(xiàn)異常時(shí)發(fā)出一些診斷日志事件。具體來說驳概,HostingApplication對(duì)象會(huì)采用3種不同的診斷日志形式赤嚼,包括基于DiagnosticSource和EventSource的診斷日志以及基于 .NET Core日志系統(tǒng)的日志。Context除HttpContext外的其他屬性都與診斷日志有關(guān)顺又。具體來說更卒,Context的Scope是為ILogger創(chuàng)建的針對(duì)當(dāng)前請(qǐng)求的日志范圍(第9章有對(duì)日志范圍的詳細(xì)介紹),此日志范圍會(huì)攜帶唯一標(biāo)識(shí)每個(gè)請(qǐng)求的ID稚照,如果注冊(cè)ILoggerProvider提供的ILogger支持日志范圍蹂空,它可以將這個(gè)請(qǐng)求ID記錄下來俯萌,那么我們就可以利用這個(gè)ID將針對(duì)同一請(qǐng)求的多條日志消息組織起來做針對(duì)性分析。
HostingApplication對(duì)象會(huì)在請(qǐng)求結(jié)束之后記錄當(dāng)前請(qǐng)求處理的耗時(shí)上枕,所以它在開始處理請(qǐng)求時(shí)就會(huì)記錄當(dāng)前的時(shí)間戳咐熙,Context的StartTimestamp屬性表示開始處理請(qǐng)求的時(shí)間戳。它的EventLogEnabled屬性表示針對(duì)EventSource的事件日志是否開啟辨萍,而Activity屬性則與針對(duì)DiagnosticSource的診斷日志有關(guān)棋恼,Activity代表基于當(dāng)前請(qǐng)求處理的活動(dòng)。
雖然ASP.NET Core應(yīng)用的請(qǐng)求處理完全由HostingApplication對(duì)象負(fù)責(zé)分瘦,但是該類型的實(shí)現(xiàn)邏輯其實(shí)是很簡單的蘸泻,因?yàn)樗鼘⒕唧w的請(qǐng)求處理分發(fā)給一個(gè)RequestDelegate對(duì)象,該對(duì)象表示的正是所有注冊(cè)中間件組成的委托鏈嘲玫。在創(chuàng)建HostingApplication對(duì)象時(shí)除了需要提供RequestDelegate對(duì)象悦施,還需要提供用于創(chuàng)建HttpContext上下文的IHttpContextFactory對(duì)象,以及與診斷日志有關(guān)的ILogger對(duì)象和DiagnosticListener對(duì)象去团,它們被用來創(chuàng)建上面提到過的HostingApplicationDiagnostics對(duì)象抡诞。
publicclassHostingApplication : IHttpApplication{
? ? privatereadonly RequestDelegate _application;
? ? private HostingApplicationDiagnostics _diagnostics;
? ? privatereadonly IHttpContextFactory _httpContextFactory;
? ? public HostingApplication(RequestDelegate application, ILogger logger,DiagnosticListener diagnosticSource, IHttpContextFactory httpContextFactory)
? ? {
? ? ? ? _application = application;
? ? ? ? _diagnostics =new HostingApplicationDiagnostics(logger, diagnosticSource);
? ? ? ? _httpContextFactory = httpContextFactory;
? ? }
? ? public Context CreateContext(IFeatureCollection contextFeatures)
? ? {
? ? ? ? varcontext =new Context();
? ? ? ? varhttpContext = _httpContextFactory.Create(contextFeatures);
? ? ? ? _diagnostics.BeginRequest(httpContext, ref context);
? ? ? ? context.HttpContext = httpContext;
? ? ? ? return context;
? ? }
? ? publicTask ProcessRequestAsync(Context context) => _application(context.HttpContext);
? ? publicvoid DisposeContext(Context context, Exception exception)
? ? {
? ? ? ? varhttpContext = context.HttpContext;
? ? ? ? _diagnostics.RequestEnd(httpContext, exception, context);
? ? ? ? _httpContextFactory.Dispose(httpContext);
? ? ? ? _diagnostics.ContextDisposed(context);
? ? }
}
如上面的代碼片段所示,當(dāng)CreateContext方法被調(diào)用時(shí)土陪,HostingApplication對(duì)象會(huì)利用IHttpContextFactory工廠創(chuàng)建出當(dāng)前HttpContext上下文昼汗,并進(jìn)一步將它封裝成一個(gè)Context對(duì)象。在返回這個(gè)Context對(duì)象之前鬼雀,它會(huì)調(diào)用HostingApplicationDiagnostics對(duì)象的BeginRequest方法記錄相應(yīng)的診斷日志顷窒。用來真正處理當(dāng)前請(qǐng)求的ProcessRequestAsync方法比較簡單,只需要調(diào)用代表中間件委托鏈的RequestDelegate對(duì)象即可源哩。
對(duì)于用來釋放上下文的DisposeContext方法來說鞋吉,它會(huì)利用IHttpContextFactory對(duì)象的Dispose方法來釋放創(chuàng)建的HttpContext對(duì)象。換句話說励烦,HttpContext上下文的生命周期是由HostingApplication對(duì)象控制的谓着。完成針對(duì)HttpContext上下文的釋放之后,HostingApplication對(duì)象會(huì)利用HostingApplicationDiagnostics對(duì)象記錄相應(yīng)的診斷日志坛掠。Context的Scope屬性表示的日志范圍就是在調(diào)用HostingApplicationDiagnostics對(duì)象的ContextDisposed方法時(shí)釋放的赊锚。如果將HostingApplication對(duì)象引入ASP.NET Core的請(qǐng)求處理管道,那么完整的管道就體現(xiàn)為下圖所示的結(jié)構(gòu)屉栓。
三舷蒲、應(yīng)用生命周期和請(qǐng)求日志
很多人可能對(duì)ASP.NET Core框架自身記錄的診斷日志并不關(guān)心,其實(shí)很多時(shí)候這些日志對(duì)糾錯(cuò)排錯(cuò)和性能監(jiān)控提供了很有用的信息友多。例如牲平,假設(shè)需要?jiǎng)?chuàng)建一個(gè)APM(Application Performance Management)來監(jiān)控ASP.NET Core處理請(qǐng)求的性能及出現(xiàn)的異常,那么我們完全可以將HostingApplication對(duì)象記錄的日志作為收集的原始數(shù)據(jù)夷陋。實(shí)際上欠拾,目前很多APM(如Elastic APM和SkyWalking APM等)針對(duì)ASP.NET Core應(yīng)用的客戶端都是利用這種方式收集請(qǐng)求調(diào)用鏈信息的。
ILogger日志
為了確定什么樣的信息會(huì)被作為診斷日志記錄下來骗绕,下面介紹一個(gè)簡單的實(shí)例藐窄,將HostingApplication對(duì)象寫入的診斷日志輸出到控制臺(tái)上。前面提及酬土,HostingApplication對(duì)象會(huì)將相同的診斷信息以3種不同的方式進(jìn)行記錄荆忍,其中包含日志系統(tǒng),所以我們可以通過注冊(cè)對(duì)應(yīng)ILoggerProvider對(duì)象的方式將日志內(nèi)容寫入對(duì)應(yīng)的輸出渠道撤缴。
整個(gè)演示實(shí)例如下面的代碼片段所示:首先通過調(diào)用IWebHostBuilder接口的ConfigureLogging方法注冊(cè)一個(gè)ConsoleLoggerProvider對(duì)象刹枉,并開啟針對(duì)日志范圍的支持。我們調(diào)用IApplicationBuilder接口的Run擴(kuò)展方法注冊(cè)了一個(gè)中間件屈呕,該中間件在處理請(qǐng)求時(shí)會(huì)利用表示當(dāng)前請(qǐng)求上下文的HttpContext對(duì)象得到與之綁定的IServiceProvider對(duì)象微宝,并進(jìn)一步從中提取出用于發(fā)送日志事件的ILogger<Program>對(duì)象,我們利用它寫入一條Information等級(jí)的日志虎眨。如果請(qǐng)求路徑為“/error”蟋软,那么該中間件會(huì)拋出一個(gè)InvalidOperationException類型的異常。
publicclass Program
{
? ? publicstaticvoid Main()
? ? {
? ? ? ? Host.CreateDefaultBuilder()
? ? ? ? ? ? .ConfigureLogging(builder => builder.AddConsole(options => options.IncludeScopes =true))
? ? ? ? ? ? .ConfigureWebHostDefaults(builder => builder
? ? ? ? ? ? ? ? .Configure(app => app.Run(context =>? ? ? ? ? ? ? ? {
? ? ? ? ? ? ? ? ? ? varlogger = context.RequestServices.GetRequiredService>();
? ? ? ? ? ? ? ? ? ? logger.LogInformation($"Log for event Foobar");
? ? ? ? ? ? ? ? ? ? if(context.Request.Path ==newPathString("/error"))
? ? ? ? ? ? ? ? ? ? {
? ? ? ? ? ? ? ? ? ? ? ? thrownewInvalidOperationException("Manually throw exception.");
? ? ? ? ? ? ? ? ? ? }
? ? ? ? ? ? ? ? ? ? return Task.CompletedTask;
? ? ? ? ? ? ? ? })))
? ? ? ? ? ? .Build()
? ? ? ? ? ? .Run();
? ? }
}
在啟動(dòng)程序之后嗽桩,我們利用瀏覽器采用不同的路徑(“/foobar”和“/error”)向應(yīng)用發(fā)送了兩次請(qǐng)求岳守,演示程序的控制臺(tái)上呈現(xiàn)的輸出結(jié)果如下圖所示。由于我們開啟了日志范圍的支持碌冶,所以被ConsoleLogger記錄下來的日志都會(huì)攜帶日志范圍的信息湿痢。日志范圍的唯一標(biāo)識(shí)被稱為請(qǐng)求ID(Request ID),它由當(dāng)前的連接ID和一個(gè)序列號(hào)組成扑庞。從圖13-4可以看出譬重,兩次請(qǐng)求的ID分別是“0HLO4ON65ALGG:00000001”和“0HLO4ON65ALGG:00000002”。由于采用的是長連接嫩挤,并且兩次請(qǐng)求共享同一個(gè)連接害幅,所以它們具有相同的連接ID(“0HLO4ON65ALGG”)。同一連接的多次請(qǐng)求將一個(gè)自增的序列號(hào)(“00000001”和“00000002”)作為唯一標(biāo)識(shí)岂昭。
除了用于唯一表示每個(gè)請(qǐng)求的請(qǐng)求ID以现,日志范圍承載的信息還包括請(qǐng)求指向的路徑,這也可以從圖13-4所示的輸出接口看出來约啊。另外邑遏,上述請(qǐng)求ID實(shí)際上對(duì)應(yīng)HttpContext類型的TraceIdentifier屬性。如果需要進(jìn)行跨應(yīng)用的調(diào)用鏈跟蹤恰矩,所有相關(guān)日志就可以通過共享TraceIdentifier屬性構(gòu)建整個(gè)調(diào)用鏈记盒。
publicabstractclass HttpContext
{
? ? publicabstractstringTraceIdentifier {get;set; }
? ? ...
}
對(duì)于兩次采用不同路徑的請(qǐng)求,控制臺(tái)共捕獲了7條日志外傅,其中類別為App.Program的日志是應(yīng)用程序自行寫入的纪吮,HostingApplication寫入日志的類別為“Microsoft.AspNetCore.Hosting.Diagnostics”俩檬。對(duì)于第一次請(qǐng)求的3條日志消息,第一條是在HostingApplication開始處理請(qǐng)求時(shí)寫入的碾盟,我們利用這條日志獲知請(qǐng)求的HTTP版本(HTTP/1.1)棚辽、HTTP方法(GET)和請(qǐng)求URL。對(duì)于包含主體內(nèi)容的請(qǐng)求冰肴,請(qǐng)求主體內(nèi)容的媒體類型(Content-Type)和大星辍(Content-Length)也會(huì)一并記錄下來。當(dāng)HostingApplication對(duì)象處理完請(qǐng)求后會(huì)寫入第三條日志熙尉,日志承載的信息包括請(qǐng)求處理耗時(shí)(67.877 6毫秒)和響應(yīng)狀態(tài)碼(200)联逻。如果響應(yīng)具有主體內(nèi)容,對(duì)應(yīng)的媒體類型同樣會(huì)被記錄下來检痰。
對(duì)于第二次請(qǐng)求包归,由于我們?nèi)藶閽伋隽艘粋€(gè)異常,所以異常的信息被寫入日志攀细。但是如果足夠仔細(xì)箫踩,就會(huì)發(fā)現(xiàn)這條等級(jí)為Error的日志并不是由HostingApplication對(duì)象寫入的,而是作為服務(wù)器的KestrelServer寫入的谭贪,因?yàn)樵撊罩静捎玫念悇e為“Microsoft.AspNetCore.Server.Kestrel”境钟。換句話說,HostingApplication對(duì)象利用ILogger記錄的日志中并不包含應(yīng)用的異常信息俭识。
DiagnosticSource診斷日志
HostingApplication采用的3種日志形式還包括基于DiagnosticSource對(duì)象的診斷日志慨削,所以我們可以通過注冊(cè)診斷監(jiān)聽器來收集診斷信息。如果通過這種方式獲取診斷信息套媚,就需要預(yù)先知道診斷日志事件的名稱和內(nèi)容荷載的數(shù)據(jù)結(jié)構(gòu)缚态。通過查看HostingApplication類型的源代碼,我們會(huì)發(fā)現(xiàn)它針對(duì)“開始請(qǐng)求”堤瘤、“結(jié)束請(qǐng)求”和“未處理異趁德”這3類診斷日志事件對(duì)應(yīng)的名稱,具體如下本辐。
開始請(qǐng)求:Microsoft.AspNetCore.Hosting.BeginRequest桥帆。
結(jié)束請(qǐng)求:Microsoft.AspNetCore.Hosting.EndRequest。
未處理異常:Microsoft.AspNetCore.Hosting.UnhandledException慎皱。
至于針對(duì)診斷日志消息的內(nèi)容荷載(Payload)的結(jié)構(gòu)老虫,上述3類診斷事件具有兩個(gè)相同的成員,分別是表示當(dāng)前請(qǐng)求上下文的HttpContext和通過一個(gè)Int64整數(shù)表示的當(dāng)前時(shí)間戳茫多,對(duì)應(yīng)的數(shù)據(jù)成員的名稱分別為httpContext和timestamp祈匙。對(duì)于未處理異常診斷事件,它承載的內(nèi)容荷載還包括一個(gè)額外的成員,那就是表示拋出異常的Exception對(duì)象夺欲,對(duì)應(yīng)的成員名稱為exception跪帝。
既然我們已經(jīng)知道事件的名稱和診斷承載數(shù)據(jù)的成員,所以可以定義如下所示的DiagnosticCollector類型作為診斷監(jiān)聽器(需要針對(duì)NuGet包“Microsoft.Extensions.DiagnosticAdapter”的引用)些阅。針對(duì)上述3類診斷事件歉甚,我們?cè)贒iagnosticCollector類型中定義了3個(gè)對(duì)應(yīng)的方法,各個(gè)方法通過標(biāo)注的DiagnosticNameAttribute特性設(shè)置了對(duì)應(yīng)的診斷事件扑眉。我們根據(jù)診斷數(shù)據(jù)承載的結(jié)構(gòu)定義了匹配的參數(shù),所以DiagnosticSource對(duì)象寫入診斷日志提供的診斷數(shù)據(jù)將自動(dòng)綁定到對(duì)應(yīng)的參數(shù)上赖钞。
publicclass DiagnosticCollector
{
? ? [DiagnosticName("Microsoft.AspNetCore.Hosting.BeginRequest")]
? ? publicvoidOnRequestStart(HttpContext httpContext,long timestamp)
? ? {
? ? ? ? varrequest = 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")]
? ? publicvoidOnRequestEnd(HttpContext httpContext,long timestamp)
? ? {
? ? ? ? varstartTimestamp =long.Parse(httpContext.Items["StartTimestamp"].ToString());
? ? ? ? vartimestampToTicks = TimeSpan.TicksPerSecond / (double)Stopwatch.Frequency;
? ? ? ? varelapsed =newTimeSpan((long)(timestampToTicks * (timestamp - startTimestamp)));
? ? ? ? Console.WriteLine($"Request finished in {elapsed.TotalMilliseconds}ms { httpContext.Response.StatusCode}");
? ? }
? ? [DiagnosticName("Microsoft.AspNetCore.Hosting.UnhandledException")]
? ? publicvoidOnException(HttpContext httpContext,long timestamp, Exception exception)
? ? {
? ? ? ? OnRequestEnd(httpContext, timestamp);
? ? ? ? Console.WriteLine($"{exception.Message}\nType:{exception.GetType()}\nStacktrace: { exception.StackTrace}");
? ? }
}
可以在針對(duì)“開始請(qǐng)求”診斷事件的OnRequestStart方法中輸出當(dāng)前請(qǐng)求的HTTP版本腰素、HTTP方法和URL。為了能夠計(jì)算整個(gè)請(qǐng)求處理的耗時(shí)雪营,我們將當(dāng)前時(shí)間戳保存在HttpContext上下文的Items集合中弓千。在針對(duì)“結(jié)束請(qǐng)求”診斷事件的OnRequestEnd方法中,我們將這個(gè)時(shí)間戳從HttpContext上下文中提取出來献起,結(jié)合當(dāng)前時(shí)間戳計(jì)算出請(qǐng)求處理耗時(shí)洋访,該耗時(shí)和響應(yīng)的狀態(tài)碼最終會(huì)被寫入控制臺(tái)。針對(duì)“未處理異城床停”診斷事件的OnException方法則在調(diào)用OnRequestEnd方法之后將異常的消息姻政、類型和跟蹤堆棧輸出到控制臺(tái)上。
如下面的代碼片段所示岂嗓,在注冊(cè)的Startup類型中汁展,我們?cè)贑onfigure方法注入DiagnosticListener服務(wù),并調(diào)用它的SubscribeWithAdapter擴(kuò)展方法將上述DiagnosticCollector對(duì)象注冊(cè)為診斷日志的訂閱者厌殉。與此同時(shí)食绿,我們調(diào)用IApplicationBuilder接口的Run擴(kuò)展方法注冊(cè)了一個(gè)中間件,該中間件會(huì)在請(qǐng)求路徑為“/error”的情況下拋出一個(gè)異常公罕。
publicclass Program
{
? ? publicstaticvoid Main()
? ? {
? ? ? ? Host.CreateDefaultBuilder()
? ? ? ? ? ? .ConfigureLogging(builder => builder.ClearProviders())
? ? ? ? ? ? .ConfigureWebHostDefaults(builder => builder.UseStartup())
? ? ? ? ? ? .Build()
? ? ? ? ? ? .Run();
? ? }
}publicclass Startup
{
? ? publicvoid Configure(IApplicationBuilder app, DiagnosticListener listener)
? ? {
? ? ? ? listener.SubscribeWithAdapter(new DiagnosticCollector());
? ? ? ? app.Run(context =>? ? ? ? {
? ? ? ? ? ? if(context.Request.Path ==newPathString("/error"))
? ? ? ? ? ? {
? ? ? ? ? ? ? ? thrownewInvalidOperationException("Manually throw exception.");
? ? ? ? ? ? }
? ? ? ? ? ? return Task.CompletedTask;
? ? ? ? });
? ? }
}
待演示實(shí)例正常啟動(dòng)后器紧,可以采用不同的路徑(“/foobar”和“/error”)對(duì)應(yīng)用程序發(fā)送兩個(gè)請(qǐng)求,服務(wù)端控制臺(tái)會(huì)以圖13-5所示的形式輸出DiagnosticCollector對(duì)象收集的診斷信息楼眷。如果我們?cè)噲D創(chuàng)建一個(gè)針對(duì)ASP.NET Core的APM框架來監(jiān)控請(qǐng)求處理的性能和出現(xiàn)的異常铲汪,可以采用這樣的方案來收集原始的診斷信息。
EventSource事件日志
除了上述兩種日志形式摩桶,HostingApplication對(duì)象針對(duì)每個(gè)請(qǐng)求的處理過程中還會(huì)利用EventSource對(duì)象發(fā)出相應(yīng)的日志事件桥状。除此之外,在啟動(dòng)和關(guān)閉應(yīng)用程序(實(shí)際上就是啟動(dòng)和關(guān)閉IWebHost對(duì)象)時(shí)硝清,同一個(gè)EventSource對(duì)象還會(huì)被使用辅斟。這個(gè)EventSource類型采用的名稱為Microsoft.AspNetCore.Hosting,上述5個(gè)日志事件對(duì)應(yīng)的名稱如下芦拿。
啟動(dòng)應(yīng)用程序:HostStart士飒。
開始處理請(qǐng)求:RequestStart查邢。
請(qǐng)求處理結(jié)束:RequestStop。
未處理異常:UnhandledException酵幕。
關(guān)閉應(yīng)用程序:HostStop扰藕。
我們可以通過如下所示的實(shí)例來演示如何利用創(chuàng)建的EventListener對(duì)象來監(jiān)聽上述5個(gè)日志事件。如下面的代碼片段所示芳撒,我們定義了派生于抽象類EventListener的DiagnosticCollector邓深。在啟動(dòng)應(yīng)用前,我們創(chuàng)建了這個(gè)DiagnosticCollector對(duì)象笔刹,并通過注冊(cè)其EventSourceCreated事件開啟了針對(duì)目標(biāo)名稱為Microsoft.AspNetCore.Hosting的EventSource的監(jiān)聽芥备。在注冊(cè)的EventWritten事件中,我們將監(jiān)聽到的事件名稱的負(fù)載內(nèi)容輸出到控制臺(tái)上舌菜。
publicclass Program
{
? ? privatesealedclass DiagnosticCollector : EventListener {}
? ? staticvoid Main()
? ? {
? ? ? ? varlistener =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(intindex =0; index < args.PayloadNames.Count; index++)
? ? ? ? ? ? {
? ? ? ? ? ? ? ? Console.WriteLine($"\t{args.PayloadNames[index]} = {args.Payload[index]}");
? ? ? ? ? ? }
? ? ? ? };
? ? ? ? Host.CreateDefaultBuilder()
? ? ? ? ? ? .ConfigureLogging(builder => builder.ClearProviders())
? ? ? ? ? ? .ConfigureWebHostDefaults(builder => builder
? ? ? ? ? ? ? ? .Configure(app => app.Run(context =>? ? ? ? ? ? ? ? {
? ? ? ? ? ? ? ? ? ? if(context.Request.Path ==newPathString("/error"))
? ? ? ? ? ? ? ? ? ? {
? ? ? ? ? ? ? ? ? ? ? ? thrownewInvalidOperationException("Manually throw exception.");
? ? ? ? ? ? ? ? ? ? }
? ? ? ? ? ? ? ? ? ? return Task.CompletedTask;
? ? ? ? ? ? ? ? })))
? ? ? ? ? ? .Build()
? ? ? ? ? ? .Run();
? ? }
}
亞馬遜測(cè)評(píng) www.yisuping.com