From e914554c1c489d1089b31d6df6461015157eca6f Mon Sep 17 00:00:00 2001 From: Lars Kaiser Date: Mon, 28 Sep 2026 00:39:13 +0200 Subject: [PATCH] Improve logging messages --- src/pixi/envManager.ts | 21 +++++++++++++++++++-- src/pixi/projectManager.ts | 9 +++++++-- src/pixi/utils.ts | 15 ++++++++++----- src/test/pixi.unit.test.ts | 22 +++++++++++++++++++--- 4 files changed, 55 insertions(+), 12 deletions(-) diff --git a/src/pixi/envManager.ts b/src/pixi/envManager.ts index 1cff735..04a6c9d 100644 --- a/src/pixi/envManager.ts +++ b/src/pixi/envManager.ts @@ -15,7 +15,7 @@ import { import { EventEmitter, LogOutputChannel, MarkdownString, ProgressLocation, ThemeIcon, Uri, window } from 'vscode'; import { createDeferred, Deferred } from '../common/deferred'; -import { traceVerbose } from '../common/logging'; +import { traceInfo, traceVerbose } from '../common/logging'; import { resolvePixiProjectPaths } from '../common/searchPaths'; import { PIXI_MANAGER_ID } from '../common/utils'; import { PixiEnvironment } from './types'; @@ -122,12 +122,14 @@ export class PixiEnvManager implements EnvironmentManager { } async set(scope: SetEnvironmentScope, environment?: PythonEnvironment) { - traceVerbose(`Called set with scope: ${scope}, environment: ${JSON.stringify(environment)}`); + const environmentName = environment?.displayName ?? environment?.envId.id ?? 'none'; + traceVerbose(`Setting ${scope === undefined ? 'global' : 'project'} Pixi environment to '${environmentName}'`); if (scope === undefined) { await setGlobalEnvId(environment?.envId.id); this.triggerDidChangeEnvironment(undefined, this.globalEnv, environment); this.globalEnv = environment; + traceInfo(`Activated global environment '${environmentName}'`); return; } @@ -150,6 +152,10 @@ export class PixiEnvManager implements EnvironmentManager { await setProjectEnvId(projectPath, environment?.envId.id); this.triggerDidChangeEnvironment(project.uri, oldEnv, environment); + const projectName = project.name ?? projectPath; + traceInfo( + `${environment ? 'Activated' : 'Cleared'} environment '${environmentName}' for project '${projectName}'`, + ); } } @@ -187,6 +193,7 @@ export class PixiEnvManager implements EnvironmentManager { } private async refreshAll(): Promise { + traceInfo('Discovering Pixi environments across registered projects and search paths'); await window.withProgress( { location: ProgressLocation.Window, @@ -202,6 +209,7 @@ export class PixiEnvManager implements EnvironmentManager { const searchPathRoots = await resolvePixiProjectPaths(); const projectPaths = new Set([...projectMap.keys(), ...searchPathRoots]); + traceVerbose(`Refreshing Pixi environments for ${projectPaths.size} project path(s)`); const changes: DidChangeEnvironmentsEventArgs = []; @@ -216,6 +224,13 @@ export class PixiEnvManager implements EnvironmentManager { ); this._onDidChangeEnvironments.fire(changes); + const environmentCount = [...this.projectToEnvs.values()].reduce( + (count, envs) => count + envs.length, + 0, + ); + traceInfo( + `Discovered ${environmentCount} Pixi environment(s); ${changes.length} environment change(s)`, + ); const envLookup = this.buildEnvLookup(); @@ -254,8 +269,10 @@ export class PixiEnvManager implements EnvironmentManager { } const projectPath = project.uri.fsPath; + traceInfo(`Refreshing Pixi environments for project '${project.name ?? projectPath}'`); const oldEnvs = this.projectToEnvs.get(projectPath) || []; const newEnvs = await refreshPixi(projectPath); + traceInfo(`Found ${newEnvs.length} Pixi environment(s) for project '${project.name ?? projectPath}'`); this.projectToEnvs.set(projectPath, newEnvs); this._onDidChangeEnvironments.fire(this.diffEnvironments(oldEnvs, newEnvs)); diff --git a/src/pixi/projectManager.ts b/src/pixi/projectManager.ts index 36e5bbe..1129c52 100644 --- a/src/pixi/projectManager.ts +++ b/src/pixi/projectManager.ts @@ -20,7 +20,7 @@ import { window, } from 'vscode'; -import { traceInfo, traceVerbose } from '../common/logging'; +import { traceError, traceInfo, traceVerbose } from '../common/logging'; import { PixiEnvironment } from './types'; import { listPixiPackages, pixiPkgsToPackages } from './utils'; @@ -90,8 +90,13 @@ export class PixiPackageManager implements PackageManager, Disposable { environment.packages = after; this.triggerOnDidChangePackages(environment, before, after); + traceInfo( + `Refreshed packages for '${environment.name}' in '${projectPath}': ${after.length} package(s)`, + ); } catch (error) { - traceInfo(`Failed to refresh packages for environment '${environment.name}': ${error}`); + traceError( + `Failed to refresh packages for environment '${environment.name}' in '${projectPath}': ${error}`, + ); } }, ); diff --git a/src/pixi/utils.ts b/src/pixi/utils.ts index 04b0a86..8728c5c 100644 --- a/src/pixi/utils.ts +++ b/src/pixi/utils.ts @@ -8,7 +8,7 @@ import which from 'which'; import { createDeferred } from '../common/deferred'; import { quoteArgs, quoteStringIfNecessary } from '../common/execUtils'; import { findPythonExecutable } from '../common/findPython'; -import { traceError, traceInfo, traceVerbose } from '../common/logging'; +import { traceError, traceVerbose } from '../common/logging'; import { getWorkspacePersistentState } from '../common/persistentState'; import { PIXI_MANAGER_ID, untildify } from '../common/utils'; import { PixiEnvironment, PixiInfo, PixiPackage } from './types'; @@ -64,6 +64,8 @@ async function _runPixi( ): Promise { const deferred = createDeferred(); args = quoteArgs(args); + const command = `${quoteStringIfNecessary(pixi)} ${args.join(' ')}`; + traceVerbose(`Running '${command}'${options?.cwd ? ` in '${options.cwd}'` : ''}`); const proc = ch.spawn(quoteStringIfNecessary(pixi), args, { shell: true, ...options }); const cancelDisposable = token?.onCancellationRequested(() => { @@ -81,7 +83,6 @@ async function _runPixi( proc.stderr?.on('data', (data) => { const d = data.toString('utf-8'); stderr += d; - traceError(d.trim()); }); proc.on('error', (err) => { deferred.reject(err); @@ -92,7 +93,9 @@ async function _runPixi( proc.on('close', () => { cancelDisposable?.dispose(); if (exitCode !== 0) { - deferred.reject(new Error(`Failed to run "pixi ${args.join(' ')}":\n ${stderr}`)); + const message = `Pixi command '${command}' failed with exit code ${exitCode ?? 'unknown'}${stderr.trim() ? `:\n${stderr.trim()}` : ''}`; + traceError(message); + deferred.reject(new Error(message)); } else { deferred.resolve(stdout); } @@ -138,7 +141,9 @@ export async function refreshPixi(projectPath: string): Promise env !== null); } catch (error) { - traceInfo(`Failed to get pixi environments: ${error}`); + traceError(`Failed to discover Pixi environments in '${projectPath}': ${error}`); return []; } } diff --git a/src/test/pixi.unit.test.ts b/src/test/pixi.unit.test.ts index a04db0f..9a1f4e2 100644 --- a/src/test/pixi.unit.test.ts +++ b/src/test/pixi.unit.test.ts @@ -13,6 +13,7 @@ type SpawnResult = { stdout?: string; stderr?: string; exitCode?: number }; const calls: SpawnCall[] = []; const infoMessages: string[] = []; +const errorMessages: string[] = []; let configuredExecutable = 'pixi'; let workspaceFolders: { uri: { fsPath: string } }[] = []; let respond: (call: SpawnCall) => SpawnResult = () => ({ exitCode: 0 }); @@ -99,13 +100,14 @@ loader._load = originalLoad; registerLogger({ info: (message: string) => void infoMessages.push(message), - error: () => undefined, + error: (message: string) => void errorMessages.push(message), debug: () => undefined, } as unknown as Parameters[0]); test('an unsupported Pixi environment does not hide valid Python environments', async () => { calls.length = 0; infoMessages.length = 0; + errorMessages.length = 0; workspaceFolders = [{ uri: { fsPath: path.resolve('workspace') } }]; const projectPath = path.resolve('workspace', 'project'); respond = ({ args }) => { @@ -136,12 +138,19 @@ test('an unsupported Pixi environment does not hide valid Python environments', ); assert.equal(calls.length, 4); assert.ok(calls.every((call) => call.options.cwd === projectPath)); - assert.ok(infoMessages.some((message) => message.includes("Skipping Pixi environment 'foreign'"))); + assert.ok( + errorMessages.some( + (message) => + message.includes("Could not list packages for Pixi environment 'project:foreign'") && + message.includes(projectPath), + ), + ); }); test('package refresh uses the Pixi environment name and keeps existing packages on failure', async () => { calls.length = 0; infoMessages.length = 0; + errorMessages.length = 0; const projectPath = path.resolve('workspace', 'project'); const prefix = path.join(projectPath, '.pixi', 'envs', 'default'); const packages = [{ name: 'python', version: '3.12.1' }]; @@ -164,7 +173,14 @@ test('package refresh uses the Pixi environment name and keeps existing packages assert.equal(environment.packages, packages); assert.equal(calls[0].args.at(-1), 'default'); assert.equal(calls[0].options.cwd, projectPath); - assert.ok(infoMessages.some((message) => message.includes('Failed to refresh packages'))); + assert.ok( + errorMessages.some((message) => message.includes('Pixi command') && message.includes('Pixi list failed')), + ); + assert.ok( + errorMessages.some( + (message) => message.includes('Failed to refresh packages') && message.includes(projectPath), + ), + ); respond = () => ({ stdout: JSON.stringify([{ name: 'python', version: '3.13.0', is_explicit: true }]) }); await manager.refresh(environment);