index.js 4.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171
  1. var {
  2. _optionalChain,
  3. _optionalChainDelete
  4. } = require('@sentry/utils');
  5. Object.defineProperty(exports, '__esModule', { value: true });
  6. const url = require('url');
  7. const core = require('@sentry/core');
  8. const utils = require('@sentry/utils');
  9. const nodeVersion = require('../../nodeVersion.js');
  10. const workerScript = require('./worker-script.js');
  11. const DEFAULT_INTERVAL = 50;
  12. const DEFAULT_HANG_THRESHOLD = 5000;
  13. function log(message, ...args) {
  14. utils.logger.log(`[ANR] ${message}`, ...args);
  15. }
  16. /**
  17. * We need to use dynamicRequire because worker_threads is not available in node < v12 and webpack error will when
  18. * targeting those versions
  19. */
  20. function getWorkerThreads() {
  21. return utils.dynamicRequire(module, 'worker_threads');
  22. }
  23. /**
  24. * Gets contexts by calling all event processors. This relies on being called after all integrations are setup
  25. */
  26. async function getContexts(client) {
  27. let event = { message: 'ANR' };
  28. const eventHint = {};
  29. for (const processor of client.getEventProcessors()) {
  30. if (event === null) break;
  31. event = await processor(event, eventHint);
  32. }
  33. return _optionalChain([event, 'optionalAccess', _2 => _2.contexts]) || {};
  34. }
  35. const INTEGRATION_NAME = 'Anr';
  36. const _anrIntegration = ((options = {}) => {
  37. return {
  38. name: INTEGRATION_NAME,
  39. // TODO v8: Remove this
  40. setupOnce() {}, // eslint-disable-line @typescript-eslint/no-empty-function
  41. setup(client) {
  42. if (nodeVersion.NODE_VERSION.major < 16 || (nodeVersion.NODE_VERSION.major === 16 && nodeVersion.NODE_VERSION.minor < 17)) {
  43. throw new Error('ANR detection requires Node 16.17.0 or later');
  44. }
  45. // setImmediate is used to ensure that all other integrations have been setup
  46. setImmediate(() => _startWorker(client, options));
  47. },
  48. };
  49. }) ;
  50. const anrIntegration = core.defineIntegration(_anrIntegration);
  51. /**
  52. * Starts a thread to detect App Not Responding (ANR) events
  53. *
  54. * ANR detection requires Node 16.17.0 or later
  55. *
  56. * @deprecated Use `anrIntegration()` instead.
  57. */
  58. // eslint-disable-next-line deprecation/deprecation
  59. const Anr = core.convertIntegrationFnToClass(INTEGRATION_NAME, anrIntegration)
  60. ;
  61. // eslint-disable-next-line deprecation/deprecation
  62. /**
  63. * Starts the ANR worker thread
  64. */
  65. async function _startWorker(client, _options) {
  66. const contexts = await getContexts(client);
  67. const dsn = client.getDsn();
  68. if (!dsn) {
  69. return;
  70. }
  71. // These will not be accurate if sent later from the worker thread
  72. _optionalChainDelete([contexts, 'access', _3 => _3.app, 'optionalAccess', _4 => delete _4.app_memory]);
  73. _optionalChainDelete([contexts, 'access', _5 => _5.device, 'optionalAccess', _6 => delete _6.free_memory]);
  74. const initOptions = client.getOptions();
  75. const sdkMetadata = client.getSdkMetadata() || {};
  76. if (sdkMetadata.sdk) {
  77. sdkMetadata.sdk.integrations = initOptions.integrations.map(i => i.name);
  78. }
  79. const options = {
  80. debug: utils.logger.isEnabled(),
  81. dsn,
  82. environment: initOptions.environment || 'production',
  83. release: initOptions.release,
  84. dist: initOptions.dist,
  85. sdkMetadata,
  86. appRootPath: _options.appRootPath,
  87. pollInterval: _options.pollInterval || DEFAULT_INTERVAL,
  88. anrThreshold: _options.anrThreshold || DEFAULT_HANG_THRESHOLD,
  89. captureStackTrace: !!_options.captureStackTrace,
  90. staticTags: _options.staticTags || {},
  91. contexts,
  92. };
  93. if (options.captureStackTrace) {
  94. // eslint-disable-next-line @typescript-eslint/no-var-requires
  95. const inspector = require('inspector');
  96. if (!inspector.url()) {
  97. inspector.open(0);
  98. }
  99. }
  100. const { Worker } = getWorkerThreads();
  101. const worker = new Worker(new url.URL(`data:application/javascript;base64,${workerScript.base64WorkerScript}`), {
  102. workerData: options,
  103. });
  104. process.on('exit', () => {
  105. worker.terminate();
  106. });
  107. const timer = setInterval(() => {
  108. try {
  109. const currentSession = core.getCurrentScope().getSession();
  110. // We need to copy the session object and remove the toJSON method so it can be sent to the worker
  111. // serialized without making it a SerializedSession
  112. const session = currentSession ? { ...currentSession, toJSON: undefined } : undefined;
  113. // message the worker to tell it the main event loop is still running
  114. worker.postMessage({ session });
  115. } catch (_) {
  116. //
  117. }
  118. }, options.pollInterval);
  119. // Timer should not block exit
  120. timer.unref();
  121. worker.on('message', (msg) => {
  122. if (msg === 'session-ended') {
  123. log('ANR event sent from ANR worker. Clearing session in this thread.');
  124. core.getCurrentScope().setSession(undefined);
  125. }
  126. });
  127. worker.once('error', (err) => {
  128. clearInterval(timer);
  129. log('ANR worker error', err);
  130. });
  131. worker.once('exit', (code) => {
  132. clearInterval(timer);
  133. log('ANR worker exit', code);
  134. });
  135. // Ensure this thread can't block app exit
  136. worker.unref();
  137. }
  138. exports.Anr = Anr;
  139. exports.anrIntegration = anrIntegration;
  140. //# sourceMappingURL=index.js.map