Skip to content

Commit aa8b2b8

Browse files
committed
feat: implement error classification for telemetry and enhance environment discovery error handling
1 parent 74bf1c8 commit aa8b2b8

File tree

3 files changed

+65
-4
lines changed

3 files changed

+65
-4
lines changed
Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
import { CancellationError } from 'vscode';
2+
import { RpcTimeoutError } from '../../managers/common/nativePythonFinder';
3+
4+
export type DiscoveryErrorType =
5+
| 'spawn_timeout'
6+
| 'spawn_enoent'
7+
| 'permission_denied'
8+
| 'canceled'
9+
| 'parse_error'
10+
| 'unknown';
11+
12+
/**
13+
* Classifies an error into a telemetry-safe category for the `errorType` property.
14+
* Does NOT include raw error messages (PII risk from file paths) — only the category.
15+
*/
16+
export function classifyError(ex: unknown): DiscoveryErrorType {
17+
if (ex instanceof CancellationError) {
18+
return 'canceled';
19+
}
20+
21+
if (ex instanceof RpcTimeoutError) {
22+
return 'spawn_timeout';
23+
}
24+
25+
if (!(ex instanceof Error)) {
26+
return 'unknown';
27+
}
28+
29+
// Check error code for spawn failures (Node.js sets `code` on spawn errors)
30+
const code = (ex as NodeJS.ErrnoException).code;
31+
if (code === 'ENOENT') {
32+
return 'spawn_enoent';
33+
}
34+
if (code === 'EACCES' || code === 'EPERM') {
35+
return 'permission_denied';
36+
}
37+
38+
// Check message patterns
39+
const msg = ex.message.toLowerCase();
40+
if (msg.includes('timed out') || msg.includes('timeout')) {
41+
return 'spawn_timeout';
42+
}
43+
if (msg.includes('parse') || msg.includes('unexpected token') || msg.includes('json')) {
44+
return 'parse_error';
45+
}
46+
47+
// Check error name for cancellation variants
48+
if (ex.name === 'CancellationError' || ex.name === 'AbortError') {
49+
return 'canceled';
50+
}
51+
52+
return 'unknown';
53+
}

src/extension.ts

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -541,12 +541,19 @@ export async function activate(context: ExtensionContext): Promise<PythonEnviron
541541
registerInterpreterSettingsChangeListener(envManagers, projectManager, nativeFinder, api),
542542
);
543543

544-
sendTelemetryEvent(EventNames.EXTENSION_MANAGER_REGISTRATION_DURATION, start.elapsedTime);
545544
await terminalManager.initialize(api);
546545
sendManagerSelectionTelemetry(projectManager);
547546
await sendProjectStructureTelemetry(projectManager, envManagers);
547+
548+
sendTelemetryEvent(EventNames.EXTENSION_MANAGER_REGISTRATION_DURATION, start.elapsedTime);
548549
} catch (error) {
549550
traceError('Failed to initialize environment managers:', error);
551+
sendTelemetryEvent(
552+
EventNames.EXTENSION_MANAGER_REGISTRATION_DURATION,
553+
start.elapsedTime,
554+
undefined,
555+
error instanceof Error ? error : undefined,
556+
);
550557
// Show a user-friendly error message
551558
window.showErrorMessage(
552559
l10n.t(

src/internal.api.ts

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@ import {
3030
import { CreateEnvironmentNotSupported, RemoveEnvironmentNotSupported } from './common/errors/NotSupportedError';
3131
import { StopWatch } from './common/stopWatch';
3232
import { EventNames } from './common/telemetry/constants';
33+
import { classifyError } from './common/telemetry/errorClassifier';
3334
import { sendTelemetryEvent } from './common/telemetry/sender';
3435

3536
export type EnvironmentManagerScope = undefined | string | Uri | PythonEnvironment;
@@ -213,14 +214,14 @@ export class InternalEnvironmentManager implements EnvironmentManager {
213214
envCount: envs.length,
214215
});
215216
} catch (ex) {
216-
const isTimeout = ex instanceof Error && ex.message.includes('timed out');
217+
const errorType = classifyError(ex);
217218
sendTelemetryEvent(
218219
EventNames.ENVIRONMENT_DISCOVERY,
219220
sw.elapsedTime,
220221
{
221222
managerId: this.id,
222-
result: isTimeout ? 'timeout' : 'error',
223-
errorType: ex instanceof Error ? ex.name : 'unknown',
223+
result: errorType === 'canceled' || errorType === 'spawn_timeout' ? 'timeout' : 'error',
224+
errorType,
224225
},
225226
ex instanceof Error ? ex : undefined,
226227
);

0 commit comments

Comments
 (0)