onuncaughtexception.js 6.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152
  1. Object.defineProperty(exports, '__esModule', { value: true });
  2. const core = require('@sentry/core');
  3. const utils = require('@sentry/utils');
  4. const debugBuild = require('../debug-build.js');
  5. const errorhandling = require('./utils/errorhandling.js');
  6. const INTEGRATION_NAME = 'OnUncaughtException';
  7. const _onUncaughtExceptionIntegration = ((options = {}) => {
  8. const _options = {
  9. exitEvenIfOtherHandlersAreRegistered: true,
  10. ...options,
  11. };
  12. return {
  13. name: INTEGRATION_NAME,
  14. // TODO v8: Remove this
  15. setupOnce() {}, // eslint-disable-line @typescript-eslint/no-empty-function
  16. setup(client) {
  17. global.process.on('uncaughtException', makeErrorHandler(client, _options));
  18. },
  19. };
  20. }) ;
  21. const onUncaughtExceptionIntegration = core.defineIntegration(_onUncaughtExceptionIntegration);
  22. /**
  23. * Global Exception handler.
  24. * @deprecated Use `onUncaughtExceptionIntegration()` instead.
  25. */
  26. // eslint-disable-next-line deprecation/deprecation
  27. const OnUncaughtException = core.convertIntegrationFnToClass(
  28. INTEGRATION_NAME,
  29. onUncaughtExceptionIntegration,
  30. )
  31. ;
  32. // eslint-disable-next-line deprecation/deprecation
  33. /** Exported only for tests */
  34. function makeErrorHandler(client, options) {
  35. const timeout = 2000;
  36. let caughtFirstError = false;
  37. let caughtSecondError = false;
  38. let calledFatalError = false;
  39. let firstError;
  40. const clientOptions = client.getOptions();
  41. return Object.assign(
  42. (error) => {
  43. let onFatalError = errorhandling.logAndExitProcess;
  44. if (options.onFatalError) {
  45. onFatalError = options.onFatalError;
  46. } else if (clientOptions.onFatalError) {
  47. onFatalError = clientOptions.onFatalError ;
  48. }
  49. // Attaching a listener to `uncaughtException` will prevent the node process from exiting. We generally do not
  50. // want to alter this behaviour so we check for other listeners that users may have attached themselves and adjust
  51. // exit behaviour of the SDK accordingly:
  52. // - If other listeners are attached, do not exit.
  53. // - If the only listener attached is ours, exit.
  54. const userProvidedListenersCount = (
  55. global.process.listeners('uncaughtException')
  56. ).reduce((acc, listener) => {
  57. if (
  58. // There are 3 listeners we ignore:
  59. listener.name === 'domainUncaughtExceptionClear' || // as soon as we're using domains this listener is attached by node itself
  60. (listener.tag && listener.tag === 'sentry_tracingErrorCallback') || // the handler we register for tracing
  61. (listener )._errorHandler // the handler we register in this integration
  62. ) {
  63. return acc;
  64. } else {
  65. return acc + 1;
  66. }
  67. }, 0);
  68. const processWouldExit = userProvidedListenersCount === 0;
  69. const shouldApplyFatalHandlingLogic = options.exitEvenIfOtherHandlersAreRegistered || processWouldExit;
  70. if (!caughtFirstError) {
  71. // this is the first uncaught error and the ultimate reason for shutting down
  72. // we want to do absolutely everything possible to ensure it gets captured
  73. // also we want to make sure we don't go recursion crazy if more errors happen after this one
  74. firstError = error;
  75. caughtFirstError = true;
  76. if (core.getClient() === client) {
  77. core.captureException(error, {
  78. originalException: error,
  79. captureContext: {
  80. level: 'fatal',
  81. },
  82. mechanism: {
  83. handled: false,
  84. type: 'onuncaughtexception',
  85. },
  86. });
  87. }
  88. if (!calledFatalError && shouldApplyFatalHandlingLogic) {
  89. calledFatalError = true;
  90. onFatalError(error);
  91. }
  92. } else {
  93. if (shouldApplyFatalHandlingLogic) {
  94. if (calledFatalError) {
  95. // we hit an error *after* calling onFatalError - pretty boned at this point, just shut it down
  96. debugBuild.DEBUG_BUILD &&
  97. utils.logger.warn(
  98. 'uncaught exception after calling fatal error shutdown callback - this is bad! forcing shutdown',
  99. );
  100. errorhandling.logAndExitProcess(error);
  101. } else if (!caughtSecondError) {
  102. // two cases for how we can hit this branch:
  103. // - capturing of first error blew up and we just caught the exception from that
  104. // - quit trying to capture, proceed with shutdown
  105. // - a second independent error happened while waiting for first error to capture
  106. // - want to avoid causing premature shutdown before first error capture finishes
  107. // it's hard to immediately tell case 1 from case 2 without doing some fancy/questionable domain stuff
  108. // so let's instead just delay a bit before we proceed with our action here
  109. // in case 1, we just wait a bit unnecessarily but ultimately do the same thing
  110. // in case 2, the delay hopefully made us wait long enough for the capture to finish
  111. // two potential nonideal outcomes:
  112. // nonideal case 1: capturing fails fast, we sit around for a few seconds unnecessarily before proceeding correctly by calling onFatalError
  113. // 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
  114. // note that after hitting this branch, we might catch more errors where (caughtSecondError && !calledFatalError)
  115. // we ignore them - they don't matter to us, we're just waiting for the second error timeout to finish
  116. caughtSecondError = true;
  117. setTimeout(() => {
  118. if (!calledFatalError) {
  119. // it was probably case 1, let's treat err as the sendErr and call onFatalError
  120. calledFatalError = true;
  121. onFatalError(firstError, error);
  122. }
  123. }, timeout); // capturing could take at least sendTimeout to fail, plus an arbitrary second for how long it takes to collect surrounding source etc
  124. }
  125. }
  126. }
  127. },
  128. { _errorHandler: true },
  129. );
  130. }
  131. exports.OnUncaughtException = OnUncaughtException;
  132. exports.makeErrorHandler = makeErrorHandler;
  133. exports.onUncaughtExceptionIntegration = onUncaughtExceptionIntegration;
  134. //# sourceMappingURL=onuncaughtexception.js.map