-
-
Notifications
You must be signed in to change notification settings - Fork 1.7k
/
Copy pathlocal-variables-async.ts
139 lines (117 loc) · 4.4 KB
/
local-variables-async.ts
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
import { Worker } from 'node:worker_threads';
import type { Event, EventHint, Exception, IntegrationFn } from '@sentry/core';
import { defineIntegration, logger } from '@sentry/core';
import type { NodeClient } from '../../sdk/client';
import { isDebuggerEnabled } from '../../utils/debug';
import type { FrameVariables, LocalVariablesIntegrationOptions, LocalVariablesWorkerArgs } from './common';
import { LOCAL_VARIABLES_KEY, functionNamesMatch } from './common';
// This string is a placeholder that gets overwritten with the worker code.
export const base64WorkerScript = '###LocalVariablesWorkerScript###';
function log(...args: unknown[]): void {
logger.log('[LocalVariables]', ...args);
}
/**
* Adds local variables to exception frames
*/
export const localVariablesAsyncIntegration = defineIntegration(((
integrationOptions: LocalVariablesIntegrationOptions = {},
) => {
function addLocalVariablesToException(exception: Exception, localVariables: FrameVariables[]): void {
// Filter out frames where the function name is `new Promise` since these are in the error.stack frames
// but do not appear in the debugger call frames
const frames = (exception.stacktrace?.frames || []).filter(frame => frame.function !== 'new Promise');
for (let i = 0; i < frames.length; i++) {
// Sentry frames are in reverse order
const frameIndex = frames.length - i - 1;
const frameLocalVariables = localVariables[i];
const frame = frames[frameIndex];
if (!frame || !frameLocalVariables) {
// Drop out if we run out of frames to match up
break;
}
if (
// We need to have vars to add
frameLocalVariables.vars === undefined ||
// We're not interested in frames that are not in_app because the vars are not relevant
frame.in_app === false ||
// The function names need to match
!functionNamesMatch(frame.function, frameLocalVariables.function)
) {
continue;
}
frame.vars = frameLocalVariables.vars;
}
}
function addLocalVariablesToEvent(event: Event, hint: EventHint): Event {
if (
hint.originalException &&
typeof hint.originalException === 'object' &&
LOCAL_VARIABLES_KEY in hint.originalException &&
Array.isArray(hint.originalException[LOCAL_VARIABLES_KEY])
) {
for (const exception of event.exception?.values || []) {
addLocalVariablesToException(exception, hint.originalException[LOCAL_VARIABLES_KEY]);
}
hint.originalException[LOCAL_VARIABLES_KEY] = undefined;
}
return event;
}
async function startInspector(): Promise<void> {
// We load inspector dynamically because on some platforms Node is built without inspector support
const inspector = await import('node:inspector');
if (!inspector.url()) {
inspector.open(0);
}
}
function startWorker(options: LocalVariablesWorkerArgs): void {
const worker = new Worker(new URL(`data:application/javascript;base64,${base64WorkerScript}`), {
workerData: options,
// We don't want any Node args to be passed to the worker
execArgv: [],
});
process.on('exit', () => {
// eslint-disable-next-line @typescript-eslint/no-floating-promises
worker.terminate();
});
worker.once('error', (err: Error) => {
log('Worker error', err);
});
worker.once('exit', (code: number) => {
log('Worker exit', code);
});
// Ensure this thread can't block app exit
worker.unref();
}
return {
name: 'LocalVariablesAsync',
async setup(client: NodeClient) {
const clientOptions = client.getOptions();
if (!clientOptions.includeLocalVariables) {
return;
}
if (await isDebuggerEnabled()) {
logger.warn('Local variables capture has been disabled because the debugger was already enabled');
return;
}
const options: LocalVariablesWorkerArgs = {
...integrationOptions,
debug: logger.isEnabled(),
};
startInspector().then(
() => {
try {
startWorker(options);
} catch (e) {
logger.error('Failed to start worker', e);
}
},
e => {
logger.error('Failed to start inspector', e);
},
);
},
processEvent(event: Event, hint: EventHint): Event {
return addLocalVariablesToEvent(event, hint);
},
};
}) satisfies IntegrationFn);