這是在ASP.NET Core 3.X中使用Serilog.AspNetCore系列文章的第四篇文章:。html
做者:依樂祝git
譯文地址:http://www.javashuo.com/article/p-erjgcyns-da.htmlgithub
在本系列的前幾篇文章中,我描述瞭如何配置Serilog的RequestLogging中間件以向Serilog的請求日誌摘要中添加附加屬性,例如請求主機名或選定的端點名稱。我還展現瞭如何使用過濾器將MVC或RazorPage特定的屬性添加到摘要日誌。app
在本文中,我將展現如何過濾掉某個特定請求的摘要日誌消息。當您有一個訪問比較頻繁的端點時,這很是有用,由於爲每一個請求都進行記錄幾乎沒有什麼價值。函數
這篇文章的動機來自咱們在Kubernetes中運行應用程序時看到的行爲。Kubernetes使用兩種類型的「健康檢查」(或「探針」)來檢查應用程序是否正常運行:liveness probes和readiness probes。您能夠將探測配置爲嚮應用程序發出HTTP請求,做爲應用程序正常運行的指示器。post
從Kubernetes 1.16版開始,存在第三種探針,即startup probe。性能
在ASP.NET Core 2.2+中提供的健康檢查終結點很是適合這些探針。您能夠設置一個簡單,沒有任何返回值的健康檢查,該健康檢查對每一個請求返回200 OK
的響應,以使Kubernetes知道您的應用程序沒有崩潰。ui
在ASP.NET Core 3.x中,可使用終結點路由來配置健康檢查。您必須在Startup.cs中的ConfigureServices
中經過調用AddHealthChecks()來添加必須的服務,並在Configure
中使用MapHealthChecks()
來添加健康檢查終結點:.net
public class Startup { public void ConfigureServices(IServiceCollection services) { // ..other service configuration services.AddHealthChecks(); // Add health check services } public void Configure(IApplicationBuilder app, IWebHostEnvironment env) { // .. other middleware app.UseRouting(); app.UseAuthorization(); app.UseEndpoints(endpoints => { endpoints.MapHealthChecks("/health"); //Add health check endpoint endpoints.MapControllers(); }); } }
在上面的示例中,向/healthz
發送請求將調用健康檢查終結點。因爲我沒有配置任何運行情況檢查200
,所以只要應用程序正在運行,端點將始終返回響應:
在上面的示例中,向/healthz發送請求將調用運行情況檢查終結點。因爲我沒有配置任何運行的健康檢查,所以只要應用程序正在運行,端點將始終返回200響應:
這裏存在的惟一的問題是Kubernetes將很是頻繁的調用這個終結點。固然,確切的頻率由您決定,但每10秒檢查一次應該是很常見的。可是若是你想讓Kubernetes能夠快速重啓有故障的Pod的話,您就須要一個相對較高的頻率了。
這自己不是問題;Kestrel每秒能夠處理數百萬個請求,所以這不是性能問題。這裏使人比較煩惱的問題是每一個請求都會生成必定數量的日誌。雖然它沒有MVC基礎架構的請求所示的那麼多-每一個請求10個日誌,可是即便每一個請求只有1個日誌(就像咱們從Serilog.AspNetCore得到的那樣)均可能會使人不快。
這裏的主要問題是成功進行健康檢查請求的日誌實際上並未告訴咱們任何有用的信息。它們與任何業務活動都不相關,它們純粹是基礎設施。這裏若是可以跳過這些請求的Serilog請求摘要日誌會很好。在下一部分中,我將介紹我所想出的方法,該方法依賴於本系列前面幾篇文章的內容,並在其基礎上作出更改。
在上一篇文章中,我展現瞭如何在Serilog請求日誌中包括所選終結點。個人方法是在註冊Serilog中間件時爲RequestLoggingOptions.EnrichDiagnosticContext
屬性提供一個自定義函數
public void Configure(IApplicationBuilder app, IWebHostEnvironment env) { // ... Other middleware app.UseSerilogRequestLogging(opts // EnrichFromRequest helper function is shown in the previous post => opts.EnrichDiagnosticContext = LogHelper.EnrichFromRequest); app.UseRouting(); app.UseAuthorization(); app.UseEndpoints(endpoints => { endpoints.MapHealthChecks("/healthz"); //Add health check endpoint endpoints.MapControllers(); }); }
RequestLoggingOptions
具備另外一個屬性,GetLevel
該屬性的Func<>
被用於肯定應用於給定請求日誌的日誌記錄級別。默認狀況下,它設置爲如下功能:
public static class SerilogApplicationBuilderExtensions { static LogEventLevel DefaultGetLevel(HttpContext ctx, double _, Exception ex) => ex != null ? LogEventLevel.Error : ctx.Response.StatusCode > 499 ? LogEventLevel.Error : LogEventLevel.Information; }
此函數檢查是否爲請求引起了異常,或者響應代碼是否爲5xx
錯誤。若是是這樣,它將建立一個Error
級別的摘要日誌,不然將建立一個Information
級別日誌。
假設您但願將摘要日誌記錄爲Debug
而不是Information
。首先,您將建立一個具備如下所需邏輯的輔助函數,以下所示:
public static class LogHelper { public static LogEventLevel CustomGetLevel(HttpContext ctx, double _, Exception ex) => ex != null ? LogEventLevel.Error : ctx.Response.StatusCode > 499 ? LogEventLevel.Error : LogEventLevel.Debug; //Debug instead of Information }
而後,您能夠在調用時設置級別功能UseSerilogRequestLogging()
:
public void Configure(IApplicationBuilder app, IWebHostEnvironment env) { // ... Other middleware app.UseSerilogRequestLogging(opts => { opts.EnrichDiagnosticContext = LogHelper.EnrichFromRequest; opts.GetLevel = LogHelper.CustomGetLevel; // Use custom level function }); //... other middleware }
如今,您的請求摘要日誌將所有記錄爲Debug
,除非發生錯誤(Seq的屏幕截圖):
但這如何解決咱們的冗長日誌的問題呢?
當你在配置Serilog時,你一般應該會定義一個最低請求級別。例如,如下簡單配置將默認級別設置爲Debug()
,並將其寫入控制檯接收器:
Log.Logger = new LoggerConfiguration() .MinimumLevel.Debug() .WriteTo.Console() .CreateLogger();
所以,過濾日誌的最簡單方法是使日誌級別低於MinimumLevel
記錄器配置中指定的級別。通常而言,若是使用最低級別Verbose
,它將幾乎老是被過濾掉。
困難之處在於咱們不想老是將Verbose
用做摘要日誌的日誌級別。若是這樣作,咱們將不會得到任何非錯誤的請求日誌,而Serilog中間件將變得毫無心義!
相反,咱們但願將日誌級別設置爲Verbose
僅針對運行健康檢查端點的請求。在下一節中,我將展現如何在不影響其餘請求的狀況下識別這些請求。
咱們須要的是可以在寫入摘要日誌時識別出健康檢查的請求的能力。如前所示,該GetLevel()
方法將當前HttpContext
做爲參數,所以理論上有一些可行性。對我來講,最明顯的作法是:
HttpContext.Request
路徑與已知的健康檢查路徑列表進行比較第一種選擇是最明顯的,可是它真的不值得嘗試。一旦你陷入其中,你會發現你必須開始複製請求路徑並處理各類邊緣狀況,所以在這裏我將跳過該狀況。
第二種方法使用了與我上一篇文章中使用的方法相似,在該方法中,咱們得到了EndpointRoutingMiddleware爲給定請求選擇的IEndpointFeature。此功能(若是存在)提供了所選端點的顯示名稱和路由數據等詳細信息。
若是咱們假設健康檢查是使用默認顯示名稱註冊的,即"Health checks"
,則咱們可使用HttpContext
來標識「健康檢查」的請求,以下所示:
public static class LogHelper { private static bool IsHealthCheckEndpoint(HttpContext ctx) { var endpoint = ctx.GetEndpoint(); if (endpoint is object) // same as !(endpoint is null) { return string.Equals( endpoint.DisplayName, "Health checks", StringComparison.Ordinal); } // No endpoint, so not a health check endpoint return false; } }
咱們能夠將此功能與默認GetLevel
功能的自定義版本結合使用,以確保運行健康檢查請求的摘要日誌使用Verbose
級別,當發生錯誤時使用Error
而其餘請求則使用Information
:
public static class LogHelper { public static LogEventLevel ExcludeHealthChecks(HttpContext ctx, double _, Exception ex) => ex != null ? LogEventLevel.Error : ctx.Response.StatusCode > 499 ? LogEventLevel.Error : IsHealthCheckEndpoint(ctx) // Not an error, check if it was a health check ? LogEventLevel.Verbose // Was a health check, use Verbose : LogEventLevel.Information; } }
這個嵌套的三目運算符有一個額外的邏輯-對於無錯誤,咱們檢查是否選擇了顯示名爲「Health check」的端點,若是選擇了,則使用級別Verbose
,不然使用Information
。
您能夠進一步推廣此代碼,以容許傳入其餘顯示名稱或其餘自定義使用的日誌級別。爲了簡單起見,我在這裏沒有這樣作,可是GitHub上的相關示例代碼顯示瞭如何執行此操做。
剩下的就是更新Serilog中間件RequestLoggingOptions
以使用您的新功能:
public void Configure(IApplicationBuilder app, IWebHostEnvironment env) { // ... Other middleware app.UseSerilogRequestLogging(opts => { opts.EnrichDiagnosticContext = LogHelper.EnrichFromRequest; opts.GetLevel = LogHelper.ExcludeHealthChecks; // Use the custom level }); //... other middleware }
這時候當你運行應用程序後檢查日誌時,您會看到標準請求的普通請求日誌,但沒有健康檢查的日誌(除非發生錯誤!)。在下面的屏幕截圖中,我將Serilog配置爲也記錄Verbose
日誌,以便您能夠查看運行情況檢查請求-一般會將它們過濾掉!
在本文中,我展現瞭如何爲Serilog中間件的RequestLoggingOptions提供一個自定義函數,該函數定義了要爲給定請求的日誌使用的LogEventLevel。例如,我展現瞭如何使用它將默認級別更改成Debug。若是您選擇的級別低於最低級別,它將被徹底過濾掉,而且不會被記錄。
我還展現了您可使用這種方法來過濾經過調用健康檢查端點生成的公共(低級別的)請求日誌。通常來講,這些請求只有在指出問題時纔有意義,但它們一般也會在成功時生成請求日誌。因爲這些端點被頻繁調用,所以它們能夠顯著增長寫入的日誌數量(無用)。
本文中的方法是檢查選定的IEndpointFeature並檢查它是否具備顯示名稱「Health checks」。若是是,請求日誌將使用Verbose
級別寫入,這一般會被過濾掉。爲了更靈活,您能夠自定義在這個帖子中顯示的日誌來處理多個端點名稱,或者任何其餘的標準。