@sentry/node-native 10.53.1 → 10.55.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -7,112 +7,70 @@ const nodeNativeStacktrace = require('@sentry-internal/node-native-stacktrace');
7
7
  const common = require('./common.js');
8
8
 
9
9
  var _documentCurrentScript = typeof document !== 'undefined' ? document.currentScript : null;
10
- const INTEGRATION_NAME = 'ThreadBlocked';
11
- const DEFAULT_THRESHOLD_MS = 1000;
12
-
10
+ const INTEGRATION_NAME = "ThreadBlocked";
11
+ const DEFAULT_THRESHOLD_MS = 1e3;
13
12
  function log(message, ...args) {
14
13
  core.debug.log(`[Sentry Event Loop Blocked] ${message}`, ...args);
15
14
  }
16
-
17
- /**
18
- * Gets contexts by calling all event processors. This shouldn't be called until all integrations are setup
19
- */
20
15
  async function getContexts(client) {
21
16
  let event = { message: INTEGRATION_NAME };
22
17
  const eventHint = {};
23
-
24
18
  for (const processor of client.getEventProcessors()) {
25
19
  if (event === null) break;
26
20
  event = await processor(event, eventHint);
27
21
  }
28
-
29
22
  return event?.contexts || {};
30
23
  }
31
-
32
24
  function getLocalScopeData() {
33
25
  const globalScope = core.getGlobalScope().getScopeData();
34
26
  const currentScope = core.getCurrentScope().getScopeData();
35
27
  core.mergeScopeData(globalScope, currentScope);
36
28
  return globalScope;
37
29
  }
38
-
39
30
  function poll(enabled, clientOptions) {
40
31
  try {
41
32
  const currentSession = core.getIsolationScope().getSession();
42
- // We need to copy the session object and remove the toJSON method so it can be sent to the worker
43
- // serialized without making it a SerializedSession
44
- const session = currentSession ? { ...currentSession, toJSON: undefined } : undefined;
33
+ const session = currentSession ? { ...currentSession, toJSON: void 0 } : void 0;
45
34
  const scope = getLocalScopeData();
46
- // message the worker to tell it the main event loop is still running
47
35
  nodeNativeStacktrace.threadPoll(enabled, { session, scope, debugImages: core.getFilenameToDebugIdMap(clientOptions.stackParser) });
48
36
  } catch {
49
- // we ignore all errors
50
37
  }
51
38
  }
52
-
53
- /**
54
- * Starts polling
55
- */
56
- function startPolling(
57
- client,
58
- integrationOptions,
59
- ) {
39
+ function startPolling(client, integrationOptions) {
60
40
  if (client.asyncLocalStorageLookup) {
61
41
  const { asyncLocalStorage, contextSymbol } = client.asyncLocalStorageLookup;
62
- nodeNativeStacktrace.registerThread({ asyncLocalStorage, stateLookup: ['_currentContext', contextSymbol] });
42
+ nodeNativeStacktrace.registerThread({ asyncLocalStorage, stateLookup: ["_currentContext", contextSymbol] });
63
43
  } else {
64
44
  nodeNativeStacktrace.registerThread();
65
45
  }
66
-
67
46
  let enabled = true;
68
-
69
47
  const initOptions = client.getOptions();
70
48
  const pollInterval = (integrationOptions.threshold || DEFAULT_THRESHOLD_MS) / common.POLL_RATIO;
71
-
72
- // unref so timer does not block exit
73
49
  setInterval(() => poll(enabled, initOptions), pollInterval).unref();
74
-
75
50
  return {
76
51
  start: () => {
77
52
  enabled = true;
78
53
  },
79
54
  stop: () => {
80
55
  enabled = false;
81
- // poll immediately because the timer above might not get a chance to run
82
- // before the event loop gets blocked
83
56
  poll(enabled, initOptions);
84
- },
57
+ }
85
58
  };
86
59
  }
