bifocal/node_modules/@sentry/node/esm/integrations/anr/index.js

225 lines
6.4 KiB
JavaScript

import { _optionalChain, _optionalChainDelete } from '@sentry/utils';
import { URL } from 'url';
import { defineIntegration, convertIntegrationFnToClass, getGlobalScope, mergeScopeData, getIsolationScope, getCurrentScope } from '@sentry/core';
import { logger, GLOBAL_OBJ, dynamicRequire } from '@sentry/utils';
import { NODE_VERSION } from '../../nodeVersion.js';
import { base64WorkerScript } from './worker-script.js';
const DEFAULT_INTERVAL = 50;
const DEFAULT_HANG_THRESHOLD = 5000;
function log(message, ...args) {
logger.log(`[ANR] ${message}`, ...args);
}
function globalWithScopeFetchFn() {
return GLOBAL_OBJ;
}
/** Fetches merged scope data */
function getScopeData() {
const scope = getGlobalScope().getScopeData();
mergeScopeData(scope, getIsolationScope().getScopeData());
mergeScopeData(scope, getCurrentScope().getScopeData());
// We remove attachments because they likely won't serialize well as json
scope.attachments = [];
// We can't serialize event processor functions
scope.eventProcessors = [];
return scope;
}
/**
* We need to use dynamicRequire because worker_threads is not available in node < v12 and webpack error will when
* targeting those versions
*/
function getWorkerThreads() {
return dynamicRequire(module, 'worker_threads');
}
/**
* Gets contexts by calling all event processors. This relies on being called after all integrations are setup
*/
async function getContexts(client) {
let event = { message: 'ANR' };
const eventHint = {};
for (const processor of client.getEventProcessors()) {
if (event === null) break;
event = await processor(event, eventHint);
}
return _optionalChain([event, 'optionalAccess', _2 => _2.contexts]) || {};
}
const INTEGRATION_NAME = 'Anr';
const _anrIntegration = ((options = {}) => {
if (NODE_VERSION.major < 16 || (NODE_VERSION.major === 16 && NODE_VERSION.minor < 17)) {
throw new Error('ANR detection requires Node 16.17.0 or later');
}
let worker;
let client;
// Hookup the scope fetch function to the global object so that it can be called from the worker thread via the
// debugger when it pauses
const gbl = globalWithScopeFetchFn();
gbl.__SENTRY_GET_SCOPES__ = getScopeData;
return {
name: INTEGRATION_NAME,
// TODO v8: Remove this
setupOnce() {}, // eslint-disable-line @typescript-eslint/no-empty-function
startWorker: () => {
if (worker) {
return;
}
if (client) {
worker = _startWorker(client, options);
}
},
stopWorker: () => {
if (worker) {
// eslint-disable-next-line @typescript-eslint/no-floating-promises
worker.then(stop => {
stop();
worker = undefined;
});
}
},
setup(initClient) {
client = initClient;
// setImmediate is used to ensure that all other integrations have had their setup called first.
// This allows us to call into all integrations to fetch the full context
setImmediate(() => this.startWorker());
},
} ;
}) ;
const anrIntegration = defineIntegration(_anrIntegration) ;
/**
* Starts a thread to detect App Not Responding (ANR) events
*
* ANR detection requires Node 16.17.0 or later
*
* @deprecated Use `anrIntegration()` instead.
*/
// eslint-disable-next-line deprecation/deprecation
const Anr = convertIntegrationFnToClass(INTEGRATION_NAME, anrIntegration)
;
// eslint-disable-next-line deprecation/deprecation
/**
* Starts the ANR worker thread
*/
async function _startWorker(
client,
integrationOptions,
) {
const dsn = client.getDsn();
if (!dsn) {
return () => {
//
};
}
const contexts = await getContexts(client);
// These will not be accurate if sent later from the worker thread
_optionalChainDelete([contexts, 'access', _3 => _3.app, 'optionalAccess', _4 => delete _4.app_memory]);
_optionalChainDelete([contexts, 'access', _5 => _5.device, 'optionalAccess', _6 => delete _6.free_memory]);
const initOptions = client.getOptions();
const sdkMetadata = client.getSdkMetadata() || {};
if (sdkMetadata.sdk) {
sdkMetadata.sdk.integrations = initOptions.integrations.map(i => i.name);
}
const options = {
debug: logger.isEnabled(),
dsn,
environment: initOptions.environment || 'production',
release: initOptions.release,
dist: initOptions.dist,
sdkMetadata,
appRootPath: integrationOptions.appRootPath,
pollInterval: integrationOptions.pollInterval || DEFAULT_INTERVAL,
anrThreshold: integrationOptions.anrThreshold || DEFAULT_HANG_THRESHOLD,
captureStackTrace: !!integrationOptions.captureStackTrace,
staticTags: integrationOptions.staticTags || {},
contexts,
};
if (options.captureStackTrace) {
// eslint-disable-next-line @typescript-eslint/no-var-requires
const inspector = require('inspector');
if (!inspector.url()) {
inspector.open(0);
}
}
const { Worker } = getWorkerThreads();
const worker = new Worker(new URL(`data:application/javascript;base64,${base64WorkerScript}`), {
workerData: options,
});
process.on('exit', () => {
// eslint-disable-next-line @typescript-eslint/no-floating-promises
worker.terminate();
});
const timer = setInterval(() => {
try {
const currentSession = getCurrentScope().getSession();
// We need to copy the session object and remove the toJSON method so it can be sent to the worker
// serialized without making it a SerializedSession
const session = currentSession ? { ...currentSession, toJSON: undefined } : undefined;
// message the worker to tell it the main event loop is still running
worker.postMessage({ session });
} catch (_) {
//
}
}, options.pollInterval);
// Timer should not block exit
timer.unref();
worker.on('message', (msg) => {
if (msg === 'session-ended') {
log('ANR event sent from ANR worker. Clearing session in this thread.');
getCurrentScope().setSession(undefined);
}
});
worker.once('error', (err) => {
clearInterval(timer);
log('ANR worker error', err);
});
worker.once('exit', (code) => {
clearInterval(timer);
log('ANR worker exit', code);
});
// Ensure this thread can't block app exit
worker.unref();
return () => {
// eslint-disable-next-line @typescript-eslint/no-floating-promises
worker.terminate();
clearInterval(timer);
};
}
export { Anr, anrIntegration };
//# sourceMappingURL=index.js.map