天天看點

如何從Serilog請求日志記錄中排除健康檢查終結點

這是在ASP.NET Core 3.X中使用Serilog.AspNetCore系列文章的第四篇文章:。

  1. 第1部分-使用Serilog RequestLogging減少日志詳細程度
  2. 第2部分-使用Serilog記錄所選的終結點屬性
  3. 第3部分-使用Serilog.AspNetCore記錄MVC屬性
  4. 第4部分-從Serilog請求日志記錄中排除健康檢查端點(本文)

作者:依樂祝

譯文位址:https://www.cnblogs.com/yilezhu/p/12253361.html

原文位址:https://andrewlock.net/using-serilog-aspnetcore-in-asp-net-core-3-excluding-health-check-endpoints-from-serilog-request-logging/

在本系列的前幾篇文章中,我描述了如何配置Serilog的RequestLogging中間件以向Serilog的請求日志摘要中添加附加屬性,例如請求主機名或標明的端點名稱。我還展示了如何使用過濾器将MVC或RazorPage特定的屬性添加到摘要日志。

在本文中,我将展示如何過濾掉某個特定請求的摘要日志消息。當您有一個通路比較頻繁的端點時,這非常有用,因為為每個請求都進行記錄幾乎沒有什麼價值。

健康檢查通路較頻繁

這篇文章的動機來自我們在Kubernetes中運作應用程式時看到的行為。Kubernetes使用兩種類型的“健康檢查”(或“探針”)來檢查應用程式是否正常運作:liveness probes和readiness probes。您可以将探測配置為向應用程式發出HTTP請求,作為應用程式正常運作的訓示器。

從Kubernetes 1.16版開始,存在第三種探針,即startup probe。

在ASP.NET Core 2.2+中提供的健康檢查終結點非常适合這些探針。您可以設定一個簡單,沒有任何傳回值的健康檢查,該健康檢查對每個請求傳回

200 OK

的響應,以使Kubernetes知道您的應用程式沒有崩潰。

在ASP.NET Core 3.x中,可以使用終結點路由來配置健康檢查。您必須在Startup.cs中的

ConfigureServices

中通過調用AddHealthChecks()來添加必須的服務,并在

Configure

中使用

MapHealthChecks()

來添加健康檢查終結點:

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響應:

如何從Serilog請求日志記錄中排除健康檢查終結點

這裡存在的唯一的問題是Kubernetes将非常頻繁的調用這個終結點。當然,确切的頻率由您決定,但每10秒檢查一次應該是很常見的。但是如果你想讓Kubernetes可以快速重新開機有故障的Pod的話,您就需要一個相對較高的頻率了。

這本身不是問題;Kestrel每秒可以處理數百萬個請求,是以這不是性能問題。這裡令人比較煩惱的問題是每個請求都會生成一定數量的日志。雖然它沒有MVC基礎架構的請求所示的那麼多-每個請求10個日志,但是即使每個請求隻有1個日志(就像我們從Serilog.AspNetCore獲得的那樣)都可能會令人不快。

這裡的主要問題是成功進行健康檢查請求的日志實際上并未告訴我們任何有用的資訊。它們與任何業務活動都不相關,它們純粹是基礎設施。這裡如果能夠跳過這些請求的Serilog請求摘要日志會很好。在下一部分中,我将介紹我所想出的方法,該方法依賴于本系列前面幾篇文章的内容,并在其基礎上做出更改。

定制用于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請求日志記錄中排除健康檢查終結點

但這如何解決我們的冗長日志的問題呢?

當你在配置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請求日志記錄中排除健康檢查終結點

總結

在本文中,我展示了如何為Serilog中間件的RequestLoggingOptions提供一個自定義函數,該函數定義了要為給定請求的日志使用的LogEventLevel。例如,我展示了如何使用它将預設級别更改為Debug。如果您選擇的級别低于最低級别,它将被完全過濾掉,并且不會被記錄。

我還展示了您可以使用這種方法來過濾通過調用健康檢查端點生成的公共(低級别的)請求日志。一般來說,這些請求隻有在指出問題時才有意義,但它們通常也會在成功時生成請求日志。由于這些端點被頻繁調用,是以它們可以顯著增加寫入的日志數量(無用)。

本文中的方法是檢查標明的IEndpointFeature并檢查它是否具有顯示名稱“Health checks”。如果是,請求日志将使用

Verbose

級别寫入,這通常會被過濾掉。為了更靈活,您可以自定義在這個文章中顯示的日志來處理多個端點名稱,或者任何其他的标準。

作者:依樂祝(祝雷)

出處:https://www.cnblogs.com/yilezhu

聯系:[email protected] .NET Core實戰項目交流群:637326624 微信:jkingzhu

本文版權歸作者和部落格園共有,歡迎轉載,但未經作者同意必須保留此段聲明,且在文章頁面明顯位置給出原文連接配接,否則保留追究法律責任的權利。如有問題或建議,請多多賜教,非常感謝。

如何從Serilog請求日志記錄中排除健康檢查終結點

繼續閱讀