Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Stop Exception generated by CancellationToken from getting Reported by ApplicationInsights

I thought it would be a good idea to use CancellationToken in my controller like so:

[HttpGet("things", Name = "GetSomething")]
public async Task<ActionResult> GetSomethingAsync(CancellationToken ct) {
    var result = await someSvc.LoadSomethingAsync(ct);
    return Ok(result);
}

The issue is that now Azure Application Insights shows a bunch of exceptions of type System.Threading.Tasks.TaskCancelledException: A Task was canceled. as well as the occasional Npgsql.PostgresException: 57014: canceling statement due to user request. This is noise that I don't need.

Application Insights is registered as a service using the standard method - services.AddApplicationInsightsTelemetry(Configuration);.

Attempted Solution

I thought I could jack into the application pipeline and catch the above Exceptions, converting them to normal Responses. I put the code below in various places. It catches any exceptions and if IsCancellationRequested, it returns a dummy response. Otherwise it rethrows the caught exception.

app.Use(async (ctx, next) =>
{
    try { await next(); }
    catch (Exception ex)
    {
        if (ctx.RequestAborted.IsCancellationRequested)
        {       
            ctx.Response.StatusCode = StatusCodes.Status418ImATeapot;
        }
        else { throw; }
    }
});

This code works in that it changes the response. However exceptions are still getting sent to Application Insights.

Requirements

  • I would like to have a solution that uses RequestAborted.IsCancellationRequested over trying to catch specific exceptions. The reason being that if I've already discovered one implementation that throws an exception not derived from OperationCanceledException the possibility exists there are others that will do the same.
  • It doesn't matter if dependency failures still get logged, just that the exceptions thrown as a result of the request getting canceled don't.
  • I don't want to have a try/catch in every controller method. It needs to be put in place in one spot.

Conclusion

I wish I understood Application Insights mechanism of reporting exceptions. After experimenting I feel like trying to catch errors in the Application pipeline isn't the correct approach. I just don't know what is.

like image 723
Daniel Gimenez Avatar asked Oct 29 '19 21:10

Daniel Gimenez


1 Answers

I was able to solve this issue with a TelemetryProcessor.

In order to get access to RequestAborted.IsCancellationRequested in the processor, the HttpContext service needed to be made available by calling services.AddHttpContextAccessor() in Startup.ConfigureServices.

In the processor I prevent any exception telemetry from being processed if IsCancellationRequested is true. Below is a simplified example of my final solution, as eventually I realized I needed to do some more nuanced work with Dependencies and Requests that are out of scope of my original request.

internal class AbortedRequestTelemetryProcessor : ITelemetryProcessor
{
    private readonly IHttpContextAccessor httpContextAccessor;
    private readonly ITelemetryProcessor next;

    public AbortedRequestTelemetryProcessor(
        ITelemetryProcessor next, IHttpContextAccessor httpContextAccessor)
    {
        this.httpContextAccessor = httpContextAccessor;
        this.next = next;
    }

    public void Process(ITelemetry item)
    {
        if (httpContextAccessor.HttpContext?.RequestAborted.IsCancellationRequested == true 
            && item is ExceptionTelemetry)
        {
            // skip exception telemetry if cancellation was requested
            return; 
        }
        // Send everything else:
        next.Process(item);
    }
}
like image 73
Daniel Gimenez Avatar answered Sep 17 '22 22:09

Daniel Gimenez