87
-
88
- /**
89
- * Starts the worker thread that will monitor the other threads.
90
- *
91
- * This function is only called in the main thread.
92
- */
93
- async function startWorker(
94
- dsn,
95
- client,
96
- integrationOptions,
97
- ) {
60
+ async function startWorker(dsn, client, integrationOptions) {
98
61
  const contexts = await getContexts(client);
99
-
100
- // These will not be accurate if sent later from the worker thread
101
62
  delete contexts.app?.app_memory;
102
63
  delete contexts.device?.free_memory;
103
-
104
64
  const initOptions = client.getOptions();
105
-
106
65
  const sdkMetadata = client.getSdkMetadata() || {};
107
66
  if (sdkMetadata.sdk) {
108
- sdkMetadata.sdk.integrations = initOptions.integrations.map(i => i.name);
67
+ sdkMetadata.sdk.integrations = initOptions.integrations.map((i) => i.name);
109
68
  }
110
-
111
69
  const options = {
112
70
  debug: core.debug.isEnabled(),
113
71
  dsn,
114
72
  tunnel: initOptions.tunnel,
115
- environment: initOptions.environment || 'production',
73
+ environment: initOptions.environment || "production",
116
74
  release: initOptions.release,
117
75
  dist: initOptions.dist,
118
76
  sdkMetadata,
@@ -120,58 +78,44 @@ async function startWorker(
120
78
  threshold: integrationOptions.threshold || DEFAULT_THRESHOLD_MS,
121
79
  maxEventsPerHour: integrationOptions.maxEventsPerHour || 1,
122
80
  staticTags: integrationOptions.staticTags || {},
123
- contexts,
81
+ contexts
124
82
  };
125
-
126
- const worker = new node_worker_threads.Worker(new URL('./event-loop-block-watchdog.js', (typeof document === 'undefined' ? require('u' + 'rl').pathToFileURL(__filename).href : (_documentCurrentScript && _documentCurrentScript.tagName.toUpperCase() === 'SCRIPT' && _documentCurrentScript.src || new URL('event-loop-block-integration.js', document.baseURI).href))), {
83
+ const worker = new node_worker_threads.Worker(new URL("./event-loop-block-watchdog.js", (typeof document === 'undefined' ? require('u' + 'rl').pathToFileURL(__filename).href : (_documentCurrentScript && _documentCurrentScript.tagName.toUpperCase() === 'SCRIPT' && _documentCurrentScript.src || new URL('event-loop-block-integration.js', document.baseURI).href))), {
127
84
  workerData: options,
128
85
  // We don't want any Node args like --import to be passed to the worker
129
86
  execArgv: [],
130
- env: { ...process.env, NODE_OPTIONS: undefined },
87
+ env: { ...process.env, NODE_OPTIONS: void 0 }
131
88
  });
132
-
133
- process.on('exit', () => {
134
- // eslint-disable-next-line @typescript-eslint/no-floating-promises
89
+ process.on("exit", () => {
135
90
  worker.terminate();
136
91
  });
137
-
138
- worker.once('error', (err) => {
139
- log('watchdog worker error', err);
140
- // eslint-disable-next-line @typescript-eslint/no-floating-promises
92
+ worker.once("error", (err) => {
93
+ log("watchdog worker error", err);
141
94
  worker.terminate();
142
95
  });
143
-
144
- worker.once('exit', (code) => {
145
- log('watchdog worker exit', code);
96
+ worker.once("exit", (code) => {
97
+ log("watchdog worker exit", code);
146
98
  });
147
-
148
- // Ensure this thread can't block app exit
149
99
  worker.unref();
150
100
  }
151
-
152
101
  const _eventLoopBlockIntegration = ((options = {}) => {
153
102
  let polling;
154
-
155
103
  return {
156
104
  name: INTEGRATION_NAME,
157
105
  async afterAllSetup(client) {
158
106
  const dsn = client.getDsn();
159
-
160
107
  if (!dsn) {
161
- log('No DSN configured, skipping starting integration');
108
+ log("No DSN configured, skipping starting integration");
162
109
  return;
163
110
  }
164
-
165
- // Otel is not setup until after afterAllSetup returns.
166
111
  setImmediate(async () => {
167
112
  try {
168
113
  polling = startPolling(client, options);
169
-
170
114
  if (node_worker_threads.isMainThread) {
171
115
  await startWorker(dsn, client, options);
172
116
  }
173
117
  } catch (err) {
174
- log('Failed to start integration', err);
118
+ log("Failed to start integration", err);
175
119
  return;
176
120
  }
177
121
  });
@@ -181,64 +125,21 @@ const _eventLoopBlockIntegration = ((options = {}) => {
181
125
  },
182
126
  stop() {
183
127
  polling?.stop();
184
- },
185
- } ;
186
- }) ;
187
-
188
- /**
189
- * Monitors the Node.js event loop for blocking behavior and reports blocked events to Sentry.
190
- *
191
- * Uses a background worker thread to detect when the main thread is blocked for longer than
192
- * the configured threshold (default: 1 second).
193
- *
194
- * When instrumenting via the `--import` flag, this integration will
195
- * automatically monitor all worker threads as well.
196
- *
197
- * ```js
198
- * // instrument.mjs
199
- * import * as Sentry from '@sentry/node';
200
- * import { eventLoopBlockIntegration } from '@sentry/node-native';
201
- *
202
- * Sentry.init({
203
- * dsn: '__YOUR_DSN__',
204
- * integrations: [
205
- * eventLoopBlockIntegration({
206
- * threshold: 500, // Report blocks longer than 500ms
207
- * }),
208
- * ],
209
- * });
210
- * ```
211
- *
212
- * Start your application with:
213
- * ```bash
214
- * node --import instrument.mjs app.mjs
215
- * ```
216
- */
128
+ }
129
+ };
130
+ });
217
131
  const eventLoopBlockIntegration = core.defineIntegration(_eventLoopBlockIntegration);
218
-
219
- /**
220
- * Disables Event Loop Block detection for the current thread for the duration
221
- * of the callback.
222
- *
223
- * This utility function allows you to disable block detection during operations that
224
- * are expected to block the event loop, such as intensive computational tasks or
225
- * synchronous I/O operations.
226
- */
227
132
  function disableBlockDetectionForCallback(callback) {
228
- const integration = core.getClient()?.getIntegrationByName(INTEGRATION_NAME) ;
229
-
133
+ const integration = core.getClient()?.getIntegrationByName(INTEGRATION_NAME);
230
134
  if (!integration) {
231
135
  return callback();
232
136
  }
233
-
234
137
  integration.stop();
235
-
236
138
  try {
237
139
  const result = callback();
238
140
  if (types.isPromise(result)) {
239
141
  return result.finally(() => integration.start());
240
142
  }
241
-
242
143
  integration.start();
243
144
  return result;
244
145
  } catch (error) {
@@ -246,34 +147,18 @@ function disableBlockDetectionForCallback(callback) {
246
147
  throw error;
247
148
  }
248
149
  }
249
-
250
- /**
251
- * Pauses the block detection integration.
252
- *
253
- * This function pauses event loop block detection for the current thread.
254
- */
255
150
  function pauseEventLoopBlockDetection() {
256
- const integration = core.getClient()?.getIntegrationByName(INTEGRATION_NAME) ;
257
-
151
+ const integration = core.getClient()?.getIntegrationByName(INTEGRATION_NAME);
258
152
  if (!integration) {
259
153
  return;
260
154
  }
261
-
262
155
  integration.stop();
263
156
  }
264
-
265
- /**
266
- * Restarts the block detection integration.
267
- *
268
- * This function restarts event loop block detection for the current thread.
269
- */
270
157
  function restartEventLoopBlockDetection() {
271
- const integration = core.getClient()?.getIntegrationByName(INTEGRATION_NAME) ;
272
-
158
+ const integration = core.getClient()?.getIntegrationByName(INTEGRATION_NAME);
273
159
  if (!integration) {
274
160
  return;
275
161
  }
276
-
277
162
  integration.start();
278
163
  }
279
164
 
@@ -1 +1 @@
1
- {"version":3,"file":"event-loop-block-integration.js","sources":["../../src/event-loop-block-integration.ts"],"sourcesContent":["import { isPromise } from 'node:util/types';\nimport { isMainThread, Worker } from 'node:worker_threads';\nimport type {\n ClientOptions,\n Contexts,\n DsnComponents,\n Event,\n EventHint,\n Integration,\n IntegrationFn,\n ScopeData,\n} from '@sentry/core';\nimport {\n debug,\n defineIntegration,\n getClient,\n getCurrentScope,\n getFilenameToDebugIdMap,\n getGlobalScope,\n getIsolationScope,\n mergeScopeData,\n} from '@sentry/core';\nimport type { NodeClient } from '@sentry/node';\nimport { registerThread, threadPoll } from '@sentry-internal/node-native-stacktrace';\nimport type { ThreadBlockedIntegrationOptions, WorkerStartData } from './common';\nimport { POLL_RATIO } from './common';\n\nconst INTEGRATION_NAME = 'ThreadBlocked';\nconst DEFAULT_THRESHOLD_MS = 1_000;\n\nfunction log(message: string, ...args: unknown[]): void {\n debug.log(`[Sentry Event Loop Blocked] ${message}`, ...args);\n}\n\n/**\n * Gets contexts by calling all event processors. This shouldn't be called until all integrations are setup\n */\nasync function getContexts(client: NodeClient): Promise<Contexts> {\n let event: Event | null = { message: INTEGRATION_NAME };\n const eventHint: EventHint = {};\n\n for (const processor of client.getEventProcessors()) {\n if (event === null) break;\n event = await processor(event, eventHint);\n }\n\n return event?.contexts || {};\n}\n\nfunction getLocalScopeData(): ScopeData {\n const globalScope = getGlobalScope().getScopeData();\n const currentScope = getCurrentScope().getScopeData();\n mergeScopeData(globalScope, currentScope);\n return globalScope;\n}\n\ntype IntegrationInternal = { start: () => void; stop: () => void };\n\nfunction poll(enabled: boolean, clientOptions: ClientOptions): void {\n try {\n const currentSession = getIsolationScope().getSession();\n // We need to copy the session object and remove the toJSON method so it can be sent to the worker\n // serialized without making it a SerializedSession\n const session = currentSession ? { ...currentSession, toJSON: undefined } : undefined;\n const scope = getLocalScopeData();\n // message the worker to tell it the main event loop is still running\n threadPoll(enabled, { session, scope, debugImages: getFilenameToDebugIdMap(clientOptions.stackParser) });\n } catch {\n // we ignore all errors\n }\n}\n\n/**\n * Starts polling\n */\nfunction startPolling(\n client: NodeClient,\n integrationOptions: Partial<ThreadBlockedIntegrationOptions>,\n): IntegrationInternal | undefined {\n if (client.asyncLocalStorageLookup) {\n const { asyncLocalStorage, contextSymbol } = client.asyncLocalStorageLookup;\n registerThread({ asyncLocalStorage, stateLookup: ['_currentContext', contextSymbol] });\n } else {\n registerThread();\n }\n\n let enabled = true;\n\n const initOptions = client.getOptions();\n const pollInterval = (integrationOptions.threshold || DEFAULT_THRESHOLD_MS) / POLL_RATIO;\n\n // unref so timer does not block exit\n setInterval(() => poll(enabled, initOptions), pollInterval).unref();\n\n return {\n start: () => {\n enabled = true;\n },\n stop: () => {\n enabled = false;\n // poll immediately because the timer above might not get a chance to run\n // before the event loop gets blocked\n poll(enabled, initOptions);\n },\n };\n}\n\n/**\n * Starts the worker thread that will monitor the other threads.\n *\n * This function is only called in the main thread.\n */\nasync function startWorker(\n dsn: DsnComponents,\n client: NodeClient,\n integrationOptions: Partial<ThreadBlockedIntegrationOptions>,\n): Promise<void> {\n const contexts = await getContexts(client);\n\n // These will not be accurate if sent later from the worker thread\n delete contexts.app?.app_memory;\n delete contexts.device?.free_memory;\n\n const initOptions = client.getOptions();\n\n const sdkMetadata = client.getSdkMetadata() || {};\n if (sdkMetadata.sdk) {\n sdkMetadata.sdk.integrations = initOptions.integrations.map(i => i.name);\n }\n\n const options: WorkerStartData = {\n debug: debug.isEnabled(),\n dsn,\n tunnel: initOptions.tunnel,\n environment: initOptions.environment || 'production',\n release: initOptions.release,\n dist: initOptions.dist,\n sdkMetadata,\n appRootPath: integrationOptions.appRootPath,\n threshold: integrationOptions.threshold || DEFAULT_THRESHOLD_MS,\n maxEventsPerHour: integrationOptions.maxEventsPerHour || 1,\n staticTags: integrationOptions.staticTags || {},\n contexts,\n };\n\n const worker = new Worker(new URL('./event-loop-block-watchdog.js', import.meta.url), {\n workerData: options,\n // We don't want any Node args like --import to be passed to the worker\n execArgv: [],\n env: { ...process.env, NODE_OPTIONS: undefined },\n });\n\n process.on('exit', () => {\n // eslint-disable-next-line @typescript-eslint/no-floating-promises\n worker.terminate();\n });\n\n worker.once('error', (err: Error) => {\n log('watchdog worker error', err);\n // eslint-disable-next-line @typescript-eslint/no-floating-promises\n worker.terminate();\n });\n\n worker.once('exit', (code: number) => {\n log('watchdog worker exit', code);\n });\n\n // Ensure this thread can't block app exit\n worker.unref();\n}\n\nconst _eventLoopBlockIntegration = ((options: Partial<ThreadBlockedIntegrationOptions> = {}) => {\n let polling: IntegrationInternal | undefined;\n\n return {\n name: INTEGRATION_NAME,\n async afterAllSetup(client: NodeClient): Promise<void> {\n const dsn = client.getDsn();\n\n if (!dsn) {\n log('No DSN configured, skipping starting integration');\n return;\n }\n\n // Otel is not setup until after afterAllSetup returns.\n setImmediate(async () => {\n try {\n polling = startPolling(client, options);\n\n if (isMainThread) {\n await startWorker(dsn, client, options);\n }\n } catch (err) {\n log('Failed to start integration', err);\n return;\n }\n });\n },\n start() {\n polling?.start();\n },\n stop() {\n polling?.stop();\n },\n } as Integration & IntegrationInternal;\n}) satisfies IntegrationFn;\n\n/**\n * Monitors the Node.js event loop for blocking behavior and reports blocked events to Sentry.\n *\n * Uses a background worker thread to detect when the main thread is blocked for longer than\n * the configured threshold (default: 1 second).\n *\n * When instrumenting via the `--import` flag, this integration will\n * automatically monitor all worker threads as well.\n *\n * ```js\n * // instrument.mjs\n * import * as Sentry from '@sentry/node';\n * import { eventLoopBlockIntegration } from '@sentry/node-native';\n *\n * Sentry.init({\n * dsn: '__YOUR_DSN__',\n * integrations: [\n * eventLoopBlockIntegration({\n * threshold: 500, // Report blocks longer than 500ms\n * }),\n * ],\n * });\n * ```\n *\n * Start your application with:\n * ```bash\n * node --import instrument.mjs app.mjs\n * ```\n */\nexport const eventLoopBlockIntegration = defineIntegration(_eventLoopBlockIntegration);\n\nexport function disableBlockDetectionForCallback<T>(callback: () => T): T;\nexport function disableBlockDetectionForCallback<T>(callback: () => Promise<T>): Promise<T>;\n/**\n * Disables Event Loop Block detection for the current thread for the duration\n * of the callback.\n *\n * This utility function allows you to disable block detection during operations that\n * are expected to block the event loop, such as intensive computational tasks or\n * synchronous I/O operations.\n */\nexport function disableBlockDetectionForCallback<T>(callback: () => T | Promise<T>): T | Promise<T> {\n const integration = getClient()?.getIntegrationByName(INTEGRATION_NAME) as IntegrationInternal | undefined;\n\n if (!integration) {\n return callback();\n }\n\n integration.stop();\n\n try {\n const result = callback();\n if (isPromise(result)) {\n return result.finally(() => integration.start());\n }\n\n integration.start();\n return result;\n } catch (error) {\n integration.start();\n throw error;\n }\n}\n\n/**\n * Pauses the block detection integration.\n *\n * This function pauses event loop block detection for the current thread.\n */\nexport function pauseEventLoopBlockDetection(): void {\n const integration = getClient()?.getIntegrationByName(INTEGRATION_NAME) as IntegrationInternal | undefined;\n\n if (!integration) {\n return;\n }\n\n integration.stop();\n}\n\n/**\n * Restarts the block detection integration.\n *\n * This function restarts event loop block detection for the current thread.\n */\nexport function restartEventLoopBlockDetection(): void {\n const integration = getClient()?.getIntegrationByName(INTEGRATION_NAME) as IntegrationInternal | undefined;\n\n if (!integration) {\n return;\n }\n\n integration.start();\n}\n"],"names":["debug","getGlobalScope","getCurrentScope","mergeScopeData","getIsolationScope","threadPoll","getFilenameToDebugIdMap","registerThread","POLL_RATIO","Worker","isMainThread","defineIntegration","getClient","isPromise"],"mappings":"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"}
1
+ {"version":3,"file":"event-loop-block-integration.js","sources":["../../src/event-loop-block-integration.ts"],"sourcesContent":["import { isPromise } from 'node:util/types';\nimport { isMainThread, Worker } from 'node:worker_threads';\nimport type {\n ClientOptions,\n Contexts,\n DsnComponents,\n Event,\n EventHint,\n Integration,\n IntegrationFn,\n ScopeData,\n} from '@sentry/core';\nimport {\n debug,\n defineIntegration,\n getClient,\n getCurrentScope,\n getFilenameToDebugIdMap,\n getGlobalScope,\n getIsolationScope,\n mergeScopeData,\n} from '@sentry/core';\nimport type { NodeClient } from '@sentry/node';\nimport { registerThread, threadPoll } from '@sentry-internal/node-native-stacktrace';\nimport type { ThreadBlockedIntegrationOptions, WorkerStartData } from './common';\nimport { POLL_RATIO } from './common';\n\nconst INTEGRATION_NAME = 'ThreadBlocked';\nconst DEFAULT_THRESHOLD_MS = 1_000;\n\nfunction log(message: string, ...args: unknown[]): void {\n debug.log(`[Sentry Event Loop Blocked] ${message}`, ...args);\n}\n\n/**\n * Gets contexts by calling all event processors. This shouldn't be called until all integrations are setup\n */\nasync function getContexts(client: NodeClient): Promise<Contexts> {\n let event: Event | null = { message: INTEGRATION_NAME };\n const eventHint: EventHint = {};\n\n for (const processor of client.getEventProcessors()) {\n if (event === null) break;\n event = await processor(event, eventHint);\n }\n\n return event?.contexts || {};\n}\n\nfunction getLocalScopeData(): ScopeData {\n const globalScope = getGlobalScope().getScopeData();\n const currentScope = getCurrentScope().getScopeData();\n mergeScopeData(globalScope, currentScope);\n return globalScope;\n}\n\ntype IntegrationInternal = { start: () => void; stop: () => void };\n\nfunction poll(enabled: boolean, clientOptions: ClientOptions): void {\n try {\n const currentSession = getIsolationScope().getSession();\n // We need to copy the session object and remove the toJSON method so it can be sent to the worker\n // serialized without making it a SerializedSession\n const session = currentSession ? { ...currentSession, toJSON: undefined } : undefined;\n const scope = getLocalScopeData();\n // message the worker to tell it the main event loop is still running\n threadPoll(enabled, { session, scope, debugImages: getFilenameToDebugIdMap(clientOptions.stackParser) });\n } catch {\n // we ignore all errors\n }\n}\n\n/**\n * Starts polling\n */\nfunction startPolling(\n client: NodeClient,\n integrationOptions: Partial<ThreadBlockedIntegrationOptions>,\n): IntegrationInternal | undefined {\n if (client.asyncLocalStorageLookup) {\n const { asyncLocalStorage, contextSymbol } = client.asyncLocalStorageLookup;\n registerThread({ asyncLocalStorage, stateLookup: ['_currentContext', contextSymbol] });\n } else {\n registerThread();\n }\n\n let enabled = true;\n\n const initOptions = client.getOptions();\n const pollInterval = (integrationOptions.threshold || DEFAULT_THRESHOLD_MS) / POLL_RATIO;\n\n // unref so timer does not block exit\n setInterval(() => poll(enabled, initOptions), pollInterval).unref();\n\n return {\n start: () => {\n enabled = true;\n },\n stop: () => {\n enabled = false;\n // poll immediately because the timer above might not get a chance to run\n // before the event loop gets blocked\n poll(enabled, initOptions);\n },\n };\n}\n\n/**\n * Starts the worker thread that will monitor the other threads.\n *\n * This function is only called in the main thread.\n */\nasync function startWorker(\n dsn: DsnComponents,\n client: NodeClient,\n integrationOptions: Partial<ThreadBlockedIntegrationOptions>,\n): Promise<void> {\n const contexts = await getContexts(client);\n\n // These will not be accurate if sent later from the worker thread\n delete contexts.app?.app_memory;\n delete contexts.device?.free_memory;\n\n const initOptions = client.getOptions();\n\n const sdkMetadata = client.getSdkMetadata() || {};\n if (sdkMetadata.sdk) {\n sdkMetadata.sdk.integrations = initOptions.integrations.map(i => i.name);\n }\n\n const options: WorkerStartData = {\n debug: debug.isEnabled(),\n dsn,\n tunnel: initOptions.tunnel,\n environment: initOptions.environment || 'production',\n release: initOptions.release,\n dist: initOptions.dist,\n sdkMetadata,\n appRootPath: integrationOptions.appRootPath,\n threshold: integrationOptions.threshold || DEFAULT_THRESHOLD_MS,\n maxEventsPerHour: integrationOptions.maxEventsPerHour || 1,\n staticTags: integrationOptions.staticTags || {},\n contexts,\n };\n\n const worker = new Worker(new URL('./event-loop-block-watchdog.js', import.meta.url), {\n workerData: options,\n // We don't want any Node args like --import to be passed to the worker\n execArgv: [],\n env: { ...process.env, NODE_OPTIONS: undefined },\n });\n\n process.on('exit', () => {\n // eslint-disable-next-line @typescript-eslint/no-floating-promises\n worker.terminate();\n });\n\n worker.once('error', (err: Error) => {\n log('watchdog worker error', err);\n // eslint-disable-next-line @typescript-eslint/no-floating-promises\n worker.terminate();\n });\n\n worker.once('exit', (code: number) => {\n log('watchdog worker exit', code);\n });\n\n // Ensure this thread can't block app exit\n worker.unref();\n}\n\nconst _eventLoopBlockIntegration = ((options: Partial<ThreadBlockedIntegrationOptions> = {}) => {\n let polling: IntegrationInternal | undefined;\n\n return {\n name: INTEGRATION_NAME,\n async afterAllSetup(client: NodeClient): Promise<void> {\n const dsn = client.getDsn();\n\n if (!dsn) {\n log('No DSN configured, skipping starting integration');\n return;\n }\n\n // Otel is not setup until after afterAllSetup returns.\n setImmediate(async () => {\n try {\n polling = startPolling(client, options);\n\n if (isMainThread) {\n await startWorker(dsn, client, options);\n }\n } catch (err) {\n log('Failed to start integration', err);\n return;\n }\n });\n },\n start() {\n polling?.start();\n },\n stop() {\n polling?.stop();\n },\n } as Integration & IntegrationInternal;\n}) satisfies IntegrationFn;\n\n/**\n * Monitors the Node.js event loop for blocking behavior and reports blocked events to Sentry.\n *\n * Uses a background worker thread to detect when the main thread is blocked for longer than\n * the configured threshold (default: 1 second).\n *\n * When instrumenting via the `--import` flag, this integration will\n * automatically monitor all worker threads as well.\n *\n * ```js\n * // instrument.mjs\n * import * as Sentry from '@sentry/node';\n * import { eventLoopBlockIntegration } from '@sentry/node-native';\n *\n * Sentry.init({\n * dsn: '__YOUR_DSN__',\n * integrations: [\n * eventLoopBlockIntegration({\n * threshold: 500, // Report blocks longer than 500ms\n * }),\n * ],\n * });\n * ```\n *\n * Start your application with:\n * ```bash\n * node --import instrument.mjs app.mjs\n * ```\n */\nexport const eventLoopBlockIntegration = defineIntegration(_eventLoopBlockIntegration);\n\nexport function disableBlockDetectionForCallback<T>(callback: () => T): T;\nexport function disableBlockDetectionForCallback<T>(callback: () => Promise<T>): Promise<T>;\n/**\n * Disables Event Loop Block detection for the current thread for the duration\n * of the callback.\n *\n * This utility function allows you to disable block detection during operations that\n * are expected to block the event loop, such as intensive computational tasks or\n * synchronous I/O operations.\n */\nexport function disableBlockDetectionForCallback<T>(callback: () => T | Promise<T>): T | Promise<T> {\n const integration = getClient()?.getIntegrationByName(INTEGRATION_NAME) as IntegrationInternal | undefined;\n\n if (!integration) {\n return callback();\n }\n\n integration.stop();\n\n try {\n const result = callback();\n if (isPromise(result)) {\n return result.finally(() => integration.start());\n }\n\n integration.start();\n return result;\n } catch (error) {\n integration.start();\n throw error;\n }\n}\n\n/**\n * Pauses the block detection integration.\n *\n * This function pauses event loop block detection for the current thread.\n */\nexport function pauseEventLoopBlockDetection(): void {\n const integration = getClient()?.getIntegrationByName(INTEGRATION_NAME) as IntegrationInternal | undefined;\n\n if (!integration) {\n return;\n }\n\n integration.stop();\n}\n\n/**\n * Restarts the block detection integration.\n *\n * This function restarts event loop block detection for the current thread.\n */\nexport function restartEventLoopBlockDetection(): void {\n const integration = getClient()?.getIntegrationByName(INTEGRATION_NAME) as IntegrationInternal | undefined;\n\n if (!integration) {\n return;\n }\n\n integration.start();\n}\n"],"names":["debug","getGlobalScope","getCurrentScope","mergeScopeData","getIsolationScope","threadPoll","getFilenameToDebugIdMap","registerThread","POLL_RATIO","Worker","isMainThread","defineIntegration","getClient","isPromise"],"mappings":"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"}
@@ -16,123 +16,92 @@ const {
16
16
  release,
17
17
  sdkMetadata,
18
18
  staticTags: tags,
19
- tunnel,
20
- } = node_worker_threads.workerData ;
21
-
19
+ tunnel
20
+ } = node_worker_threads.workerData;
22
21
  const pollInterval = threshold / common.POLL_RATIO;
23
- const triggeredThreads = new Set();
24
-
22
+ const triggeredThreads = /* @__PURE__ */ new Set();
25
23
  function log(...msg) {
26
24
  if (debug) {
27
- // eslint-disable-next-line no-console
28
- console.log('[Sentry Event Loop Blocked Watchdog]', ...msg);
25
+ console.log("[Sentry Event Loop Blocked Watchdog]", ...msg);
29
26
  }
30
27
  }
31
-
32
- function createRateLimiter(maxEventsPerHour) {
28
+ function createRateLimiter(maxEventsPerHour2) {
33
29
  let currentHour = 0;
34
30
  let currentCount = 0;
35
-
36
- return function isRateLimited() {
37
- const hour = new Date().getHours();
38
-
31
+ return function isRateLimited2() {
32
+ const hour = (/* @__PURE__ */ new Date()).getHours();
39
33
  if (hour !== currentHour) {
40
34
  currentHour = hour;
41
35
  currentCount = 0;
42
36
  }
43
-
44
- if (currentCount >= maxEventsPerHour) {
45
- if (currentCount === maxEventsPerHour) {
37
+ if (currentCount >= maxEventsPerHour2) {
38
+ if (currentCount === maxEventsPerHour2) {
46
39
  currentCount += 1;
47
40
  log(`Rate limit reached: ${currentCount} events in this hour`);
48
41
  }
49
42
  return true;
50
43
  }
51
-
52
44
  currentCount += 1;
53
45
  return false;
54
46
  };
55
47
  }
56
-
57
48
  const url = core.getEnvelopeEndpointWithUrlEncodedAuth(dsn, tunnel, sdkMetadata.sdk);
58
49
  const transport = node.makeNodeTransport({
59
50
  url,
60
51
  recordDroppedEvent: () => {
61
- //
62
- },
52
+ }
63
53
  });
64
54
  const isRateLimited = createRateLimiter(maxEventsPerHour);
65
-
66
55
  async function sendAbnormalSession(serializedSession) {
67
56
  if (!serializedSession) {
68
57
  return;
69
58
  }
70
-
71
- log('Sending abnormal session');
59
+ log("Sending abnormal session");
72
60
  const session = core.makeSession(serializedSession);
73
-
74
61
  core.updateSession(session, {
75
- status: 'abnormal',
76
- abnormal_mechanism: 'anr_foreground',
62
+ status: "abnormal",
63
+ abnormal_mechanism: "anr_foreground",
77
64
  release,
78
- environment,
65
+ environment
79
66
  });
80
-
81
67
  const envelope = core.createSessionEnvelope(session, dsn, sdkMetadata, tunnel);
82
- // Log the envelope so to aid in testing
83
68
  log(JSON.stringify(envelope));
84
-
85
69
  await transport.send(envelope);
86
70
  }
87
-
88
- log('Started');
89
-
71
+ log("Started");
90
72
  function prepareStackFrames(stackFrames) {
91
73
  if (!stackFrames) {
92
- return undefined;
74
+ return void 0;
93
75
  }
94
-
95
- // Strip Sentry frames and reverse the stack frames so they are in the correct order
96
76
  const strippedFrames = core.stripSentryFramesAndReverse(stackFrames);
97
-
98
77
  for (const frame of strippedFrames) {
99
78
  if (!frame.filename) {
100
79
  continue;
101
80
  }
102
-
103
81
  frame.in_app = core.filenameIsInApp(frame.filename);
104
-
105
- // If we have an app root path, rewrite the filenames to be relative to the app root
106
82
  if (appRootPath) {
107
83
  frame.filename = core.normalizeUrlToBase(frame.filename, appRootPath);
108
84
  }
109
85
  }
110
-
111
86
  return strippedFrames;
112
87
  }
113
-
114
88
  function stripFileProtocol(filename) {
115
89
  if (!filename) {
116
- return undefined;
90
+ return void 0;
117
91
  }
118
- return filename.replace(/^file:\/\//, '');
92
+ return filename.replace(/^file:\/\//, "");
119
93
  }
120
-
121
- // eslint-disable-next-line complexity
122
94
  function applyDebugMeta(event, debugImages) {
123
95
  if (Object.keys(debugImages).length === 0) {
124
96
  return;
125
97
  }
126
-
127
98
  const normalisedDebugImages = appRootPath ? {} : debugImages;
128
99
  if (appRootPath) {
129
100
  for (const [path, debugId] of Object.entries(debugImages)) {
130
101
  normalisedDebugImages[core.normalizeUrlToBase(path, appRootPath)] = debugId;
131
102
  }
132
103
  }
133
-
134
- const filenameToDebugId = new Map();
135
-
104
+ const filenameToDebugId = /* @__PURE__ */ new Map();
136
105
  for (const exception of event.exception?.values || []) {
137
106
  for (const frame of exception.stacktrace?.frames || []) {
138
107
  const filename = stripFileProtocol(frame.abs_path || frame.filename);
@@ -141,7 +110,6 @@ function applyDebugMeta(event, debugImages) {
141
110
  }
142
111
  }
143
112
  }
144
-
145
113
  for (const thread of event.threads?.values || []) {
146
114
  for (const frame of thread.stacktrace?.frames || []) {
147
115
  const filename = stripFileProtocol(frame.abs_path || frame.filename);
@@ -150,145 +118,115 @@ function applyDebugMeta(event, debugImages) {
150
118
  }
151
119
  }
152
120
  }
153
-
154
121
  if (filenameToDebugId.size > 0) {
155
122
  const images = [];
156
123
  for (const [code_file, debug_id] of filenameToDebugId.entries()) {
157
124
  images.push({
158
- type: 'sourcemap',
125
+ type: "sourcemap",
159
126
  code_file,
160
- debug_id,
127
+ debug_id
161
128
  });
162
129
  }
163
130
  event.debug_meta = { images };
164
131
  }
165
132
  }
166
-
167
- function getExceptionAndThreads(
168
- crashedThreadId,
169
- threads,
170
- ) {
133
+ function getExceptionAndThreads(crashedThreadId, threads) {
171
134
  const crashedThread = threads[crashedThreadId];
172
-
173
135
  return {
174
136
  exception: {
175
137
  values: [
176
138
  {
177
- type: 'EventLoopBlocked',
139
+ type: "EventLoopBlocked",
178
140
  value: `Event Loop Blocked for at least ${threshold} ms`,
179
141
  stacktrace: { frames: prepareStackFrames(crashedThread?.frames) },
180
142
  // This ensures the UI doesn't say 'Crashed in' for the stack trace
181
- mechanism: { type: 'ANR' },
182
- thread_id: crashedThreadId,
183
- },
184
- ],
143
+ mechanism: { type: "ANR" },
144
+ thread_id: crashedThreadId
145
+ }
146
+ ]
185
147
  },
186
148
  threads: {
187
149
  values: Object.entries(threads).map(([threadId, threadState]) => {
188
150
  const crashed = threadId === crashedThreadId;
189
-
190
151
  const thread = {
191
152
  id: threadId,
192
- name: threadId === '0' ? 'main' : `worker-${threadId}`,
153
+ name: threadId === "0" ? "main" : `worker-${threadId}`,
193
154
  crashed,
194
155
  current: true,
195
- main: threadId === '0',
156
+ main: threadId === "0"
196
157
  };
197
-
198
158
  if (!crashed) {
199
159
  thread.stacktrace = { frames: prepareStackFrames(threadState.frames) };
200
160
  }
201
-
202
161
  return thread;
203
- }),
204
- },
162
+ })
163
+ }
205
164
  };
206
165
  }
207
-
208
166
  function applyScopeToEvent(event, scope) {
209
167
  core.applyScopeDataToEvent(event, scope);
210
-
211
168
  if (!event.contexts?.trace) {
212
169
  const { traceId, parentSpanId, propagationSpanId } = scope.propagationContext;
213
170
  event.contexts = {
214
171
  trace: {
215
172
  trace_id: traceId,
216
173
  span_id: propagationSpanId || core.generateSpanId(),
217
- parent_span_id: parentSpanId,
174
+ parent_span_id: parentSpanId
218
175
  },
219
- ...event.contexts,
176
+ ...event.contexts
220
177
  };
221
178
  }
222
179
  }
223
-
224
180
  async function sendBlockEvent(crashedThreadId) {
225
181
  if (isRateLimited()) {
226
182
  return;
227
183
  }
228
-
229
184
  const threads = nodeNativeStacktrace.captureStackTrace();
230
185
  const crashedThread = threads[crashedThreadId];
231
-
232
186
  if (!crashedThread) {
233
187
  log(`No thread found with ID '${crashedThreadId}'`);
234
188
  return;
235
189
  }
236
-
237
190
  try {
238
191
  await sendAbnormalSession(crashedThread.pollState?.session);
239
192
  } catch (error) {
240
193
  log(`Failed to send abnormal session for thread '${crashedThreadId}':`, error);
241
194
  }
242
-
243
- log('Sending event');
244
-
195
+ log("Sending event");
245
196
  const event = {
246
197
  event_id: core.uuid4(),
247
198
  contexts,
248
199
  release,
249
200
  environment,
250
201
  dist,
251
- platform: 'node',
252
- level: 'error',
202
+ platform: "node",
203
+ level: "error",
253
204
  tags,
254
- ...getExceptionAndThreads(crashedThreadId, threads),
205
+ ...getExceptionAndThreads(crashedThreadId, threads)
255
206
  };
256
-
257
- const scope = crashedThread.pollState?.scope
258
- ? new core.Scope().update(crashedThread.pollState.scope).getScopeData()
259
- : new core.Scope().getScopeData();
260
-
207
+ const scope = crashedThread.pollState?.scope ? new core.Scope().update(crashedThread.pollState.scope).getScopeData() : new core.Scope().getScopeData();
261
208
  if (crashedThread?.asyncState?.isolationScope) {
262
- // We need to rehydrate the scope from the serialized object with properties beginning with _user, etc
263
209
  const isolationScope = Object.assign(new core.Scope(), crashedThread.asyncState.isolationScope).getScopeData();
264
210
  core.mergeScopeData(scope, isolationScope);
265
211
  }
266
212
  applyScopeToEvent(event, scope);
267
-
268
213
  const allDebugImages = Object.values(threads).reduce((acc, threadState) => {
269
214
  return { ...acc, ...threadState.pollState?.debugImages };
270
215
  }, {});
271
-
272
216
  applyDebugMeta(event, allDebugImages);
273
-
274
217
  const envelope = core.createEventEnvelope(event, dsn, sdkMetadata, tunnel);
275
- // Log the envelope to aid in testing
276
218
  log(JSON.stringify(envelope));
277
-
278
219
  await transport.send(envelope);
279
- await transport.flush(2000);
220
+ await transport.flush(2e3);
280
221
  }
281
-
282
222
  setInterval(async () => {
283
223
  for (const [threadId, time] of Object.entries(nodeNativeStacktrace.getThreadsLastSeen())) {
284
224
  if (time > threshold) {
285
225
  if (triggeredThreads.has(threadId)) {
286
226
  continue;
287
227
  }
288
-
289
228
  log(`Blocked thread detected '${threadId}' last polled ${time} ms ago.`);
290
229
  triggeredThreads.add(threadId);
291
-
292
230
  try {
293
231
  await sendBlockEvent(threadId);
294
232
  } catch (error) {