It’s not clear to me from the samples or your documentation how I can send additional information to a custom ISentryEventProcessor or ISentryEventExceptionProcessor since they seem to be called by the SentryClient in the SDK instead of me having to consume them in my application directly. While that is a great design (since the SDK calls it for me), I’d like to know what the recommendation is to add additional info to the SentryEvent . I see the samples just adding hardcoded strings in the Process(SentryEvent) and Process(Exception, SentryEvent) methods, but I need to pass a DTO so that it can be serialized with the SentryEvent (via SetExtra() ).
There are two options I can think of but unsure if there is a better way to do this.
Stuff this data in the HttpContext.Items and then pull it out in the EventProcessor via an injected IHttpContextAccessor . Though this seems brittle and I try to avoid using IHttpContextAcessor .
Stuff this extra data in the Exception’s Data dictionary. However, this will only work for a custom ISentryEventExceptionProcessor and not ISentryEventProcessor .
Is there a recommendation on how to accomplish this?
The processor takes IHttpAcessor (ASP.NET Core) as a dependency (why do you think this is brittle?). This shows you can use DI (like this) and also, that you could register your own “func” to retrieve the data you need.
Data added to Exception.Data is automatically read by the SDK and added to the event.
If you want to add data to the event before capturing it, you can use the scope:
Now if inside MyMethod you call Sentry.CaptureException or something, the tag you set before will be included.
If you’re sending the event yourself, you have access to the whole object that gets serialized and sent to Sentry:
Sentry.CaptureEvent(new SentryEvent() {
// access to the whole event
});
Note that SentryEvent has a constructor overload that takes an exception. Which is equivalent to CaptureException() but gives you access to the event too.
Your example about a DTO is similar to this:
Here you have a try catch where you capture the exception and add to Data a whole object that gets serialized and is shown in Sentry’s event UI.
Note about this example: anonymous object might not serialize if you’re using .NET Framework (I hope you’re not :)), in this case create a class.
You can register a func but it won’t help with the DTOP.
Could you please clarify where would the data you want to add come from?
Is it something you have access to at some point before calling some business logic?
I suggest simply pushing a scope (which in asp.net core you can do with the logger interface) and adding your DTO:
var dto = _dep.GetDto();
using (logger.BeginScope(dto))
{
// any event raised in this block will include the serialized DTO
}
// Scope is gone, so DTO won't be included anymore
The idea was that any method with a try/catch would take the input param (there is only 1 input since I am using a Request/Response model design), and somehow expose that input param DTO to the logged event/exception. I can see now how events are processed and that I should have control over creating them. WRT logging exceptions though it wasn’t as clear since I am using the .NET Core ILogger implementation. In this case, ISentryEventExceptionProcessor:Process(Exception exception, SentryEvent sentryEvent) is indirectly called by having called events on the ILogger instance. Thus, it wasn’t clear to me how to get this input param DTO to be logged with the SentryEvent:SetExtra() in the Process() method. It seems now that the alternatives are to either:
Stuff it into the Exception’s Data dictionary OR
use Sentry’s Scope mechanism around the call to ILogger in the catch block - per your suggestion
Which of these is preferred? Do you know how others typically accomplish this kind of thing?
WRT, using HttpContextAccessor, there are several considerations/tradeoffs:
Per a member of the ASPNET Core Development Team, there is additional performance cost associated with HttpContextAccessor usage and registration (assuming your app does not already need it) - see https://github.com/aspnet/Hosting/issues/793#issue-159298688.
HttpContext is not thread-safe
Injecting IHttpContextAccessor creates additional dependencies throughout your code. This not only violates SOLID principles, but adds overhead to having to mock yet another dependency in unit/integration tests.
Just because you can do something, doesn’t mean you should
Which of these is preferred? Do you know how others typically accomplish this kind of thing?
Adding the object to the Exception.Data is my personal way to do this. I prefer this to avoid coupling the code with IHub (the Sentry abstraction that you can use with DI to manipulate scope).
If you want more advanced stuff that IHub can help you with, then it makes sense to add the dependency. If it’s just an object to be serialized and shown in the event view, just add to the exception, simple enough.
WRT, using HttpContextAccessor , there are several considerations/tradeoffs:
The reason it impacts perf is AsyncLocal and a lot of things work with that anyway, including Sentry’s SDK. So if you are using Serilog, Sentry’s SDK and many other libraries, you’ll pay the price of AsyncLocal.
This is not really relevant to most. It is for ASP.NET Core when they were trying to reach 7 million requests per second on a 10GB netword card (maxing out the card as opposed to CPU/mem). But that’s a plain text benchmark and not a real world app. ASP.NET Core is still going to be faster than most proper frameworks out there even if you add AsyncLocal to it anyway.
HttpContext is not thread-safe. Right. And the call to the EventProcessor is done on the same thread that you captured the event. So unless you do Task.Run(() => _logger.Log(), things are going to work fine.
Taking IHttpContextAccessor as a dependency does mean you get a new dependency. But again, if you want request data made available somehow where you can’t pass that data explicitly, you need a mechanism and in ASP.NET Core, that’s the one.
I agree with trying to pass it explicitly. Anytime it’s possible. Another way is:
using(_logger.BeginScope(dto))
{
// do work here
}
This way in case of an exception, the SDK has a hold of the DTO and is able to add to the event. It does so relying on AsyncLocal (note the DTO wasn’t explicly passed via parameter so no other way).
It’s either that or the simpler and better approach of:
try
{
// do work
catch (Exception e)
{
e.Data["key'] = dto;
_logger.LogError(e);
}
The last one being explicit wouldn’t need AsyncLocal or any sort of ambient data since it’s being passed down as an argument.
@bruno-garcia
I’m trying to use scope to add detail but I’m not seeing it in the Event data in Sentry. I’m using Microsoft’s ILogger. Is there something I have to do to enable it? I’m also not seeing the RequestPayload or ActivityData. Here are my Sentry settings:
For the param I pass to BeginScope(), I’ve also tried using a string, Dictionary<string, string> and a List<KeyValuePair<string, string>> - neither of which add any data to my Sentry event.
The only thing which seems to get the data added is to create an Exception purely for the purpose of treating it as a DTO to pass the data to the Sentry Event. This feels like a hack since exceptions should only be used when they’re meant to be thrown. In this use case, I do not want to throw an exception.
var ex = new Exception("a useless exception");
ex.Data.Add("response", badRequestObjectResult);
logger.LogError(ex, "Model Validation error");