Duplicate error reports being sent when using custom provider class
Steve D
Posted on
May 24 2023
We have a ASP.NET Core website (v6) with Raygun v6.6.6. We have this code in our Startup.ConfigureServices.
services.AddRaygun(Configuration);
Then in Startup.Configure, this is below app.UseExceptionHandler("/Home/Error/").
app.UseRaygun();
We were also using a custom Raygun provider which inherits from DefaultRaygunAspNetCoreClientProvider to set the UserInfo object. This has been removed for testing purposes.
The problem is when an error occurs the system is sending two errors to Raygun. The first one is automatically picked up by Raygun. The second one is being trapped in Home/Error controller/action. Because two identical errors are being sent to Raygun, the second one is ignored, per Raygun documentation. We would like to send the error report with a custom reference number that will also be displayed to the user.
I understand that we can set an option to ignore all unhandled exceptions, but this seems counter to the purpose of using Raygun.
What we need is a way to send all errors to Raygun (custom provider, custom controller/action, whatever) that will only send one error report and also allow us to include custom data? I've already talked to ChatGPT (that was a joke).
Any help would be appreciated.
Deleted User
Raygun
Posted on
May 25 2023
Hi Steve,
It sounds like we can help you here, but I have a few questions:
Is it correct that you want only one of these two errors to reach Raygun, and you want that error to have custom data attached?
When you say "the system is sending 2 errors to Raygun" - is that your intention or part of the issue? Do you know how two errors are being generated when a single error occurs?
Could you point me to the documentation you're looking at that discusses our handling of identical errors? We should happily accept 2 identical errors if they are both being sent to us, but they would be grouped into a single error group.
As for the idea to make a custom provider inheriting from DefaultRaygunAspNetCoreClientProvider, is that because you want to set user info on all errors you send to Raygun? Are you also intending to add the custom reference number as custom data on all errors?
Kind regards, Deacon
Steve D
Posted on
May 25 2023
To be fair, I can no longer find the page where I read Raygun ignores the second identical error report. If I find it again, I'll post it here.
We only want the error report with our custom reference id to display in Raygun. Currently, this error is not getting to Raygun or is not being displayed. As far as I'm aware there are no filters or settings to hide these errors.
We recently updated the app to .NET 6, but I don't believe that is causing the problem. The NuGet package we have installed is Mindscape.Raygun4Net.AspNetCore. Here is the complete code, we have this is in the Startup file not the Program file.
In ConfigureServices, almost at the end of the method.
services.AddRaygun(Configuration, new RaygunMiddlewareSettings()
{
ClientProvider = new RaygunAspCoreClientProvider()
});
In Configure immediately after we call app.UseExceptionHandler("/Home/Error")
app.UseRaygun();
And our custom provider, I know this is being executed.
public class RaygunAspCoreClientProvider : DefaultRaygunAspNetCoreClientProvider
{
public override RaygunClient GetClient(RaygunSettings settings, HttpContext context)
{
var client = base.GetClient(settings, context);
var identity = context?.User?.Identity as ClaimsIdentity;
var email = identity?.Claims?.Where(c => c.Type == "name").Select(c => c.Value).FirstOrDefault();
client.UserInfo = new RaygunIdentifierMessage(email)
{
IsAnonymous = !(identity?.IsAuthenticated == true),
Email = email,
FullName = $"{identity?.Claims?.Where(x => x.Type == "given_name").Select(c => c.Value).FirstOrDefault()} {identity?.Claims?.Where(x => x.Type == "family_name").Select(c => c.Value).FirstOrDefault()}",
UUID = Activity.Current?.Id ?? context.TraceIdentifier
};
return client;
}
}
Our /Home/Error action defined in Startup.
public async Task<IActionResult> Error(string errorId, [FromServices] IWebHostEnvironment webHostEnvironment)
{
var vm = new ErrorViewModel();
if (!string.IsNullOrWhiteSpace(errorId))
{
// IdentityServer4 error. Retrieve error details from identityserver
var message = await _interaction.GetErrorContextAsync(errorId);
if (message != null)
vm.Error = message;
}
else
{
var context = HttpContext.Features.Get<IExceptionHandlerFeature>();
var rc = _raygunClientProvider.GetClient(_raygunSettings.Value, HttpContext);
List<string> tags = new List<string>();
tags.Add($"Trace Identifier: {HttpContext.TraceIdentifier}");
var data = new Dictionary<string, string>() { { "Trace Identifier", HttpContext.TraceIdentifier } };
await rc.SendInBackground(context.Error, tags, data);
vm.Error = new ErrorMessage
{
RequestId = HttpContext.TraceIdentifier
};
}
if (Request.Query.ContainsKey("ReturnUrl"))
vm.Error.RedirectUri = Request.Query["ReturnUrl"];
return View(vm);
}
I can put a break point in our /Home/Error action on the await rc.SendInBackground call and when execution stops I can check Raygun and see an error entry with a tag of UnhandledException. When I let the break point execute, the second error never shows in Raygun.
Steve D
Posted on
May 25 2023
We have partially solved this issue by adding a scope to the Startup. But be aware this website is hosted on Azure which could be part of the issue.
services.AddScoped<IRaygunAspNetCoreClientProvider, DefaultRaygunAspNetCoreClientProvider>();
Our custom RaygunAspCoreClientProvider is not only being executed once. And we are not seeing two error reports in Raygun. The first is the unhandled exception, the second is the one sent by our /Home/Error controller.
Deleted User
Raygun
Posted on
May 26 2023
Hi Steve, I've spoken to a colleague who explained to me how Raygun4Net handles duplicates:
When the same Exception instance tries to be logged to Raygun twice, we only consume it once. The way this works is every time an Exception instance is passed to the Raygun4Net client, it adds an "AlreadySentToRaygun" tag to the Data dictionary of the Exception. Then if the client sees an exception with that tag, it throws it away.
So in your code, perhaps Raygun4Net's unhandled exception handling is marking that instance as "AlreadySentToRaygun", so by the time SendInBackground attempts to send the error along with custom data, it sees the "AlreadySentToRaygun" tag and ignores it. That lines up with the behaviour you saw when testing with the breakpoint.
So I'm thinking we need Raygun4Net's unhandled exception handling to not receive the exception instance at all, or to at least receive one that has the "AlreadySentToRaygun" tag.
One idea is to use Send
instead of SendInBackground
(that might at least confirm what the issue is). That would block the thread while sending, but if that Error controller is not on the main thread that could be an option?
Otherwise, there might be a way to mark the exception as "handled", or somewhere you can avoid re-throwing the error, so that it never reaches Raygun4Net's unhandled exception code if it passes through your /Home/Error action.
Are you using any exception-handling middleware above that /Home/Error controller, perhaps something in there needs to mark the exception as handled? Or are there any other places in your code which might be interfering with exception handling?
And finally, I'm not quite sure what you mean by "partially solved" sorry, what changed after you added the scope?
Kind regards,
Deacon
Steve D
Posted on
May 26 2023
There are no other error handling middleware that loads prior to what I've shown. Unless IdentityServer is loading there own; their stuff loads before anything else.
When I said partially solved I meant that our Raygun provider class was only being executed once per error instead of twice. Before adding the scope, it would execute twice, once for the automatic unhandled exception, and once when our /Home/Error handler executed. Adding the scope still allowed the statement;
var rc = _raygunClientProvider.GetClient(_raygunSettings.Value, HttpContext);
to execute without stepping into the class code (breakpoint was set).
I now suspect that the only way to deal with would be to wrap all the code we want to specifically trap errors for in a try/catch and call Send manually. This is what I've done in the past and it works, but I was not in complete control of this code.
I don't think there's much more that we can do. We at least now have a populated userinfo object being sent with all errors.
Thanks for your time and help.