123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148 |
- import { defineIntegration, convertIntegrationFnToClass, getClient, captureException } from '@sentry/core';
- import { logger } from '@sentry/utils';
- import { DEBUG_BUILD } from '../debug-build.js';
- import { logAndExitProcess } from './utils/errorhandling.js';
- const INTEGRATION_NAME = 'OnUncaughtException';
- const _onUncaughtExceptionIntegration = ((options = {}) => {
- const _options = {
- exitEvenIfOtherHandlersAreRegistered: true,
- ...options,
- };
- return {
- name: INTEGRATION_NAME,
- // TODO v8: Remove this
- setupOnce() {}, // eslint-disable-line @typescript-eslint/no-empty-function
- setup(client) {
- global.process.on('uncaughtException', makeErrorHandler(client, _options));
- },
- };
- }) ;
- const onUncaughtExceptionIntegration = defineIntegration(_onUncaughtExceptionIntegration);
- /**
- * Global Exception handler.
- * @deprecated Use `onUncaughtExceptionIntegration()` instead.
- */
- // eslint-disable-next-line deprecation/deprecation
- const OnUncaughtException = convertIntegrationFnToClass(
- INTEGRATION_NAME,
- onUncaughtExceptionIntegration,
- )
- ;
- // eslint-disable-next-line deprecation/deprecation
- /** Exported only for tests */
- function makeErrorHandler(client, options) {
- const timeout = 2000;
- let caughtFirstError = false;
- let caughtSecondError = false;
- let calledFatalError = false;
- let firstError;
- const clientOptions = client.getOptions();
- return Object.assign(
- (error) => {
- let onFatalError = logAndExitProcess;
- if (options.onFatalError) {
- onFatalError = options.onFatalError;
- } else if (clientOptions.onFatalError) {
- onFatalError = clientOptions.onFatalError ;
- }
- // Attaching a listener to `uncaughtException` will prevent the node process from exiting. We generally do not
- // want to alter this behaviour so we check for other listeners that users may have attached themselves and adjust
- // exit behaviour of the SDK accordingly:
- // - If other listeners are attached, do not exit.
- // - If the only listener attached is ours, exit.
- const userProvidedListenersCount = (
- global.process.listeners('uncaughtException')
- ).reduce((acc, listener) => {
- if (
- // There are 3 listeners we ignore:
- listener.name === 'domainUncaughtExceptionClear' || // as soon as we're using domains this listener is attached by node itself
- (listener.tag && listener.tag === 'sentry_tracingErrorCallback') || // the handler we register for tracing
- (listener )._errorHandler // the handler we register in this integration
- ) {
- return acc;
- } else {
- return acc + 1;
- }
- }, 0);
- const processWouldExit = userProvidedListenersCount === 0;
- const shouldApplyFatalHandlingLogic = options.exitEvenIfOtherHandlersAreRegistered || processWouldExit;
- if (!caughtFirstError) {
- // this is the first uncaught error and the ultimate reason for shutting down
- // we want to do absolutely everything possible to ensure it gets captured
- // also we want to make sure we don't go recursion crazy if more errors happen after this one
- firstError = error;
- caughtFirstError = true;
- if (getClient() === client) {
- captureException(error, {
- originalException: error,
- captureContext: {
- level: 'fatal',
- },
- mechanism: {
- handled: false,
- type: 'onuncaughtexception',
- },
- });
- }
- if (!calledFatalError && shouldApplyFatalHandlingLogic) {
- calledFatalError = true;
- onFatalError(error);
- }
- } else {
- if (shouldApplyFatalHandlingLogic) {
- if (calledFatalError) {
- // we hit an error *after* calling onFatalError - pretty boned at this point, just shut it down
- DEBUG_BUILD &&
- logger.warn(
- 'uncaught exception after calling fatal error shutdown callback - this is bad! forcing shutdown',
- );
- logAndExitProcess(error);
- } else if (!caughtSecondError) {
- // two cases for how we can hit this branch:
- // - capturing of first error blew up and we just caught the exception from that
- // - quit trying to capture, proceed with shutdown
- // - a second independent error happened while waiting for first error to capture
- // - want to avoid causing premature shutdown before first error capture finishes
- // it's hard to immediately tell case 1 from case 2 without doing some fancy/questionable domain stuff
- // so let's instead just delay a bit before we proceed with our action here
- // in case 1, we just wait a bit unnecessarily but ultimately do the same thing
- // in case 2, the delay hopefully made us wait long enough for the capture to finish
- // two potential nonideal outcomes:
- // nonideal case 1: capturing fails fast, we sit around for a few seconds unnecessarily before proceeding correctly by calling onFatalError
- // 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
- // note that after hitting this branch, we might catch more errors where (caughtSecondError && !calledFatalError)
- // we ignore them - they don't matter to us, we're just waiting for the second error timeout to finish
- caughtSecondError = true;
- setTimeout(() => {
- if (!calledFatalError) {
- // it was probably case 1, let's treat err as the sendErr and call onFatalError
- calledFatalError = true;
- onFatalError(firstError, error);
- }
- }, timeout); // capturing could take at least sendTimeout to fail, plus an arbitrary second for how long it takes to collect surrounding source etc
- }
- }
- }
- },
- { _errorHandler: true },
- );
- }
- export { OnUncaughtException, makeErrorHandler, onUncaughtExceptionIntegration };
- //# sourceMappingURL=onuncaughtexception.js.map
|