Logging to Sentry in ASP.NET Core

With the increasingly distributed nature of modern web applications, logging can become problematic. Infrastructure providers such as Docker enable us to linearly scale applications on any number of virtual machines. That’s great, but what about the logs? When using traditional file logging implementations, we’d have to first figure out which container the error occurred on, we’d then have to attach to the container, and retrieve the relevant logs. That takes up a lot more time than it should.

Fortunately, error reporting services such as Sentry come to the rescue. They enable applications to log to a centralized repository, and provide rich tools for analysing any errors or log message that are reported. No longer are we plagued with logging into various containers or machines and retrieving text files — it’s all right there, on a single dashboard.

I couldn’t find many resources on how to implement Sentry in ASP.NET Core in an idiomatic way. This article covers implementing sentry through the use of a middleware component, providing you with a flexible implementation to log your exceptions to Sentry.

Middleware

In .NET Core 2.0, a good example of standard middleware is the DeveloperExceptionPageMiddleware component. When in development, if an exception occurs, this middleware component catches it and displays the relevant details to the developer.

That’s fantastic, but we definitely don’t want to be exposing stack traces and other juicy details to end users in a production setting, so we can’t be running that out in the wild.

But then what do we do? As it turns out, ASP.NET Core MVC doesn’t really provide any production-ready solution for logging unhandled exceptions. We’ll have to roll our own.

Logging exceptions

If we look at the source for DeveloperExceptionPageMiddleware, we can see that it essentially takes the RequestDelegate, wraps it in a try/catch clause, and if an exception occurs, some code is executed to display the exception details to the developer.

Surely we can do something similar to log unhandled exceptions to an error reporting service such as Sentry.

Sentry uses Raven to do its error reporting. For .NET Core, a package named RavenSharp.Core is available on NuGet. It’s worth noting that this is not actually an official package, but instead one written by as community member. At the moment of writing, there is no official .NET Core package available, and the one written by wakawaka54 works just fine.

For short, we’ll need the following for our middleware to work:

  1. Configuration for our Sentry DSN
  2. A class to send our error reports to Sentry
  3. A middleware component to catch any unhandled exceptions

Configuration

 “Sentry”: {
“Dsn”: “{your_DSN_here}”
}

Alright. Next up, we’ll need to hook this up to a type so we can pass it as an IOptions<T> type to our error reporting class.

Something along the lines of:

csharp
public class SentryOptions
{
public string Dsn { get; set; }
}

Should do just fine. Of course, there are more variables you can configure, but I’ll leave those up to you.

Next up, in Startup.cs, we’ll need to grab the options in our app settings and map them to our freshly created SentryOptions type so we can inject IOptions<SentryOptions> further down the road. In your ConfigureServices method, add the following line:

 services.Configure<SentryOptions>(Configuration.GetSection(“Sentry”));

Of course, if you named your configuration section anything other than Sentry, you’ll need to adjust the above call accordingly.

Alright, that should take care of the configuration part. We should be all set to write the actual error reporter now.

The Error Reporting class

The below code should be fairly straight forward:


public interface IErrorReporter
{
Task CaptureAsync(Exception exception);
Task CaptureAsync(string message);
}

The reason I’m declaring an IErrorReporter interface instead of hooking this stuff up to the Microsoft.Extensions.Loggingabstractions is because logging unhandled exceptions to services like Sentry is a hail-mary, last resort action, whereas general logging should (in my opinion) not end up in services such as Sentry, but in files, log stashes, or similar.

Our Middleware

As discussed before, we can achieve this through the use of a middleware component. Since we’re not manipulating the response, we can simplify the developer exception page middleware significantly, down to the point where just need to inject our error reporter class and wrap the request in a try/catch block:

And that’s really all there’s to it.

Wiring it all up

First off, in your Startup.cs, add the following to your ConfigureServices method:

 services.AddScoped<IErrorReporter, SentryErrorReporter>();

This will set up the DI container to inject an instance of our SentryErrorReporter into the middleware on a per-request scope.

Then, near the top of Configure, add:

 app.UseMiddleware<SentryMiddleware>();

Keep in mind the ASP.NET Core request pipeline goes in both directions, as illustrated by this image:

Since we’ve written our middleware to not handle the exception but instead to rethrow it, we need to make sure we add our middleware before any other exception-handling middleware on the way back to the response.

By default, ASP.NET Core MVC applications contain an error handler along the lines of app.UseExceptionHandler(“/Error”); which will actually catch the exception and handles it by displaying a page to the user whenever an error occurs. We’ll want to make sure that during response, our middleware gets the chance to catch the exception before such middleware runs.

Ergo, we should add it along the lines of:


if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
app.UseBrowserLink();
}
else
{
app.UseExceptionHandler(“/Error”);
}

app.UseMiddleware<SentryMiddleware>();

So we get the chance to report the exception to Sentry before any other middleware gets a chance to handle it.

Conclusion

Of course you could replace Sentry with any other similar service in a similar fashion as outlined above.

Keep in mind that users generally don’t report errors. They just abandon your product and go search for an alternative that does work. Being aware of issues your users run in as they occur is extremely important, and at least with ASP.NET Core, it’s a breeze too.

Software Engineer writing about his daily software adventures, wherever they may lead.

Software Engineer writing about his daily software adventures, wherever they may lead.