Error Logging Middleware in ASP.NET Core

This is part 5 in our series about ASP.NET Core:

Most parts of elmah.io consist of small services. While they may not be microservices, they are in fact small and each do one thing. We recently started experimenting with ASP.NET Core (or just Core for short) for some internal services and are planning a number of blog posts about the experiences we have made while developing these services. This is the fifth part in the series.

Error Logging Middleware in ASP.NET Core

This post is about the concept of middleware in Core. We have named the post Error Logging Middleware in ASP.NET Core, because we want to use error logging as an example of utilizing middleware. The concepts around middleware shown in the examples throughout this post isn't bound to error logging in any way and can be used as a foundation for building all types of middleware.

Middleware are code components executed as part of the request pipeline in Core. If you have a background in ASP.NET and think this sounds familiar, you're right. Middleware is pretty much HTTP modules as you know it from ASP.NET. The biggest difference between modules and middleware is really how you configure it. Modules are configured in web.config and since Core doesn't use the concept of a web.config, you configure middleware in C#. If you know Express for Node.js, you will find that configuring middleware heavily borrow a lot of concepts from that.

Let's wait with more babbling about middleware and look at an example. Middleware in its most simple form, is a C# class. Let's create some error logging middleware:

public class ErrorLoggingMiddleware
{
    private readonly RequestDelegate _next;

    public ErrorLoggingMiddleware(RequestDelegate next)
    {
        _next = next;
    }

    public async Task Invoke(HttpContext context)
    {
        try
        {
            await _next(context);
        }
        catch (Exception e)
        {
            System.Diagnostics.Debug.WriteLine($"The following error happened: {e.Message}");
            throw;
        }
    }
}

In order for our middleware to work, we need to implement two things. A constructor accepting a RequestDelegate and an Invoke method. The contructor is called a single time, but the underlying delegate will change from request to request. All middleware components are responsible for either executing the next link in the pipeline (_next) or terminate the pipeline by not calling _next. In our case, we want to execute the rest of the pipeline in order to catch any exceptions happening while processing the HTTP request. When an exception is catched, we log a message to System.Diagnostics.Debug. In real life you probably want to log somewhere better, but for the demo, we want the exception to show up inside Visual Studio only.

Notice that the catch-block throw the the exception after logging it to System.Diagnostics. Throwing the exception makes sure that other pieces of middleware handling exceptions still work.

Would your users appreciate fewer errors?

➡️ Reduce errors by 90% with elmah.io error logging and uptime monitoring ⬅️

To tell Core about our new and shiny piece of middleware, configure it in Startup.cs:

public void Configure(IApplicationBuilder app, IHostingEnvironment env, ILoggerFactory fac)
{
    ...
    app.UseMiddleware<ErrorLoggingMiddleware>();
    ...
}

Make sure to call the UseMiddleware-method after installing other pieces of middleware handling exceptions (like UseExceptionHandler). Some middleware classes "swallow" the exception, which result in your catch block not being triggered. By adding your middleware as the last piece, the class is invoked closer to the failing code.

To test the middleware, make the Index-method in HomeController throw an exception:

public class HomeController : Controller
{
    public IActionResult Index()
    {
        throw new Exception("Some error yo");
    }
}

When starting the project, the exception is now logged to the Output window in Visual Studio:

Debug message in output

Success! We just implemented our first piece of functional middleware for Core.

While calling the UseMiddleware<> method in Startup.cs definitely work, it is better to create a custom Use-method that will show up in IntelliSense and allow you to include additional config:

public static class ErrorLoggingMiddlewareExtensions
{
    public static IApplicationBuilder UseErrorLogging(this IApplicationBuilder builder)
    {
        return builder.UseMiddleware<ErrorLoggingMiddleware>();
    }
}

Call the static method in Startup.cs:

public void Configure(IApplicationBuilder app, IHostingEnvironment env, ILoggerFactory fac)
{
    ...
    app.UseErrorLogging();
    ...
}

For an example on how to implement fully featured error logging middleware for Core, check out our elmah.io support for ASP.NET Core on GitHub.

In the next post, we will take a look at routing in ASP.NET Core.

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