Revisiting improved HTTP logging in ASP.NET Core 8

A few years ago, I had a play with HTTP logging added in ASP.NET Core 6. ASP.NET Core 8 introduced a set of additional configuration options that I believe are essential to make this feature usable. I will recap the details from the previous post below, but for more context, the first part of this series is here. In this post, I'll go through some of the changes introduced in HTTP logging since last.

Revisiting improved HTTP logging in ASP.NET Core 8

Before I jump into the improvements, let's recap how to set up HTTP logging. In an ASP.NET Core 8 web app, include the following in the Program.cs file:

var builder = WebApplication.CreateBuilder(args);

builder.Services.AddHttpLogging(options => {}); // ⬅️

// ...

var app = builder.Build();

// ...

app.UseHttpLogging(); // ⬅️

// ...

app.Run();

The two important lines are marked with a commented arrow. Compared to ASP.NET Core 6, calling AddHttpLogging now seems mandatory, but you can avoid setting any options like in the code above.

Unless you already output Information logging in your configured logging providers, HTTP logging can be enabled by adding the following to the LogLevel section of the appsettings.json file:

"Microsoft.AspNetCore.HttpLogging.HttpLoggingMiddleware": "Information"

That's it. ASP.NET Core now outputs a log message per request and response through ILogger. This can be inspected in Visual Studio in the Output window.

Structured logging

I just wanted to follow up on my complaints about missing structured logging from the previous post. The issue is that when logging a request or response, the log message becomes a concatenated string of key/value pairs. Including newlines but still now optimal when viewed in remote loggers like SQL Server, Elasticsearch, or elmah.io:

Concatenated log message

This is not a huge issue since most of the information embedded in the message still ends up in the right fields (like Method, URL, and Code in the screenshot above). It would still be nice if log messages logged from HTTP logging would include a template message like when logging structured properties through the ILogger manually. I guess you can't win them all.

Combining request and response

One thing that bothered me when first looking at HTTP logging, was the separation between the request and response in two log messages (see the screenshot above). I like logging both the request and response in a single log message since it (IMO) represents the same action where a user requests the server and gets back a response.

Luckily, Microsoft included a new configuration option to do just that called CombineLogs that can be set in options:

builder.Services.AddHttpLogging(options =>
{
    options.CombineLogs = true; // ⬅️
});

When enabling combined logs, a single log message will be created per request (and response):

Request and response message in one

Notice how the message now contains the method, URL, and status code in one. Plus, the first part of the log message says "Request and Response". Having this option is great and causes log messages sent from HTTP logging to look more like other types of log messages like errors produced from exception logging middleware.

Interceptors

It's easy to demand everything in version 1 of a feature. But interceptors are one of those features that it's hard to live without. As already discussed in the previous post, the number of configuration options for HTTP logging is very limited. You basically only have the option to decide which fields and headers should be logged. This is where interceptors get really interesting.

As a default, HTTP logging will log all requests and responses. This includes requests for .css, .js, and similar files. Let's use this as an example of how to use interceptors. To ignore all requests for these two file types, add a new class named IgnoreLoggingInterceptor with the following implementation:

public class IgnoreLoggingInterceptor : IHttpLoggingInterceptor
{
    public ValueTask OnRequestAsync(HttpLoggingInterceptorContext logContext)
    {
        string? path = logContext.HttpContext.Request?.Path.Value;
        if (path == null
            || path.EndsWith(".css", StringComparison.OrdinalIgnoreCase)
            || path.EndsWith(".js", StringComparison.OrdinalIgnoreCase))
        {
            logContext.LoggingFields = HttpLoggingFields.None;
        }

        return default;
    }

    public ValueTask OnResponseAsync(HttpLoggingInterceptorContext logContext)
    {
        return default;
    }
}

The OnRequestAsync and OnResponseAsync methods will be called by HTTP logging before logging requests and responses. In these methods, we can do different things, like deciding which fields to include in the log message on runtime. In the example above, I set the LoggingFields property to HttpLoggingFields.None which will ignore the entire log message in case the request is for a css or js file. Since we already combined requests and responses into a single log message in the previous section, we only need to implement the OnRequestAsync method.

To include this interceptor in the pipeline, set it up just after calling the AddHttpLogging method in Program.cs:

builder.Services.AddHttpLogging(options =>
{
    options.CombineLogs = true;
});
builder.Services.AddHttpLoggingInterceptor<IgnoreLoggingInterceptor>(); // ⬅️

Having the option to add one or more interceptors is a very flexible feature for creating differentiated output messages from HTTP logging.

elmah.io: Error logging and Uptime Monitoring for your web apps

This blog post is brought to you by elmah.io. elmah.io is error logging, uptime monitoring, deployment tracking, and service heartbeats for your .NET and JavaScript applications. Stop relying on your users to notify you when something is wrong or dig through hundreds of megabytes of log files spread across servers. With elmah.io, we store all of your log messages, notify you through popular channels like email, Slack, and Microsoft Teams, and help you fix errors fast.

See how we can help you monitor your website for crashes Monitor your website