getsentry / sentry-javascript

Official Sentry SDKs for JavaScript
https://sentry.io
MIT License
8.01k stars 1.58k forks source link

Sentry/Next.js: tags are not added for exceptions logged in `app/error.tsx` #13944

Closed christianvuerings closed 1 month ago

christianvuerings commented 1 month ago

Is there an existing issue for this?

How do you use Sentry?

Sentry Saas (sentry.io)

Which SDK are you using?

@sentry/nextjs

SDK Version

8.32.0

Framework Version

Next.js 14.2.12

Link to Sentry event

https://cambly-inc.sentry.io/issues/5978793591/?project=234235&query=TEST_ERROR_DEFG_1234&referrer=issue-stream&statsPeriod=7d&stream_index=0

Reproduction Example/SDK Setup

import { type Options, type Integration } from "@sentry/types";

const sentryConfig: Options & { integrations: Integration[] } = {
  dsn: "https://9c3f154b2e064abbb12a3901d3c68cd6@o106476.ingest.sentry.io/234235",
  release: `cambly-frontend@development`,
  environment: "development",
  debug: true,
  allowUrls: [
    "webpack-internal://",
    "localhost:8080",
    "localhost:8084",
  ],
  integrations: [],
  beforeSend: (event) => {
    // For debugging
    console.log("///// event beforeSend", {
      tags: event.tags ? JSON.stringify(Object.keys(event.tags)) : null,
    });

    return event;
  },
  tracesSampler(): number {
    return process.env.NODE_ENV !== "production" ? 1 : 0.05;
  },
};

Steps to Reproduce

  1. Create a app/error.tsx file:
    
    "use client";

import { useEffect, type ReactElement } from "react"; import * as Sentry from "@sentry/nextjs";

export default function Error({ error, reset, }: { error: Error & { digest?: string }; reset: () => void; }): ReactElement { console.log("///// captured before", error.__sentry_captured__); useEffect(() => { Sentry.withScope((scope) => { scope.clear(); scope.setTag("cambly.captureLocationTemp", "nextjsPageErrorAppError"); Sentry.captureException(error); }); }, [error]);

return (

Something went wrong{" "}

); }


2. Create a page route `app/page.tsx`:
```js
"use client";

import { type ReactElement } from "react";
import { useSearchParams } from "next/navigation";

export default function Page(): ReactElement {

  const throwErrorIntentionally = useSearchParams().get("throwErrorIntentionally");
  if (throwErrorIntentionally === "true") {
    throw new Error("TEST_ERROR_DEFG_1234");
  }

  return (
   <h1>Test</h1>
  );
}
  1. Go to http://localhost:8080?throwErrorIntentionally=true

Expected Result

Error contains cambly.captureLocationTemp tag

Actual Result

Error does not contain cambly.captureLocationTemp

christianvuerings commented 1 month ago

Notes

Lms24 commented 1 month ago

Hey @christianvuerings thanks for writing in!

I think your suspicion that something else is capturing the error before it's caught by the error page is correct. We can probably find out what that is by looking at event.exception.values[0].mechanism. Would you mind logging this out in your beforeSend hook and reporting back?

Side-note: I see you'Re calling scope.clear() which is fairly destructive. Is there a specific reason for that? This pretty much wipes almost everything that the SDK currently stores on the scope for this request, including stuff like trace information. So if your error on the server is throwing an error in the browser, these won't be linked in the Sentry UI. Not the end of the world of course but just a heads-up :)

christianvuerings commented 1 month ago

I think your suspicion that something else is capturing the error before it's caught by the error page is correct. We can probably find out what that is by looking at event.exception.values[0].mechanism. Would you mind logging this out in your beforeSend hook and reporting back?

@Lms24 event.exception.values[0].mechanism always logs instrument which is the same value as what we see for the exception online: Image

Assuming this is Sentry auto instrumenting exceptions, should autoInstrumentAppDirectory set to false fix this? We tried that and also set autoInstrumentServerFunctions and autoInstrumentMiddleware to false without any luck. It always logs instrument and does not add the tag.

What we want to achieve is for exceptions that hit the error boundary to get a specific tag - e.g. cambly.captureLocation set to nextjsPageError but if Sentry already captures those exceptions, we're not sure how to do that.

Side-note: I see you'Re calling scope.clear() which is fairly destructive. Is there a specific reason for that? This pretty much wipes almost everything that the SDK currently stores on the scope for this request, including stuff like trace information. So if your error on the server is throwing an error in the browser, these won't be linked in the Sentry UI. Not the end of the world of course but just a heads-up :)

Thanks for pointing that out. It was just for debugging purposes to see if that would allow us to set the tag and not something we use in production.

Lms24 commented 1 month ago

@christianvuerings thanks for getting back to me! Given the mechanism is instrument and (after looking at the linked Sentry event) the instrumented function where this error was caught is the callback of addEventListener on an EventTarget object, it seems like this error is caught by the browserApiErrorsIntegration which patches a lot of callback handlers for different browser APIs.

Ideally, the error component would catch this first but it kinda makes sense to me that browser-internal API are getting triggered first. However, I'm by far not a NextJS expert. Therefore, I'll tag @lforst - are you aware of similar cases?

lforst commented 1 month ago

@christianvuerings can you try your scenario for a production build? Next.js in dev mode is a bit funky when it comes to errors, in a way that we cannot address.

I think if you do a prod build everything should work as expected. At least it did so in a quick repro I attempted.

christianvuerings commented 1 month ago

@lforst Using a prod build does add the tag correctly (repo repro)

Prod: https://cambly-inc.sentry.io/issues/5998667791/?project=4508137977610240 Image

Development: https://cambly-inc.sentry.io/issues/5998661679/?project=4508137977610240 Image

However, it still didn't work in our app. We found that the root cause was captureConsoleIntegration. Once we removed that integration, everything worked as expected

- Sentry.captureConsoleIntegration({
-   levels: ["error"],
- }),