Skip to content

Commit 8a77e75

Browse files
committed
add more telemetry info
1 parent c29567b commit 8a77e75

File tree

9 files changed

+266
-163
lines changed

9 files changed

+266
-163
lines changed

src/common/telemetry/constants.ts

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -56,6 +56,7 @@ export enum EventNames {
5656
* Properties:
5757
* - managerName: string (e.g. 'system', 'conda', 'pyenv', 'pipenv', 'poetry', 'shellStartupVars')
5858
* - errorType: string (classified error category from classifyError)
59+
* - failureStage: string (hierarchical stage indicator, e.g. 'getPipenv:nativeFinderRefresh')
5960
*/
6061
MANAGER_REGISTRATION_FAILED = 'MANAGER_REGISTRATION.FAILED',
6162
/**
@@ -282,12 +283,14 @@ export interface IEventNamePropertyMapping {
282283
/* __GDPR__
283284
"manager_registration.failed": {
284285
"managerName": { "classification": "SystemMetaData", "purpose": "FeatureInsight", "owner": "StellaHuang95" },
285-
"errorType": { "classification": "SystemMetaData", "purpose": "FeatureInsight", "owner": "StellaHuang95" }
286+
"errorType": { "classification": "SystemMetaData", "purpose": "FeatureInsight", "owner": "StellaHuang95" },
287+
"failureStage": { "classification": "SystemMetaData", "purpose": "FeatureInsight", "owner": "StellaHuang95" }
286288
}
287289
*/
288290
[EventNames.MANAGER_REGISTRATION_FAILED]: {
289291
managerName: string;
290292
errorType: string;
293+
failureStage: string;
291294
};
292295

293296
/* __GDPR__

src/common/utils/asyncUtils.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,9 +16,14 @@ export async function safeRegister(name: string, task: Promise<void>): Promise<v
1616
await task;
1717
} catch (error) {
1818
traceError(`Failed to register ${name} features:`, error);
19+
const failureStage =
20+
error instanceof Error
21+
? ((error as Error & { failureStage?: string }).failureStage ?? 'unknown')
22+
: 'unknown';
1923
sendTelemetryEvent(EventNames.MANAGER_REGISTRATION_FAILED, undefined, {
2024
managerName: name,
2125
errorType: classifyError(error),
26+
failureStage,
2227
});
2328
}
2429
}

src/managers/conda/main.ts

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,11 +18,13 @@ export async function registerCondaFeatures(
1818
log: LogOutputChannel,
1919
projectManager: PythonProjectManager,
2020
): Promise<void> {
21+
let stage = 'getPythonApi';
2122
const api: PythonEnvironmentApi = await getPythonApi();
2223

2324
let condaPath: string | undefined;
2425
try {
2526
// get Conda will return only ONE conda manager, that correlates to a single conda install
27+
stage = 'getConda';
2628
condaPath = await getConda(nativeFinder);
2729
} catch (ex) {
2830
traceInfo('Conda not found, turning off conda features.', ex);
@@ -36,14 +38,18 @@ export async function registerCondaFeatures(
3638

3739
// Conda was found — errors below are real registration failures (let safeRegister handle telemetry)
3840
try {
41+
stage = 'constructCondaSourcingStatus';
3942
const sourcingStatus: CondaSourcingStatus = await constructCondaSourcingStatus(condaPath);
4043
traceInfo(sourcingStatus.toString());
4144

45+
stage = 'createEnvManager';
4246
const envManager = new CondaEnvManager(nativeFinder, api, log);
47+
stage = 'createPkgManager';
4348
const packageManager = new CondaPackageManager(api, log);
4449

4550
envManager.sourcingInformation = sourcingStatus;
4651

52+
stage = 'registerManagers';
4753
disposables.push(
4854
envManager,
4955
packageManager,
@@ -52,6 +58,8 @@ export async function registerCondaFeatures(
5258
);
5359
} catch (ex) {
5460
await notifyMissingManagerIfDefault('ms-python.python:conda', projectManager, api);
55-
throw ex;
61+
const err = ex instanceof Error ? ex : new Error(String(ex));
62+
(err as Error & { failureStage?: string }).failureStage = stage;
63+
throw err;
5664
}
5765
}

src/managers/pipenv/main.ts

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,17 +17,22 @@ export async function registerPipenvFeatures(
1717
disposables: Disposable[],
1818
projectManager: PythonProjectManager,
1919
): Promise<void> {
20+
let stage = 'getPythonApi';
2021
const api: PythonEnvironmentApi = await getPythonApi();
2122

2223
try {
24+
stage = 'getPipenv';
2325
const pipenv = await getPipenv(nativeFinder);
2426

2527
// Register the manager if the CLI is found, or if there are existing pipenv environments.
2628
// This allows users with existing pipenv environments to still see and use them.
29+
stage = 'hasPipenvEnvironments';
2730
const hasPipenvEnvs = !pipenv && (await hasPipenvEnvironments(nativeFinder));
2831

2932
if (pipenv || hasPipenvEnvs) {
33+
stage = 'createManager';
3034
const mgr = new PipenvManager(nativeFinder, api);
35+
stage = 'registerManager';
3136
disposables.push(mgr, api.registerEnvironmentManager(mgr));
3237
if (!pipenv) {
3338
traceInfo(
@@ -45,13 +50,15 @@ export async function registerPipenvFeatures(
4550
await notifyMissingManagerIfDefault('ms-python.python:pipenv', projectManager, api);
4651
}
4752
} catch (ex) {
53+
const failureStage = (ex as Error & { failureStage?: string })?.failureStage ?? stage;
4854
traceInfo(
4955
'Pipenv not found, turning off pipenv features. If you have pipenv installed in a non-standard location, set the "python.pipenvPath" setting.',
5056
ex,
5157
);
5258
sendTelemetryEvent(EventNames.MANAGER_REGISTRATION_FAILED, undefined, {
5359
managerName: 'pipenv',
5460
errorType: classifyError(ex),
61+
failureStage,
5562
});
5663
await notifyMissingManagerIfDefault('ms-python.python:pipenv', projectManager, api);
5764
}

src/managers/pipenv/pipenvUtils.ts

Lines changed: 75 additions & 50 deletions
Original file line numberDiff line numberDiff line change
@@ -56,10 +56,18 @@ export async function clearPipenvCache(): Promise<void> {
5656
* This allows the manager to be registered even if the CLI is not found.
5757
*/
5858
export async function hasPipenvEnvironments(nativeFinder: NativePythonFinder): Promise<boolean> {
59-
const data = await nativeFinder.refresh(false);
60-
return data
61-
.filter((e) => isNativeEnvInfo(e))
62-
.some((e) => (e as NativeEnvInfo).kind === NativePythonEnvironmentKind.pipenv);
59+
let stage = 'nativeFinderRefresh';
60+
try {
61+
const data = await nativeFinder.refresh(false);
62+
stage = 'filterPipenvEnvs';
63+
return data
64+
.filter((e) => isNativeEnvInfo(e))
65+
.some((e) => (e as NativeEnvInfo).kind === NativePythonEnvironmentKind.pipenv);
66+
} catch (ex) {
67+
const err = ex instanceof Error ? ex : new Error(String(ex));
68+
(err as Error & { failureStage?: string }).failureStage = `hasPipenvEnvironments:${stage}`;
69+
throw err;
70+
}
6371
}
6472

6573
function getPipenvPathFromSettings(): string | undefined {
@@ -68,61 +76,78 @@ function getPipenvPathFromSettings(): string | undefined {
6876
}
6977

7078
export async function getPipenv(native?: NativePythonFinder): Promise<string | undefined> {
71-
// Priority 1: Settings (if explicitly set and valid)
72-
const settingPath = getPipenvPathFromSettings();
73-
if (settingPath) {
74-
if (await fs.exists(untildify(settingPath))) {
75-
traceInfo(`Using pipenv from settings: ${settingPath}`);
76-
return untildify(settingPath);
79+
let stage = 'checkSettings';
80+
try {
81+
// Priority 1: Settings (if explicitly set and valid)
82+
const settingPath = getPipenvPathFromSettings();
83+
if (settingPath) {
84+
stage = 'validateSettingsPath';
85+
if (await fs.exists(untildify(settingPath))) {
86+
traceInfo(`Using pipenv from settings: ${settingPath}`);
87+
return untildify(settingPath);
88+
}
89+
traceInfo(`Pipenv path from settings does not exist: ${settingPath}`);
7790
}
78-
traceInfo(`Pipenv path from settings does not exist: ${settingPath}`);
79-
}
8091

81-
// Priority 2: In-memory cache
82-
if (pipenvPath) {
83-
if (await fs.exists(untildify(pipenvPath))) {
84-
return untildify(pipenvPath);
92+
// Priority 2: In-memory cache
93+
stage = 'checkCache';
94+
if (pipenvPath) {
95+
stage = 'validateCachePath';
96+
if (await fs.exists(untildify(pipenvPath))) {
97+
return untildify(pipenvPath);
98+
}
99+
pipenvPath = undefined;
85100
}
86-
pipenvPath = undefined;
87-
}
88101

89-
// Priority 3: Persistent state
90-
const state = await getWorkspacePersistentState();
91-
const storedPath = await state.get<string>(PIPENV_PATH_KEY);
92-
if (storedPath) {
93-
if (await fs.exists(untildify(storedPath))) {
94-
pipenvPath = storedPath;
95-
traceInfo(`Using pipenv from persistent state: ${pipenvPath}`);
96-
return untildify(pipenvPath);
102+
// Priority 3: Persistent state
103+
stage = 'getPersistentState';
104+
const state = await getWorkspacePersistentState();
105+
stage = 'checkPersistentState';
106+
const storedPath = await state.get<string>(PIPENV_PATH_KEY);
107+
if (storedPath) {
108+
stage = 'validatePersistentStatePath';
109+
if (await fs.exists(untildify(storedPath))) {
110+
pipenvPath = storedPath;
111+
traceInfo(`Using pipenv from persistent state: ${pipenvPath}`);
112+
return untildify(pipenvPath);
113+
}
114+
await state.set(PIPENV_PATH_KEY, undefined);
97115
}
98-
await state.set(PIPENV_PATH_KEY, undefined);
99-
}
100116

101-
// Priority 4: PATH lookup
102-
const foundPipenv = await findPipenv();
103-
if (foundPipenv) {
104-
pipenvPath = foundPipenv;
105-
traceInfo(`Found pipenv in PATH: ${foundPipenv}`);
106-
return foundPipenv;
107-
}
117+
// Priority 4: PATH lookup
118+
stage = 'pathLookup';
119+
const foundPipenv = await findPipenv();
120+
if (foundPipenv) {
121+
pipenvPath = foundPipenv;
122+
traceInfo(`Found pipenv in PATH: ${foundPipenv}`);
123+
return foundPipenv;
124+
}
108125

109-
// Priority 5: Native finder as fallback
110-
if (native) {
111-
const data = await native.refresh(false);
112-
const managers = data
113-
.filter((e) => !isNativeEnvInfo(e))
114-
.map((e) => e as NativeEnvManagerInfo)
115-
.filter((e) => e.tool.toLowerCase() === 'pipenv');
116-
if (managers.length > 0) {
117-
pipenvPath = managers[0].executable;
118-
traceInfo(`Using pipenv from native finder: ${pipenvPath}`);
119-
await state.set(PIPENV_PATH_KEY, pipenvPath);
120-
return pipenvPath;
126+
// Priority 5: Native finder as fallback
127+
stage = 'nativeFinderRefresh';
128+
if (native) {
129+
const data = await native.refresh(false);
130+
stage = 'filterNativeFinderResults';
131+
const managers = data
132+
.filter((e) => !isNativeEnvInfo(e))
133+
.map((e) => e as NativeEnvManagerInfo)
134+
.filter((e) => e.tool.toLowerCase() === 'pipenv');
135+
if (managers.length > 0) {
136+
pipenvPath = managers[0].executable;
137+
traceInfo(`Using pipenv from native finder: ${pipenvPath}`);
138+
stage = 'persistNativeFinderResult';
139+
await state.set(PIPENV_PATH_KEY, pipenvPath);
140+
return pipenvPath;
141+
}
121142
}
122-
}
123143

124-
traceInfo('Pipenv not found');
125-
return undefined;
144+
traceInfo('Pipenv not found');
145+
return undefined;
146+
} catch (ex) {
147+
const err = ex instanceof Error ? ex : new Error(String(ex));
148+
(err as Error & { failureStage?: string }).failureStage = `getPipenv:${stage}`;
149+
throw err;
150+
}
126151
}
127152

128153
async function nativeToPythonEnv(

src/managers/poetry/main.ts

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,19 +18,25 @@ export async function registerPoetryFeatures(
1818
outputChannel: LogOutputChannel,
1919
projectManager: PythonProjectManager,
2020
): Promise<void> {
21+
let stage = 'getPythonApi';
2122
const api: PythonEnvironmentApi = await getPythonApi();
2223

2324
try {
25+
stage = 'getPoetry';
2426
const poetryPath = await getPoetry(nativeFinder);
2527
if (poetryPath) {
2628
traceInfo(
2729
'The `shell` command is not available by default in Poetry versions 2.0.0 and above. Therefore all shell activation will be handled by calling `source <path-to-activate>`. If you face any problems with shell activation, please file an issue at https://github.com/microsoft/vscode-python-environments/issues to help us improve this implementation.',
2830
);
31+
stage = 'getPoetryVersion';
2932
const version = await getPoetryVersion(poetryPath);
3033
traceInfo(`Poetry found at ${poetryPath}, version: ${version}`);
34+
stage = 'createEnvManager';
3135
const envManager = new PoetryManager(nativeFinder, api);
36+
stage = 'createPkgManager';
3237
const pkgManager = new PoetryPackageManager(api, outputChannel, envManager);
3338

39+
stage = 'registerManagers';
3440
disposables.push(
3541
envManager,
3642
pkgManager,
@@ -46,10 +52,12 @@ export async function registerPoetryFeatures(
4652
await notifyMissingManagerIfDefault('ms-python.python:poetry', projectManager, api);
4753
}
4854
} catch (ex) {
55+
const failureStage = (ex as Error & { failureStage?: string })?.failureStage ?? stage;
4956
traceInfo('Poetry not found, turning off poetry features.', ex);
5057
sendTelemetryEvent(EventNames.MANAGER_REGISTRATION_FAILED, undefined, {
5158
managerName: 'poetry',
5259
errorType: classifyError(ex),
60+
failureStage,
5361
});
5462
await notifyMissingManagerIfDefault('ms-python.python:poetry', projectManager, api);
5563
}

0 commit comments

Comments
 (0)