sentry-javascript icon indicating copy to clipboard operation
sentry-javascript copied to clipboard

Improve handling of `HttpResponseError` objects being captured in Angular SDK

Open cobyeastwood183 opened this issue 11 months ago • 7 comments

Is there an existing issue for this?

  • [x] I have checked for existing issues https://github.com/getsentry/sentry-javascript/issues
  • [x] I have reviewed the documentation https://docs.sentry.io/
  • [x] I am using the latest SDK release https://github.com/getsentry/sentry-javascript/releases

How do you use Sentry?

Sentry Saas (sentry.io)

Which SDK are you using?

@sentry/angular

SDK Version

8.52.0

Framework Version

19.1.0

Link to Sentry event

No response

Reproduction Example/SDK Setup

https://github.com/cobyeastwood183/angular

error.json

{
    "headers": {
        "headers": {},
        "normalizedNames": {},
        "lazyUpdate": null
    },
    "status": 0,
    "statusText": "Unknown Error",
    "url": "http://some.example.com/test",
    "ok": false,
    "name": "HttpErrorResponse",
    "message": "Http failure response for http://some.example.com/test: 0 Unknown Error",
    "error": {
        "isTrusted": true
    }
}

Steps to Reproduce

  1. npm install
  2. add DSN
  3. npm start
  4. visit http://localhost:4200/test
  5. click button "Test API 2(Localhost)"

Expected Result

Expect a HttpException to be captured for HttpErrorResponse.

Actual Result

Instead 'unknown' is captured when caught by the ErrorHandler or 'Object.error' when caught by calling captureException.

cobyeastwood183 avatar Jan 31 '25 01:01 cobyeastwood183

Dumping my thoughts from the slack conversation here. I don't have time to look at this today but will continue on Monday.

I’ll summarize my preliminary findings from checking out the repro (thanks a lot btw!):

  • If you call captureException when handling the error, it won’t go through the ErrorHandler but will be sent directly to Sentry. In this case, the error object ends up with the Exception Captured With Keys message that you encountered.
    • Maybe we can apply the same extraction logic as in the ErrorHandler within the Angular SDK. let me think about this for a bit.
  • Letting the error bubble up via the ErrorHandler, leads to the error in Sentry.
    • The “” error type must have been changed recently in the product or during ingest because we add a title property to the event which is “” . The SDK doesn’t set title at all. So some kind of inference logic is going wrong here… I asked ~ingest~ the issues team about this
    • The other problem is that we capture much less information about this error than if we’d directly call captureException on it directly. Most importantly, we loose the stack trace
    • We could add a stack trace but due to Angular’s ZoneJS usage, it’s pretty worthless because it doesn’t point back to the user source code. Not sure yet if we can solve this but it doesn’t look trivial.

Lms24 avatar Jan 31 '25 16:01 Lms24

I improved HttpErrorResponse handling in my Angular project by implementing a custom error extractor. The extractor creates a custom Error object that includes debug info from the original HttpErrorResponse.

I also configured event fingerprinting for this custom error type to group errors with better granularity.

Now issues in Sentry look like this:

HttpErrorResponseError /Services/Asset/Deactivate: 400 – Missing 'date'

  • First line - custom error type
  • Second line - custom error message with details from HttpErrorResponse. It includes request path, response code and server message.

klyakh avatar Nov 03 '25 22:11 klyakh

Hey @klyakh thanks for letting us know! Are you interested in contributing your extraction logic as a PR? Perhaps we can combine it with our existing extractor to improve the errors for everyone. No pressure, just let us know :)

Lms24 avatar Nov 04 '25 10:11 Lms24

This issue has gone three weeks without activity. In another week, I will close it.

But! If you comment or otherwise update it, I will reset the clock, and if you remove the label Waiting for: Community, I will leave it alone ... forever!


"A weed is but an unloved flower." ― Ella Wheeler Wilcox 🥀

getsantry[bot] avatar Nov 26 '25 08:11 getsantry[bot]

Hi @Lms24 , I’m not ready to open a PR yet, because the code I have on my side feels more like a workaround tailored to my app than a clean, generic solution. But I can share what I found and how I’m currently handling it – maybe that helps.

1. Main problem: different endpoints end up in the same Issue

Right now, different HttpErrorResponse instances from different API endpoints are grouped into a single Issue. That makes it hard to use Sentry in a normal workflow: one Issue may contain many different root causes, some already fixed, some not.

In my setup, this is what happens:

  1. The Angular integration extracts only a string (not an Error object) from HttpErrorResponse, something like: Http failure response for https://mydomain.com/api/asset/search?page=1&pageSize=50&keyword=test: 401 Unauthorized
  2. Grouping then happens by the stripped exception value. After stripping, this becomes something like: Http failure response for <url> <int> Unauthorized
  3. Because the URL is normalized away, errors from different endpoints are grouped together.

So I lose the distinction between different API endpoints, which is often exactly what I want to group by.

2. Why only a simple string is captured from HttpErrorResponse

From what I see in extractHttpModuleError in packages/angular/src/errorhandler.ts, HttpErrorResponse.error is only used if it’s in one of these shapes:

  1. instanceof Error
  2. { name: string; message: string; stack?: string }
  3. instanceof ErrorEvent
  4. string

If it’s none of those, it’s effectively ignored and the code falls back to HttpErrorResponse.message. That’s how we end up with a plain string like the example above.

In my case, the server returns a structured JSON for server-side errors, e.g.:

{
  "message": "An error has occurred.",
  "exceptionMessage": "This is test server error",
  "exceptionType": "System.NotImplementedException",
  "stackTrace": "<server-side stack trace here>"
}

This entire JSON ends up in HttpErrorResponse.error, but because it doesn’t match any of the four patterns, it’s ignored. As a result:

  • The meaningful fields from the server response are not visible in the Sentry event.
  • I also see <unknown> as the Issue title in the UI for these cases.

3. My current workaround

To work around this, I wrote a custom extractor that turns an HttpErrorResponse into a custom Error subclass, e.g. HttpErrorResponseError. That class contains:

  • endpoint URL
  • status code
  • the raw payload from HttpErrorResponse.error

So instead of sending just the simple string, I send this custom error object to Sentry. I then:

  • add the structured server payload as part of the error (message/extra), and
  • tune the fingerprint logic to group events based on properties of this custom error (for example, using the HTTP method + path + status).

This gives me usable grouping and much richer event data, but it feels quite hacky and app-specific.

Happy to share more details if that helps.

klyakh avatar Nov 29 '25 14:11 klyakh

@klyakh Thanks a lot of the detailed explanation, It would be great if you can share more details about the fingerprinting and enriched payload.

I think it may not be as app-specific as you think. Grouping by URL seems reasonable to me as a default but if you can share details, I can discuss them with the team and see if you (or we) could PR this.

logaretm avatar Dec 01 '25 09:12 logaretm

Thank you for the great response! Just a couple of thoughts from my end:

Grouping then happens by the stripped exception value. After stripping, this becomes something like: Http failure response for Unauthorized

Could you link to such an event in Sentry? This makes it a bit easier for us to understand which grouping rules were applied.

Grouping by URL seems reasonable to me as a default

Agreed, as long as we don't have a client-side stack trace. I need to do some more investigative work to see if that's the case. My thinking is that there's likely a different stack trace/call stack for different endpoints, so ideally the errors shouldn't be grouped together in the first place. If we don't have a stack trace, falling back to the URL (and status code?) sounds reasonable to me on first glance. We could adjust the default fingerprint for HttpResponseErrors from within the SDK.

Lms24 avatar Dec 01 '25 10:12 Lms24