How to exclude health check endpoints from Serilog request logging

This is the fourth article in a series of articles using Serilog.AspNetCore in ASP.NET Core 3.X :.

  1. Part 1-Use Serilog RequestLogging to reduce log detail
  2. Part 2-Use Serilog to record selected endpoint attributes
  3. Part 3-Use Serilog.AspNetCore to record MVC properties
  4. Part 4-Exclude health check endpoints from Serilog request logging (this article)

Author: according to Le Wish

Translation address: https://www.cnblogs.com/yilezhu/p/12253361.html

Original address: https://andrewlock.net/using-serilog-aspnetcore-in-asp-net-core-3-excluding-health-check-endpoints-from-serilog-request-logging/

In the previous articles in this series, I described how to configure Serilog ’s RequestLogging middleware to add additional properties to Serilog ’s request log summary, such as the request host name or the selected endpoint name. I also showed how to use filters to add MVC or RazorPage specific properties to the summary log.

In this article, I will show how to filter out summary log messages for a specific request. This is very useful when you have an endpoint that is accessed frequently, because logging for every request is of little value.

Health check visits are more frequent

The motivation for this article comes from the behavior we see when running applications in Kubernetes. Kubernetes uses two types of "health checks" (or "probes") to check whether the application is working properly: liveness probes and readiness probes. You can configure the probe to issue HTTP requests to the application as an indicator of the normal operation of the application.

Starting from Kubernetes version 1.16, there is a third probe, startup probe .

The health checkpoints provided in ASP.NET Core 2.2+ are very suitable for these probes. You can set up a simple health check without any return value, and the health check responds to each request 200 OKto let Kubernetes know that your application has not crashed.

In ASP.NET Core 3.x, you can use endpoint routing to configure health checks. You must Startup.cs in the ConfigureServicesmiddle of the service must be added by calling AddHealthChecks (), and Configureused MapHealthChecks()to add endpoint health checks:

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();
            });
        }
}

In the above example, /healthzsending a request to will call the health check endpoint. Since I have not configured any health checks 200, as long as the application is running, the endpoint will always return a response:

In the above example, sending a request to / healthz will invoke the health check endpoint. Since I have not configured any running health checks, as long as the application is running, the endpoint will always return a 200 response:

image-20200201214448978

The only problem here is that Kubernetes will call this endpoint very frequently. Of course, the exact frequency is up to you, but it should be common to check every 10 seconds. But if you want Kubernetes to quickly restart a failed Pod, you need a relatively high frequency.

This is not an issue in itself; Kestrel can process millions of requests per second, so this is not a performance issue. The annoying problem here is that each request generates a certain amount of logs. Although it is not as many as the requests of the MVC infrastructure show-10 logs per request , even if there are only 1 log per request (as we obtained from Serilog.AspNetCore), it can be unpleasant.

The main problem here is that the log of the successful health check request does not actually tell us any useful information. They are not related to any business activities, they are purely infrastructure. It would be nice if you can skip the Serilog request summary log of these requests. In the next section, I will introduce the method I came up with, which relies on the content of the previous articles in this series and makes changes based on it.

Custom log levels for Serilog request logs

In the previous article, I showed how to include the selected endpoint in the Serilog request log . My method is to RequestLoggingOptions.EnrichDiagnosticContextprovide a custom function for the attribute when registering Serilog middleware

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();
    });
}

RequestLoggingOptionsThere is another attribute GetLevelwhich Func<>is used to determine the level of logging applied to a given request log. By default, it is set to the following functions :

public static class SerilogApplicationBuilderExtensions
{
    static LogEventLevel DefaultGetLevel(HttpContext ctx, double _, Exception ex) =>
        ex != null
            ? LogEventLevel.Error 
            : ctx.Response.StatusCode > 499 
                ? LogEventLevel.Error 
                : LogEventLevel.Information;
}

This function checks whether an exception is thrown for the request, or whether the response code is an 5xxerror. If so, it will create a Errorlevel summary log, otherwise it will create a Informationlevel log.

Suppose you want to record summary logs Debuginstead Information. First, you will create a helper function with the following required logic as follows:

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
}

You can then set the level function when calling 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
}

Now, your request summary log will all be recorded as Debug, unless an error occurs ( screen shot of Seq ):

Display the sequence of debug request logs

But how does this solve our problem of lengthy logs?

When you configure Serilog, you should usually define a minimum request level. For example, the following simple configuration sets the default level to Debug()and writes it to the console receiver:

Log.Logger = new LoggerConfiguration()
    .MinimumLevel.Debug()
    .WriteTo.Console()
    .CreateLogger();

Therefore, the easiest way to filter logs is to make the log level lower than MinimumLevelthe level specified in the logger configuration . In general, if the lowest level is used Verbose, it will almost always be filtered out.

The difficulty is that we do not want to always be Verbosethe log level as a summary log. If we do this, we will not get any non-error request logs, and Serilog middleware will become meaningless!

Instead, we want to set the log level Verbose only for requests to run health check endpoints. In the next section, I will show how to identify other requests without affecting them.

Use custom log levels for health check endpoint requests

What we need is the ability to identify health check requests when writing to the summary log. As shown earlier, this GetLevel()method takes the current HttpContextas a parameter, so there are theoretically some feasibility. For me, the most obvious approach is:

  • Compare the HttpContext.Requestpath with a list of known health check paths
  • When the health check endpoint is requested, the selected endpoint metadata is used for identification

The first option is the most obvious, but it is really not worth trying. Once you get caught in it, you will find that you have to start copying the request path and handle various edge cases, so I will skip that case here.

The second method uses a method similar to that used in my previous article . In this method, we obtain the IEndpointFeature selected by EndpointRoutingMiddleware for a given request. This function (if present) provides detailed information such as the display name and routing data of the selected endpoint.

If we assume that the health check is registered with the default display name , that is "Health checks", we can use HttpContextthe request to identify the "health check" as follows:

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;
    }
}

We can use this feature in combination with a customized version of the default GetLevelfeature to ensure the Verboselevel of summary logs used to run health check requests, used when an error occurs Errorand other requests 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;
        }
}

This nested trinocular operator has an additional logic-for error-free, we check if the endpoint with the display name "Health check" is selected, if it is selected, the level is used Verbose, otherwise it is used Information.

You can further promote this code to allow passing in other display names or other custom-use log levels. For simplicity, I did not do this here, but the relevant sample code on GitHub shows how to do this .

All that remains is to update Serilog middleware RequestLoggingOptionsto use your new features:

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
}

At this time, when you check the log after running the application, you will see the normal request log of the standard request, but there is no log of the health check (unless an error occurs!). In the screenshot below, I configured Serilog to also Verboselog, so that you can view health check requests-they are usually filtered out!

Seq screenshot showing a health check request using detailed level

to sum up

In this article, I showed how to provide a custom function for RequestLoggingOptions of Serilog middleware, which defines the LogEventLevel to be used for the log of a given request. For example, I showed how to use it to change the default level to Debug. If the level you select is lower than the lowest level, it will be completely filtered out and will not be recorded.

I also showed that you can use this method to filter the public (low-level) request logs generated by calling the health check endpoint. In general, these requests only make sense when they point out problems, but they usually also generate request logs when they succeed. Since these endpoints are called frequently, they can significantly increase the number of logs written (useless).

The method in this article is to check the selected IEndpointFeature and check whether it has the display name "Health checks". If it is, the request log will be Verbosewritten using the level, which is usually filtered out. For more flexibility, you can customize the logs displayed in this post to handle multiple endpoint names, or any other standard.

Guess you like

Origin www.cnblogs.com/moon3/p/12735697.html