{"version":3,"file":"onuncaughtexception.js","sources":["../../../src/integrations/onuncaughtexception.ts"],"sourcesContent":["import { captureException, convertIntegrationFnToClass, defineIntegration } from '@sentry/core';\nimport { getClient } from '@sentry/core';\nimport type { Integration, IntegrationClass, IntegrationFn } from '@sentry/types';\nimport { logger } from '@sentry/utils';\n\nimport type { NodeClient } from '../client';\nimport { DEBUG_BUILD } from '../debug-build';\nimport { logAndExitProcess } from './utils/errorhandling';\n\ntype OnFatalErrorHandler = (firstError: Error, secondError?: Error) => void;\n\ntype TaggedListener = NodeJS.UncaughtExceptionListener & {\n  tag?: string;\n};\n\n// CAREFUL: Please think twice before updating the way _options looks because the Next.js SDK depends on it in `index.server.ts`\ninterface OnUncaughtExceptionOptions {\n  // TODO(v8): Evaluate whether we should switch the default behaviour here.\n  // Also, we can evaluate using https://nodejs.org/api/process.html#event-uncaughtexceptionmonitor per default, and\n  // falling back to current behaviour when that's not available.\n  /**\n   * Controls if the SDK should register a handler to exit the process on uncaught errors:\n   * - `true`: The SDK will exit the process on all uncaught errors.\n   * - `false`: The SDK will only exit the process when there are no other `uncaughtException` handlers attached.\n   *\n   * Default: `true`\n   */\n  exitEvenIfOtherHandlersAreRegistered: boolean;\n\n  /**\n   * This is called when an uncaught error would cause the process to exit.\n   *\n   * @param firstError Uncaught error causing the process to exit\n   * @param secondError Will be set if the handler was called multiple times. This can happen either because\n   * `onFatalError` itself threw, or because an independent error happened somewhere else while `onFatalError`\n   * was running.\n   */\n  onFatalError?(this: void, firstError: Error, secondError?: Error): void;\n}\n\nconst INTEGRATION_NAME = 'OnUncaughtException';\n\nconst _onUncaughtExceptionIntegration = ((options: Partial<OnUncaughtExceptionOptions> = {}) => {\n  const _options = {\n    exitEvenIfOtherHandlersAreRegistered: true,\n    ...options,\n  };\n\n  return {\n    name: INTEGRATION_NAME,\n    // TODO v8: Remove this\n    setupOnce() {}, // eslint-disable-line @typescript-eslint/no-empty-function\n    setup(client: NodeClient) {\n      global.process.on('uncaughtException', makeErrorHandler(client, _options));\n    },\n  };\n}) satisfies IntegrationFn;\n\nexport const onUncaughtExceptionIntegration = defineIntegration(_onUncaughtExceptionIntegration);\n\n/**\n * Global Exception handler.\n * @deprecated Use `onUncaughtExceptionIntegration()` instead.\n */\n// eslint-disable-next-line deprecation/deprecation\nexport const OnUncaughtException = convertIntegrationFnToClass(\n  INTEGRATION_NAME,\n  onUncaughtExceptionIntegration,\n) as IntegrationClass<Integration & { setup: (client: NodeClient) => void }> & {\n  new (\n    options?: Partial<{\n      exitEvenIfOtherHandlersAreRegistered: boolean;\n      onFatalError?(this: void, firstError: Error, secondError?: Error): void;\n    }>,\n  ): Integration;\n};\n\n// eslint-disable-next-line deprecation/deprecation\nexport type OnUncaughtException = typeof OnUncaughtException;\n\ntype ErrorHandler = { _errorHandler: boolean } & ((error: Error) => void);\n\n/** Exported only for tests */\nexport function makeErrorHandler(client: NodeClient, options: OnUncaughtExceptionOptions): ErrorHandler {\n  const timeout = 2000;\n  let caughtFirstError: boolean = false;\n  let caughtSecondError: boolean = false;\n  let calledFatalError: boolean = false;\n  let firstError: Error;\n\n  const clientOptions = client.getOptions();\n\n  return Object.assign(\n    (error: Error): void => {\n      let onFatalError: OnFatalErrorHandler = logAndExitProcess;\n\n      if (options.onFatalError) {\n        onFatalError = options.onFatalError;\n      } else if (clientOptions.onFatalError) {\n        onFatalError = clientOptions.onFatalError as OnFatalErrorHandler;\n      }\n\n      // Attaching a listener to `uncaughtException` will prevent the node process from exiting. We generally do not\n      // want to alter this behaviour so we check for other listeners that users may have attached themselves and adjust\n      // exit behaviour of the SDK accordingly:\n      // - If other listeners are attached, do not exit.\n      // - If the only listener attached is ours, exit.\n      const userProvidedListenersCount = (\n        global.process.listeners('uncaughtException') as TaggedListener[]\n      ).reduce<number>((acc, listener) => {\n        if (\n          // There are 3 listeners we ignore:\n          listener.name === 'domainUncaughtExceptionClear' || // as soon as we're using domains this listener is attached by node itself\n          (listener.tag && listener.tag === 'sentry_tracingErrorCallback') || // the handler we register for tracing\n          (listener as ErrorHandler)._errorHandler // the handler we register in this integration\n        ) {\n          return acc;\n        } else {\n          return acc + 1;\n        }\n      }, 0);\n\n      const processWouldExit = userProvidedListenersCount === 0;\n      const shouldApplyFatalHandlingLogic = options.exitEvenIfOtherHandlersAreRegistered || processWouldExit;\n\n      if (!caughtFirstError) {\n        // this is the first uncaught error and the ultimate reason for shutting down\n        // we want to do absolutely everything possible to ensure it gets captured\n        // also we want to make sure we don't go recursion crazy if more errors happen after this one\n        firstError = error;\n        caughtFirstError = true;\n\n        if (getClient() === client) {\n          captureException(error, {\n            originalException: error,\n            captureContext: {\n              level: 'fatal',\n            },\n            mechanism: {\n              handled: false,\n              type: 'onuncaughtexception',\n            },\n          });\n        }\n\n        if (!calledFatalError && shouldApplyFatalHandlingLogic) {\n          calledFatalError = true;\n          onFatalError(error);\n        }\n      } else {\n        if (shouldApplyFatalHandlingLogic) {\n          if (calledFatalError) {\n            // we hit an error *after* calling onFatalError - pretty boned at this point, just shut it down\n            DEBUG_BUILD &&\n              logger.warn(\n                'uncaught exception after calling fatal error shutdown callback - this is bad! forcing shutdown',\n              );\n            logAndExitProcess(error);\n          } else if (!caughtSecondError) {\n            // two cases for how we can hit this branch:\n            //   - capturing of first error blew up and we just caught the exception from that\n            //     - quit trying to capture, proceed with shutdown\n            //   - a second independent error happened while waiting for first error to capture\n            //     - want to avoid causing premature shutdown before first error capture finishes\n            // it's hard to immediately tell case 1 from case 2 without doing some fancy/questionable domain stuff\n            // so let's instead just delay a bit before we proceed with our action here\n            // in case 1, we just wait a bit unnecessarily but ultimately do the same thing\n            // in case 2, the delay hopefully made us wait long enough for the capture to finish\n            // two potential nonideal outcomes:\n            //   nonideal case 1: capturing fails fast, we sit around for a few seconds unnecessarily before proceeding correctly by calling onFatalError\n            //   nonideal case 2: case 2 happens, 1st error is captured but slowly, timeout completes before capture and we treat second error as the sendErr of (nonexistent) failure from trying to capture first error\n            // note that after hitting this branch, we might catch more errors where (caughtSecondError && !calledFatalError)\n            //   we ignore them - they don't matter to us, we're just waiting for the second error timeout to finish\n            caughtSecondError = true;\n            setTimeout(() => {\n              if (!calledFatalError) {\n                // it was probably case 1, let's treat err as the sendErr and call onFatalError\n                calledFatalError = true;\n                onFatalError(firstError, error);\n              } else {\n                // it was probably case 2, our first error finished capturing while we waited, cool, do nothing\n              }\n            }, timeout); // capturing could take at least sendTimeout to fail, plus an arbitrary second for how long it takes to collect surrounding source etc\n          }\n        }\n      }\n    },\n    { _errorHandler: true },\n  );\n}\n"],"names":[],"mappings":"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"}