|
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