DEV Community

loading...
Cover image for ASP.NET Core request logging middleware

ASP.NET Core request logging middleware

Thomas Ardal
Entrepreneur and founder of elmah.io, Occasional speaker. TV show addict.
Originally published at blog.elmah.io ・4 min read

I needed to debug an error logged through Microsoft.Extensions.Logging (MEL) on one of our ASP.NET Core-based services today. Usually, when getting errors logged through MEL, all of the HTTP context data is logged as part of the error, making it easy to spot why an error happened or at least making it a lot easier recreating the error. Today's error didn't include much more information than an error message and a request-id.

To help debug what is turning sideways, I created a quick request logging feature in ASP.NET Core. There are multiple posts on the subject already, but while most of them are really good posts, they often cover too much for my simple use case or leave out essential information about how to create the configuration. I simply want a log message for each request, with as little information as possible, but enough for me to be able to correlate the generated error with an HTTP request.

To log a message on every web request, start by creating a new class named RequestLoggingMiddleware:

public class RequestLoggingMiddleware
{
}
Enter fullscreen mode Exit fullscreen mode

Add the following locals:

private readonly RequestDelegate _next;
private readonly ILogger _logger;
Enter fullscreen mode Exit fullscreen mode

I'll explain what these are and what they are used for in a bit. Create a constructor on the class:

public RequestLoggingMiddleware(RequestDelegate next, ILoggerFactory loggerFactory)
{
    _next = next;
    _logger = loggerFactory.CreateLogger<RequestLoggingMiddleware>();
}
Enter fullscreen mode Exit fullscreen mode

I set the RequestDelegate local added in the previous step and create a new MEL ILogger for my middleware class.

Finally, add a new Invoke method:

public async Task Invoke(HttpContext context)
{
    try
    {
        await _next(context);
    }
    finally
    {
        _logger.LogInformation(
            "Request {method} {url} => {statusCode}",
            context.Request?.Method,
            context.Request?.Path.Value,
            context.Response?.StatusCode);
    }
}
Enter fullscreen mode Exit fullscreen mode

This is where the magic happens. The method invokes the next piece of ASP.NET Core middleware and logs an information message with the HTTP method, URL and status code. To avoid repeating myself, check out Error Logging Middleware for a detailed explanation of how ASP.NET Core middleware works.

Here's the source for the entire class:

using Microsoft.AspNetCore.Http;
using Microsoft.Extensions.Logging;
using System.Threading.Tasks;

namespace My.Service.Logging
{
    public class RequestLoggingMiddleware
    {
        private readonly RequestDelegate _next;
        private readonly ILogger _logger;

        public RequestLoggingMiddleware(RequestDelegate next, ILoggerFactory loggerFactory)
        {
            _next = next;
            _logger = loggerFactory.CreateLogger<RequestLoggingMiddleware>();
        }

        public async Task Invoke(HttpContext context)
        {
            try
            {
                await _next(context);
            }
            finally
            {
                _logger.LogInformation(
                    "Request {method} {url} => {statusCode}",
                    context.Request?.Method,
                    context.Request?.Path.Value,
                    context.Response?.StatusCode);
            }
        }
    }
}
Enter fullscreen mode Exit fullscreen mode

The only missing part now is configuring the middleware. You'd normally create an extension method to help, but in this case I'm taking the easy path and call the UseMiddleware method directly in my Startup.cs class:

public void Configure(IApplicationBuilder app, IHostingEnvironment env)
{
    ...
    app.UseMiddleware<RequestLoggingMiddleware>();
    ...
}
Enter fullscreen mode Exit fullscreen mode

That's it. Every request now generates a log message in the configured log. In my case, I'm logging to elmah.io. I'm using the Elmah.Io.Extensions.Logging NuGet package to do the actual logging and the Elmah.Io.AspNetCore.ExtensionsLogging package to decorate all log messages with HTTP contextual information.

I normally don't log information messages from production, since ASP.NET Core generates a lot of info messages out of the box. In this example, I want to log all Warning, Error, and Fatal messages, but also the new Information messages generated from the new middleware class. This can be set up by including the following config in appsettings.json:

{
  "Logging": {
    "ElmahIo": {
      "LogLevel": {
        "Default": "Warning",
        "My.Service.Logging": "Information"
      }
    }
  },
  ...
}
Enter fullscreen mode Exit fullscreen mode

Inside the Logging section, I've included an ElmahIo section. If you are using Console or another logger, you will need to replace the section name with the correct one. By including a Default log level and setting the value to Warning, I make sure that only Warning messages and up are logged. For the specific namespace My.Service.Logging I also allow Information messages.

To use the configuration from the Logging section, call the AddConfiguration method as part of your logging initialization in the Program.cs file:

public static IWebHostBuilder CreateWebHostBuilder(string[] args) =>
    WebHost.CreateDefaultBuilder(args)
        .UseStartup<Startup>()
        .ConfigureLogging((ctx, logging) =>
        {
            logging.AddConfiguration(ctx.Configuration.GetSection("Logging"));
            ...
        });
Enter fullscreen mode Exit fullscreen mode

Everything is done in less than 30 minutes and ready to go to production. Here's an example of a log message stored in elmah.io:

If you are using another logger, the view will be different but you will be able to see all of the same information.

Would your users appreciate fewer errors?

elmah.io is the easy error logging and uptime monitoring service for .NET. Take back control of your errors with support for all .NET web and logging frameworks.

➡️ Error Monitoring for .NET Web Applications ⬅️

This article first appeared on the elmah.io blog at https://blog.elmah.io/asp-net-core-request-logging-middleware/

Discussion (4)

Collapse
antoniofalcao profile image
Antonio Falcão Jr. • Edited

Hi Thomas Ardal, very nice post!

At this moment, .NET developers starting using the native HTTP logging middleware, take a look too:

using Microsoft.AspNetCore.HttpLogging;

public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
    app.UseHttpLogging();
}

HTTP logging provides logs of:

  • HTTP Request information
  • Common properties
  • Headers
  • Body
  • HTTP Response information

To configure the HTTP logging middleware, you can specify HttpLoggingOptions in your call to ConfigureServices():

public void ConfigureServices(IServiceCollection services)
{
   services.AddHttpLogging(logging =>
   {
       // Customize HTTP logging here.
       logging.LoggingFields = HttpLoggingFields.All;
       logging.RequestHeaders.Add("My-Request-Header");
       logging.ResponseHeaders.Add("My-Response-Header");
       logging.MediaTypeOptions.AddText("application/javascript");
       logging.RequestBodyLogLimit = 4096;
       logging.ResponseBodyLogLimit = 4096;
   });
}

devblogs.microsoft.com/aspnet/asp-...

Collapse
thomasardal profile image
Thomas Ardal Author • Edited

Hi Antonio

Agree, HTTP logging looks cool for sure. Played around with it as soon as the recent preview of .NET 6 was released (and even found and reported a bug on it 😂). It will take some time before everyone are on .NET 6, though.

Collapse
kaylumah profile image
Max Hamulyák

Very cool Antonio missed that announcement, will be very usefull :)

Collapse
prasadhkumar profile image
prasadhkumar

Actually i too tried custom exception middle ware in our .net core 2.2 Web API ..but we faced a issue when an exception happens we were not able to get the request body and save it to db using serilog.... Tried using memory stream too for it wasn't much help