Skip to content

Commit f530a5c

Browse files
author
Kartik Raj
authored
Log commands run by the discovery component in the output channel (#17670)
* Log commands run by the discovery component in the output channel * Remove outdated comments
1 parent 2ac1c26 commit f530a5c

File tree

14 files changed

+117
-107
lines changed

14 files changed

+117
-107
lines changed

news/3 Code Health/16732.md

+1
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
Log commands run by the discovery component in the output channel.

src/client/common/configSettings.ts

-34
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,6 @@ import {
1616
import { LanguageServerType } from '../activation/types';
1717
import './extensions';
1818
import { IInterpreterAutoSelectionProxyService } from '../interpreter/autoSelection/types';
19-
import { LogLevel } from '../logging/levels';
2019
import { sendTelemetryEvent } from '../telemetry';
2120
import { EventName } from '../telemetry/constants';
2221
import { sendSettingTelemetry } from '../telemetry/envFileTelemetry';
@@ -36,12 +35,10 @@ import {
3635
IFormattingSettings,
3736
IInterpreterPathService,
3837
ILintingSettings,
39-
ILoggingSettings,
4038
IPythonSettings,
4139
ISortImportSettings,
4240
ITensorBoardSettings,
4341
ITerminalSettings,
44-
LoggingLevelSettingType,
4542
Resource,
4643
} from './types';
4744
import { debounceSync } from './utils/decorators';
@@ -137,8 +134,6 @@ export class PythonSettings implements IPythonSettings {
137134

138135
public languageServerIsDefault = true;
139136

140-
public logging: ILoggingSettings = { level: LogLevel.Error };
141-
142137
protected readonly changed = new EventEmitter<void>();
143138

144139
private workspaceRoot: Resource;
@@ -305,15 +300,6 @@ export class PythonSettings implements IPythonSettings {
305300
this.devOptions = systemVariables.resolveAny(pythonSettings.get<any[]>('devOptions'))!;
306301
this.devOptions = Array.isArray(this.devOptions) ? this.devOptions : [];
307302

308-
// eslint-disable-next-line @typescript-eslint/no-explicit-any
309-
const loggingSettings = systemVariables.resolveAny(pythonSettings.get<any>('logging'))!;
310-
loggingSettings.level = convertSettingTypeToLogLevel(loggingSettings.level);
311-
if (this.logging) {
312-
Object.assign<ILoggingSettings, ILoggingSettings>(this.logging, loggingSettings);
313-
} else {
314-
this.logging = loggingSettings;
315-
}
316-
317303
const lintingSettings = systemVariables.resolveAny(pythonSettings.get<ILintingSettings>('linting'))!;
318304
if (this.linting) {
319305
Object.assign<ILintingSettings, ILintingSettings>(this.linting, lintingSettings);
@@ -709,23 +695,3 @@ function isValidPythonPath(pythonPath: string): boolean {
709695
path.basename(getOSType() === OSType.Windows ? pythonPath.toLowerCase() : pythonPath).startsWith('python')
710696
);
711697
}
712-
713-
function convertSettingTypeToLogLevel(setting: LoggingLevelSettingType | undefined): LogLevel | 'off' {
714-
switch (setting) {
715-
case 'info': {
716-
return LogLevel.Info;
717-
}
718-
case 'warn': {
719-
return LogLevel.Warn;
720-
}
721-
case 'off': {
722-
return 'off';
723-
}
724-
case 'debug': {
725-
return LogLevel.Debug;
726-
}
727-
default: {
728-
return LogLevel.Error;
729-
}
730-
}
731-
}

src/client/common/platform/fs-paths.ts

+8-2
Original file line numberDiff line numberDiff line change
@@ -134,9 +134,9 @@ export class FileSystemPathUtils implements IFileSystemPathUtils {
134134
}
135135

136136
public getDisplayName(filename: string, cwd?: string): string {
137-
if (cwd && filename.startsWith(cwd)) {
137+
if (cwd && isParentPath(filename, cwd)) {
138138
return `.${this.paths.sep}${this.raw.relative(cwd, filename)}`;
139-
} else if (filename.startsWith(this.home)) {
139+
} else if (isParentPath(filename, this.home)) {
140140
return `~${this.paths.sep}${this.raw.relative(this.home, filename)}`;
141141
} else {
142142
return filename;
@@ -154,6 +154,12 @@ export function normCasePath(filePath: string): string {
154154
* @param parentPath The potential parent path to check for
155155
*/
156156
export function isParentPath(filePath: string, parentPath: string): boolean {
157+
if (!parentPath.endsWith(nodepath.sep)) {
158+
parentPath += nodepath.sep;
159+
}
160+
if (!filePath.endsWith(nodepath.sep)) {
161+
filePath += nodepath.sep;
162+
}
157163
return normCasePath(filePath).startsWith(normCasePath(parentPath));
158164
}
159165

src/client/common/process/logger.ts

+12-8
Original file line numberDiff line numberDiff line change
@@ -17,22 +17,26 @@ export class ProcessLogger implements IProcessLogger {
1717
@inject(IPathUtils) private readonly pathUtils: IPathUtils,
1818
) {}
1919

20-
public logProcess(file: string, args: string[], options?: SpawnOptions) {
20+
public logProcess(fileOrCommand: string, args?: string[], options?: SpawnOptions) {
2121
if (!isTestExecution() && isCI && process.env.UITEST_DISABLE_PROCESS_LOGGING) {
2222
// Added to disable logging of process execution commands during UI Tests.
2323
// Used only during UI Tests (hence this setting need not be exposed as a valid setting).
2424
return;
2525
}
26-
const argsList = args.reduce((accumulator, current, index) => {
27-
let formattedArg = this.pathUtils.getDisplayName(current).toCommandArgument();
28-
if (current[0] === "'" || current[0] === '"') {
29-
formattedArg = `${current[0]}${this.pathUtils.getDisplayName(current.substr(1))}`;
30-
}
31-
26+
// Note: Single quotes maybe converted to double quotes for printing purposes.
27+
let commandList: string[];
28+
if (!args) {
29+
// It's a quoted command.
30+
commandList = fileOrCommand.split('" "').map((s) => s.trimQuotes());
31+
} else {
32+
commandList = [fileOrCommand, ...args].map((s) => s.trimQuotes());
33+
}
34+
const command = commandList.reduce((accumulator, current, index) => {
35+
const formattedArg = this.pathUtils.getDisplayName(current).toCommandArgument();
3236
return index === 0 ? formattedArg : `${accumulator} ${formattedArg}`;
3337
}, '');
3438

35-
const info = [`> ${this.pathUtils.getDisplayName(file)} ${argsList}`];
39+
const info = [`> ${command}`];
3640
if (options && options.cwd) {
3741
info.push(`${Logging.currentWorkingDirectory()} ${this.pathUtils.getDisplayName(options.cwd)}`);
3842
}

src/client/common/process/proc.ts

+1
Original file line numberDiff line numberDiff line change
@@ -58,6 +58,7 @@ export class ProcessService extends EventEmitter implements IProcessService {
5858
}
5959

6060
public shellExec(command: string, options: ShellOptions = {}): Promise<ExecutionResult<string>> {
61+
this.emit('exec', command, undefined, options);
6162
return shellExec(command, options, this.env, this.processesToKill);
6263
}
6364
}

src/client/common/process/types.ts

+6-1
Original file line numberDiff line numberDiff line change
@@ -40,7 +40,12 @@ export type ExecutionResult<T extends string | Buffer> = {
4040

4141
export const IProcessLogger = Symbol('IProcessLogger');
4242
export interface IProcessLogger {
43-
logProcess(file: string, ars: string[], options?: SpawnOptions): void;
43+
/**
44+
* Pass `args` as `undefined` if first argument is supposed to be a shell command.
45+
* Note it is assumed that command args are always quoted and respect
46+
* `String.prototype.toCommandArgument()` prototype.
47+
*/
48+
logProcess(fileOrCommand: string, args?: string[], options?: SpawnOptions): void;
4449
}
4550

4651
export interface IProcessService extends IDisposable {

src/client/common/types.ts

-7
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,6 @@ import {
1919
WorkspaceEdit,
2020
} from 'vscode';
2121
import { LanguageServerType } from '../activation/types';
22-
import { LogLevel } from '../logging/levels';
2322
import type { ExtensionChannels } from './insidersBuild/types';
2423
import type { InterpreterUri, ModuleInstallFlags } from './installer/types';
2524
import { EnvironmentVariables } from './variables/types';
@@ -189,7 +188,6 @@ export interface IPythonSettings {
189188
readonly languageServer: LanguageServerType;
190189
readonly languageServerIsDefault: boolean;
191190
readonly defaultInterpreterPath: string;
192-
readonly logging: ILoggingSettings;
193191
readonly tensorBoard: ITensorBoardSettings | undefined;
194192
initialize(): void;
195193
}
@@ -224,11 +222,6 @@ export interface IMypyCategorySeverity {
224222
readonly note: DiagnosticSeverity;
225223
}
226224

227-
export type LoggingLevelSettingType = 'off' | 'error' | 'warn' | 'info' | 'debug';
228-
229-
export interface ILoggingSettings {
230-
readonly level: LogLevel | 'off';
231-
}
232225
export interface ILintingSettings {
233226
readonly enabled: boolean;
234227
readonly ignorePatterns: string[];

src/client/extensionActivation.ts

+7-5
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,7 @@ import * as pythonEnvironments from './pythonEnvironments';
4646
import { ActivationResult, ExtensionState } from './components';
4747
import { Components } from './extensionInit';
4848
import { setDefaultLanguageServer } from './activation/common/defaultlanguageServer';
49+
import { getLoggingLevel } from './logging/settings';
4950

5051
export async function activateComponents(
5152
// `ext` is passed to any extra activation funcs.
@@ -111,12 +112,13 @@ async function activateLegacy(ext: ExtensionState): Promise<ActivationResult> {
111112
const extensions = serviceContainer.get<IExtensions>(IExtensions);
112113
await setDefaultLanguageServer(extensions, serviceManager);
113114

114-
const configuration = serviceManager.get<IConfigurationService>(IConfigurationService);
115-
// We should start logging using the log level as soon as possible, so set it as soon as we can access the level.
116-
// `IConfigurationService` may depend any of the registered types, so doing it after all registrations are finished.
117-
// XXX Move this *after* abExperiments is activated?
118-
setLoggingLevel(configuration.getSettings().logging.level);
115+
// Note we should not trigger any extension related code which logs, until we have set logging level. So we cannot
116+
// use configurations service to get level setting. Instead, we use Workspace service to query for setting as it
117+
// directly queries VSCode API.
118+
setLoggingLevel(getLoggingLevel());
119119

120+
// `IConfigurationService` may depend any of the registered types, so doing it after all registrations are finished.
121+
const configuration = serviceManager.get<IConfigurationService>(IConfigurationService);
120122
const languageServerType = configuration.getSettings().languageServer;
121123

122124
// Language feature registrations.

src/client/logging/settings.ts

+38
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
// Copyright (c) Microsoft Corporation. All rights reserved.
2+
// Licensed under the MIT License.
3+
4+
'use strict';
5+
6+
import { WorkspaceService } from '../common/application/workspace';
7+
import { LogLevel } from './levels';
8+
9+
type LoggingLevelSettingType = 'off' | 'error' | 'warn' | 'info' | 'debug';
10+
11+
/**
12+
* Uses Workspace service to query for `python.logging.level` setting and returns it.
13+
*/
14+
export function getLoggingLevel(): LogLevel | 'off' {
15+
const workspace = new WorkspaceService();
16+
const value = workspace.getConfiguration('python').get<LoggingLevelSettingType>('logging.level');
17+
return convertSettingTypeToLogLevel(value);
18+
}
19+
20+
function convertSettingTypeToLogLevel(setting: LoggingLevelSettingType | undefined): LogLevel | 'off' {
21+
switch (setting) {
22+
case 'info': {
23+
return LogLevel.Info;
24+
}
25+
case 'warn': {
26+
return LogLevel.Warn;
27+
}
28+
case 'off': {
29+
return 'off';
30+
}
31+
case 'debug': {
32+
return LogLevel.Debug;
33+
}
34+
default: {
35+
return LogLevel.Error;
36+
}
37+
}
38+
}

src/client/pythonEnvironments/base/info/interpreter.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -73,7 +73,7 @@ export async function getInterpreterInfo(python: PythonExecInfo): Promise<Interp
7373
const argv = [info.command, ...info.args];
7474

7575
// Concat these together to make a set of quoted strings
76-
const quoted = argv.reduce((p, c) => (p ? `${p} "${c}"` : `"${c.replace('\\', '\\\\')}"`), '');
76+
const quoted = argv.reduce((p, c) => (p ? `${p} "${c}"` : `"${c}"`), '');
7777

7878
// Try shell execing the command, followed by the arguments. This will make node kill the process if it
7979
// takes too long.

src/client/pythonEnvironments/common/environmentManagers/conda.ts

+1-13
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,6 @@ import { parseVersion } from '../../base/info/pythonVersion';
99

1010
import { getRegistryInterpreters } from '../windowsUtils';
1111
import { EnvironmentType, PythonEnvironment } from '../../info';
12-
import { IDisposable } from '../../../common/types';
1312
import { cache } from '../../../common/utils/decorators';
1413
import { isTestExecution } from '../../../common/constants';
1514

@@ -353,19 +352,8 @@ export class Conda {
353352
@cache(30_000, true, 10_000)
354353
// eslint-disable-next-line class-methods-use-this
355354
private async getInfoCached(command: string): Promise<CondaInfo> {
356-
const disposables = new Set<IDisposable>();
357-
const result = await exec(command, ['info', '--json'], { timeout: 50000 }, disposables);
355+
const result = await exec(command, ['info', '--json'], { timeout: 50000 });
358356
traceVerbose(`conda info --json: ${result.stdout}`);
359-
360-
// Ensure the process we started is cleaned up.
361-
disposables.forEach((p) => {
362-
try {
363-
p.dispose();
364-
} catch {
365-
// ignore.
366-
}
367-
});
368-
369357
return JSON.parse(result.stdout);
370358
}
371359

src/client/pythonEnvironments/common/externalDependencies.ts

+6-33
Original file line numberDiff line numberDiff line change
@@ -4,14 +4,12 @@
44
import * as fsapi from 'fs-extra';
55
import * as path from 'path';
66
import * as vscode from 'vscode';
7-
import { ExecutionResult, ShellOptions, SpawnOptions } from '../../common/process/types';
7+
import { ExecutionResult, IProcessServiceFactory, ShellOptions, SpawnOptions } from '../../common/process/types';
88
import { IExperimentService, IDisposable, IConfigurationService } from '../../common/types';
99
import { chain, iterable } from '../../common/utils/async';
1010
import { normalizeFilename } from '../../common/utils/filesystem';
1111
import { getOSType, OSType } from '../../common/utils/platform';
1212
import { IServiceContainer } from '../../ioc/types';
13-
import { plainExec, shellExec } from '../../common/process/rawProcessApis';
14-
import { BufferDecoder } from '../../common/process/decoder';
1513

1614
let internalServiceContainer: IServiceContainer;
1715
export function initializeExternalDependencies(serviceContainer: IServiceContainer): void {
@@ -20,39 +18,14 @@ export function initializeExternalDependencies(serviceContainer: IServiceContain
2018

2119
// processes
2220

23-
/**
24-
* Specialized version of the more generic shellExecute function to use only in
25-
* cases where we don't need to pass custom environment variables read from env
26-
* files or execution options.
27-
*
28-
* Also ensures to kill the processes created after execution.
29-
*/
3021
export async function shellExecute(command: string, options: ShellOptions = {}): Promise<ExecutionResult<string>> {
31-
const disposables = new Set<IDisposable>();
32-
return shellExec(command, options, undefined, disposables).finally(() => {
33-
// Ensure the process we started is cleaned up.
34-
disposables.forEach((p) => {
35-
try {
36-
p.dispose();
37-
} catch {
38-
// ignore.
39-
}
40-
});
41-
});
22+
const service = await internalServiceContainer.get<IProcessServiceFactory>(IProcessServiceFactory).create();
23+
return service.shellExec(command, options);
4224
}
4325

44-
/**
45-
* Specialized version of the more generic exec function to use only in
46-
* cases where we don't need to pass custom environment variables read from
47-
* env files.
48-
*/
49-
export async function exec(
50-
file: string,
51-
args: string[],
52-
options: SpawnOptions = {},
53-
disposables?: Set<IDisposable>,
54-
): Promise<ExecutionResult<string>> {
55-
return plainExec(file, args, options, new BufferDecoder(), undefined, disposables);
26+
export async function exec(file: string, args: string[], options: SpawnOptions = {}): Promise<ExecutionResult<string>> {
27+
const service = await internalServiceContainer.get<IProcessServiceFactory>(IProcessServiceFactory).create();
28+
return service.exec(file, args, options);
5629
}
5730

5831
// filesystem

src/test/common/configSettings/configSettings.unit.test.ts

-2
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,6 @@ import {
1818
IExperiments,
1919
IFormattingSettings,
2020
ILintingSettings,
21-
ILoggingSettings,
2221
ISortImportSettings,
2322
ITerminalSettings,
2423
} from '../../../client/common/types';
@@ -97,7 +96,6 @@ suite('Python Settings', async () => {
9796
config.setup((c) => c.get<any[]>('devOptions')).returns(() => sourceSettings.devOptions);
9897

9998
// complex settings
100-
config.setup((c) => c.get<ILoggingSettings>('logging')).returns(() => sourceSettings.logging);
10199
config.setup((c) => c.get<ILintingSettings>('linting')).returns(() => sourceSettings.linting);
102100
config.setup((c) => c.get<IAnalysisSettings>('analysis')).returns(() => sourceSettings.analysis);
103101
config.setup((c) => c.get<ISortImportSettings>('sortImports')).returns(() => sourceSettings.sortImports);

0 commit comments

Comments
 (0)