|
1 | 1 | // TODO (v8): This import can be removed once we only support Node with global URL
|
2 | 2 | import { URL } from 'url';
|
3 |
| -import { getCurrentScope } from '@sentry/core'; |
4 |
| -import type { Contexts, Event, EventHint, Integration } from '@sentry/types'; |
| 3 | +import { convertIntegrationFnToClass, getCurrentScope } from '@sentry/core'; |
| 4 | +import type { Contexts, Event, EventHint, IntegrationFn } from '@sentry/types'; |
5 | 5 | import { dynamicRequire, logger } from '@sentry/utils';
|
6 | 6 | import type { Worker, WorkerOptions } from 'worker_threads';
|
7 | 7 | import type { NodeClient } from '../../client';
|
@@ -50,108 +50,106 @@ interface InspectorApi {
|
50 | 50 | url: () => string | undefined;
|
51 | 51 | }
|
52 | 52 |
|
| 53 | +const INTEGRATION_NAME = 'Anr'; |
| 54 | + |
| 55 | +const anrIntegration = ((options: Partial<Options> = {}) => { |
| 56 | + return { |
| 57 | + name: INTEGRATION_NAME, |
| 58 | + setup(client: NodeClient) { |
| 59 | + if (NODE_VERSION.major < 16) { |
| 60 | + throw new Error('ANR detection requires Node 16 or later'); |
| 61 | + } |
| 62 | + |
| 63 | + // setImmediate is used to ensure that all other integrations have been setup |
| 64 | + setImmediate(() => _startWorker(client, options)); |
| 65 | + }, |
| 66 | + }; |
| 67 | +}) satisfies IntegrationFn; |
| 68 | + |
53 | 69 | /**
|
54 | 70 | * Starts a thread to detect App Not Responding (ANR) events
|
55 | 71 | */
|
56 |
| -export class Anr implements Integration { |
57 |
| - public name: string = 'Anr'; |
| 72 | +// eslint-disable-next-line deprecation/deprecation |
| 73 | +export const Anr = convertIntegrationFnToClass(INTEGRATION_NAME, anrIntegration); |
58 | 74 |
|
59 |
| - public constructor(private readonly _options: Partial<Options> = {}) {} |
| 75 | +/** |
| 76 | + * Starts the ANR worker thread |
| 77 | + */ |
| 78 | +async function _startWorker(client: NodeClient, _options: Partial<Options>): Promise<void> { |
| 79 | + const contexts = await getContexts(client); |
| 80 | + const dsn = client.getDsn(); |
60 | 81 |
|
61 |
| - /** @inheritdoc */ |
62 |
| - public setupOnce(): void { |
63 |
| - // Do nothing |
| 82 | + if (!dsn) { |
| 83 | + return; |
64 | 84 | }
|
65 | 85 |
|
66 |
| - /** @inheritdoc */ |
67 |
| - public setup(client: NodeClient): void { |
68 |
| - if (NODE_VERSION.major < 16) { |
69 |
| - throw new Error('ANR detection requires Node 16 or later'); |
70 |
| - } |
| 86 | + // These will not be accurate if sent later from the worker thread |
| 87 | + delete contexts.app?.app_memory; |
| 88 | + delete contexts.device?.free_memory; |
71 | 89 |
|
72 |
| - // setImmediate is used to ensure that all other integrations have been setup |
73 |
| - setImmediate(() => this._startWorker(client)); |
74 |
| - } |
| 90 | + const initOptions = client.getOptions(); |
75 | 91 |
|
76 |
| - /** |
77 |
| - * Starts the ANR worker thread |
78 |
| - */ |
79 |
| - private async _startWorker(client: NodeClient): Promise<void> { |
80 |
| - const contexts = await getContexts(client); |
81 |
| - const dsn = client.getDsn(); |
| 92 | + const sdkMetadata = client.getSdkMetadata() || {}; |
| 93 | + if (sdkMetadata.sdk) { |
| 94 | + sdkMetadata.sdk.integrations = initOptions.integrations.map(i => i.name); |
| 95 | + } |
82 | 96 |
|
83 |
| - if (!dsn) { |
84 |
| - return; |
| 97 | + const options: WorkerStartData = { |
| 98 | + debug: logger.isEnabled(), |
| 99 | + dsn, |
| 100 | + environment: initOptions.environment || 'production', |
| 101 | + release: initOptions.release, |
| 102 | + dist: initOptions.dist, |
| 103 | + sdkMetadata, |
| 104 | + pollInterval: _options.pollInterval || DEFAULT_INTERVAL, |
| 105 | + anrThreshold: _options.anrThreshold || DEFAULT_HANG_THRESHOLD, |
| 106 | + captureStackTrace: !!_options.captureStackTrace, |
| 107 | + contexts, |
| 108 | + }; |
| 109 | + |
| 110 | + if (options.captureStackTrace) { |
| 111 | + // eslint-disable-next-line @typescript-eslint/no-var-requires |
| 112 | + const inspector: InspectorApi = require('inspector'); |
| 113 | + if (!inspector.url()) { |
| 114 | + inspector.open(0); |
85 | 115 | }
|
| 116 | + } |
86 | 117 |
|
87 |
| - // These will not be accurate if sent later from the worker thread |
88 |
| - delete contexts.app?.app_memory; |
89 |
| - delete contexts.device?.free_memory; |
90 |
| - |
91 |
| - const initOptions = client.getOptions(); |
92 |
| - |
93 |
| - const sdkMetadata = client.getSdkMetadata() || {}; |
94 |
| - if (sdkMetadata.sdk) { |
95 |
| - sdkMetadata.sdk.integrations = initOptions.integrations.map(i => i.name); |
| 118 | + const { Worker } = getWorkerThreads(); |
| 119 | + |
| 120 | + const worker = new Worker(new URL(`data:application/javascript;base64,${base64WorkerScript}`), { |
| 121 | + workerData: options, |
| 122 | + }); |
| 123 | + // Ensure this thread can't block app exit |
| 124 | + worker.unref(); |
| 125 | + |
| 126 | + const timer = setInterval(() => { |
| 127 | + try { |
| 128 | + const currentSession = getCurrentScope().getSession(); |
| 129 | + // We need to copy the session object and remove the toJSON method so it can be sent to the worker |
| 130 | + // serialized without making it a SerializedSession |
| 131 | + const session = currentSession ? { ...currentSession, toJSON: undefined } : undefined; |
| 132 | + // message the worker to tell it the main event loop is still running |
| 133 | + worker.postMessage({ session }); |
| 134 | + } catch (_) { |
| 135 | + // |
96 | 136 | }
|
| 137 | + }, options.pollInterval); |
97 | 138 |
|
98 |
| - const options: WorkerStartData = { |
99 |
| - debug: logger.isEnabled(), |
100 |
| - dsn, |
101 |
| - environment: initOptions.environment || 'production', |
102 |
| - release: initOptions.release, |
103 |
| - dist: initOptions.dist, |
104 |
| - sdkMetadata, |
105 |
| - pollInterval: this._options.pollInterval || DEFAULT_INTERVAL, |
106 |
| - anrThreshold: this._options.anrThreshold || DEFAULT_HANG_THRESHOLD, |
107 |
| - captureStackTrace: !!this._options.captureStackTrace, |
108 |
| - contexts, |
109 |
| - }; |
110 |
| - |
111 |
| - if (options.captureStackTrace) { |
112 |
| - // eslint-disable-next-line @typescript-eslint/no-var-requires |
113 |
| - const inspector: InspectorApi = require('inspector'); |
114 |
| - if (!inspector.url()) { |
115 |
| - inspector.open(0); |
116 |
| - } |
| 139 | + worker.on('message', (msg: string) => { |
| 140 | + if (msg === 'session-ended') { |
| 141 | + log('ANR event sent from ANR worker. Clearing session in this thread.'); |
| 142 | + getCurrentScope().setSession(undefined); |
117 | 143 | }
|
| 144 | + }); |
118 | 145 |
|
119 |
| - const { Worker } = getWorkerThreads(); |
120 |
| - |
121 |
| - const worker = new Worker(new URL(`data:application/javascript;base64,${base64WorkerScript}`), { |
122 |
| - workerData: options, |
123 |
| - }); |
124 |
| - // Ensure this thread can't block app exit |
125 |
| - worker.unref(); |
126 |
| - |
127 |
| - const timer = setInterval(() => { |
128 |
| - try { |
129 |
| - const currentSession = getCurrentScope().getSession(); |
130 |
| - // We need to copy the session object and remove the toJSON method so it can be sent to the worker |
131 |
| - // serialized without making it a SerializedSession |
132 |
| - const session = currentSession ? { ...currentSession, toJSON: undefined } : undefined; |
133 |
| - // message the worker to tell it the main event loop is still running |
134 |
| - worker.postMessage({ session }); |
135 |
| - } catch (_) { |
136 |
| - // |
137 |
| - } |
138 |
| - }, options.pollInterval); |
| 146 | + worker.once('error', (err: Error) => { |
| 147 | + clearInterval(timer); |
| 148 | + log('ANR worker error', err); |
| 149 | + }); |
139 | 150 |
|
140 |
| - worker.on('message', (msg: string) => { |
141 |
| - if (msg === 'session-ended') { |
142 |
| - log('ANR event sent from ANR worker. Clearing session in this thread.'); |
143 |
| - getCurrentScope().setSession(undefined); |
144 |
| - } |
145 |
| - }); |
146 |
| - |
147 |
| - worker.once('error', (err: Error) => { |
148 |
| - clearInterval(timer); |
149 |
| - log('ANR worker error', err); |
150 |
| - }); |
151 |
| - |
152 |
| - worker.once('exit', (code: number) => { |
153 |
| - clearInterval(timer); |
154 |
| - log('ANR worker exit', code); |
155 |
| - }); |
156 |
| - } |
| 151 | + worker.once('exit', (code: number) => { |
| 152 | + clearInterval(timer); |
| 153 | + log('ANR worker exit', code); |
| 154 | + }); |
157 | 155 | }
|
0 commit comments