Skip to content

Commit 0aa7d95

Browse files
authored
fix: Disable ANR and Local Variables if debugger is enabled via CLI args (#14643)
1 parent d9d3b06 commit 0aa7d95

File tree

5 files changed

+70
-5
lines changed

5 files changed

+70
-5
lines changed

dev-packages/node-integration-tests/suites/anr/test.ts

+31-2
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,35 @@ const ANR_EVENT = {
5252
},
5353
};
5454

55+
const ANR_EVENT_WITHOUT_STACKTRACE = {
56+
// Ensure we have context
57+
contexts: {
58+
device: {
59+
arch: expect.any(String),
60+
},
61+
app: {
62+
app_start_time: expect.any(String),
63+
},
64+
os: {
65+
name: expect.any(String),
66+
},
67+
culture: {
68+
timezone: expect.any(String),
69+
},
70+
},
71+
// and an exception that is our ANR
72+
exception: {
73+
values: [
74+
{
75+
type: 'ApplicationNotResponding',
76+
value: 'Application Not Responding for at least 100 ms',
77+
mechanism: { type: 'ANR' },
78+
stacktrace: {},
79+
},
80+
],
81+
},
82+
};
83+
5584
const ANR_EVENT_WITH_SCOPE = {
5685
...ANR_EVENT,
5786
user: {
@@ -98,11 +127,11 @@ conditionalTest({ min: 16 })('should report ANR when event loop blocked', () =>
98127
createRunner(__dirname, 'indefinite.mjs').withMockSentryServer().expect({ event: ANR_EVENT }).start(done);
99128
});
100129

101-
test('With --inspect', done => {
130+
test("With --inspect the debugger isn't used", done => {
102131
createRunner(__dirname, 'basic.mjs')
103132
.withMockSentryServer()
104133
.withFlags('--inspect')
105-
.expect({ event: ANR_EVENT_WITH_SCOPE })
134+
.expect({ event: ANR_EVENT_WITHOUT_STACKTRACE })
106135
.start(done);
107136
});
108137

packages/node/src/integrations/anr/index.ts

+7-1
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ import {
1414
} from '@sentry/core';
1515
import { NODE_VERSION } from '../../nodeVersion';
1616
import type { NodeClient } from '../../sdk/client';
17+
import { isDebuggerEnabled } from '../../utils/debug';
1718
import type { AnrIntegrationOptions, WorkerStartData } from './common';
1819

1920
const { isPromise } = types;
@@ -98,9 +99,14 @@ const _anrIntegration = ((options: Partial<AnrIntegrationOptions> = {}) => {
9899
});
99100
}
100101
},
101-
setup(initClient: NodeClient) {
102+
async setup(initClient: NodeClient) {
102103
client = initClient;
103104

105+
if (options.captureStackTrace && (await isDebuggerEnabled())) {
106+
logger.warn('ANR captureStackTrace has been disabled because the debugger was already enabled');
107+
options.captureStackTrace = false;
108+
}
109+
104110
// setImmediate is used to ensure that all other integrations have had their setup called first.
105111
// This allows us to call into all integrations to fetch the full context
106112
setImmediate(() => this.startWorker());

packages/node/src/integrations/local-variables/local-variables-async.ts

+7-1
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ import { Worker } from 'node:worker_threads';
22
import type { Event, EventHint, Exception, IntegrationFn } from '@sentry/core';
33
import { defineIntegration, logger } from '@sentry/core';
44
import type { NodeClient } from '../../sdk/client';
5+
import { isDebuggerEnabled } from '../../utils/debug';
56
import type { FrameVariables, LocalVariablesIntegrationOptions, LocalVariablesWorkerArgs } from './common';
67
import { LOCAL_VARIABLES_KEY, functionNamesMatch } from './common';
78

@@ -101,13 +102,18 @@ export const localVariablesAsyncIntegration = defineIntegration(((
101102

102103
return {
103104
name: 'LocalVariablesAsync',
104-
setup(client: NodeClient) {
105+
async setup(client: NodeClient) {
105106
const clientOptions = client.getOptions();
106107

107108
if (!clientOptions.includeLocalVariables) {
108109
return;
109110
}
110111

112+
if (await isDebuggerEnabled()) {
113+
logger.warn('Local variables capture has been disabled because the debugger was already enabled');
114+
return;
115+
}
116+
111117
const options: LocalVariablesWorkerArgs = {
112118
...integrationOptions,
113119
debug: logger.isEnabled(),

packages/node/src/integrations/local-variables/local-variables-sync.ts

+7-1
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ import type { Event, Exception, IntegrationFn, StackFrame, StackParser } from '@
33
import { LRUMap, defineIntegration, getClient, logger } from '@sentry/core';
44
import { NODE_MAJOR } from '../../nodeVersion';
55
import type { NodeClient } from '../../sdk/client';
6+
import { isDebuggerEnabled } from '../../utils/debug';
67
import type {
78
FrameVariables,
89
LocalVariablesIntegrationOptions,
@@ -289,7 +290,7 @@ const _localVariablesSyncIntegration = ((
289290

290291
return {
291292
name: INTEGRATION_NAME,
292-
setupOnce() {
293+
async setupOnce() {
293294
const client = getClient<NodeClient>();
294295
const clientOptions = client?.getOptions();
295296

@@ -306,6 +307,11 @@ const _localVariablesSyncIntegration = ((
306307
return;
307308
}
308309

310+
if (await isDebuggerEnabled()) {
311+
logger.warn('Local variables capture has been disabled because the debugger was already enabled');
312+
return;
313+
}
314+
309315
AsyncSession.create(sessionOverride).then(
310316
session => {
311317
function handlePaused(

packages/node/src/utils/debug.ts

+18
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
let cachedDebuggerEnabled: boolean | undefined;
2+
3+
/**
4+
* Was the debugger enabled when this function was first called?
5+
*/
6+
export async function isDebuggerEnabled(): Promise<boolean> {
7+
if (cachedDebuggerEnabled === undefined) {
8+
try {
9+
// Node can be built without inspector support
10+
const inspector = await import('node:inspector');
11+
cachedDebuggerEnabled = !!inspector.url();
12+
} catch (_) {
13+
cachedDebuggerEnabled = false;
14+
}
15+
}
16+
17+
return cachedDebuggerEnabled;
18+
}

0 commit comments

Comments
 (0)