diff --git a/package-lock.json b/package-lock.json index e19c54a2f7..bbd22cac57 100644 --- a/package-lock.json +++ b/package-lock.json @@ -38,7 +38,7 @@ "@types/node": "16.x", "@types/node-fetch": "^2.5.12", "@types/semver": "^7.3.9", - "@types/vscode": "1.67.0", + "@types/vscode": "1.74.0", "@types/xml2js": "^0.4.8", "@typescript-eslint/eslint-plugin": "^5.19.0", "@typescript-eslint/parser": "^5.19.0", @@ -58,7 +58,7 @@ "webpack-cli": "^4.6.0" }, "engines": { - "vscode": "^1.67.0" + "vscode": "^1.74.0" } }, "node_modules/@azure/abort-controller": { @@ -917,9 +917,9 @@ } }, "node_modules/@types/vscode": { - "version": "1.67.0", - "resolved": "https://registry.npmjs.org/@types/vscode/-/vscode-1.67.0.tgz", - "integrity": "sha512-GH8BDf8cw9AC9080uneJfulhSa7KHSMI2s/CyKePXoGNos9J486w2V4YKoeNUqIEkW4hKoEAWp6/cXTwyGj47g==", + "version": "1.74.0", + "resolved": "https://registry.npmjs.org/@types/vscode/-/vscode-1.74.0.tgz", + "integrity": "sha512-LyeCIU3jb9d38w0MXFwta9r0Jx23ugujkAxdwLTNCyspdZTKUc43t7ppPbCiPoQ/Ivd/pnDFZrb4hWd45wrsgA==", "dev": true }, "node_modules/@types/xml2js": { @@ -7535,9 +7535,9 @@ } }, "@types/vscode": { - "version": "1.67.0", - "resolved": "https://registry.npmjs.org/@types/vscode/-/vscode-1.67.0.tgz", - "integrity": "sha512-GH8BDf8cw9AC9080uneJfulhSa7KHSMI2s/CyKePXoGNos9J486w2V4YKoeNUqIEkW4hKoEAWp6/cXTwyGj47g==", + "version": "1.74.0", + "resolved": "https://registry.npmjs.org/@types/vscode/-/vscode-1.74.0.tgz", + "integrity": "sha512-LyeCIU3jb9d38w0MXFwta9r0Jx23ugujkAxdwLTNCyspdZTKUc43t7ppPbCiPoQ/Ivd/pnDFZrb4hWd45wrsgA==", "dev": true }, "@types/xml2js": { diff --git a/package.json b/package.json index 846d7f07a5..d2bebd19c0 100644 --- a/package.json +++ b/package.json @@ -2894,7 +2894,7 @@ ] }, "engines": { - "vscode": "^1.67.0" + "vscode": "^1.74.0" }, "capabilities": { "virtualWorkspaces": false, @@ -2939,7 +2939,7 @@ "@types/node": "16.x", "@types/node-fetch": "^2.5.12", "@types/semver": "^7.3.9", - "@types/vscode": "1.67.0", + "@types/vscode": "1.74.0", "@types/xml2js": "^0.4.8", "@typescript-eslint/eslint-plugin": "^5.19.0", "@typescript-eslint/parser": "^5.19.0", diff --git a/src/commands/registries/azure/DockerAssignAcrPullRoleStep.ts b/src/commands/registries/azure/DockerAssignAcrPullRoleStep.ts index 5d04e8bc29..0818713399 100644 --- a/src/commands/registries/azure/DockerAssignAcrPullRoleStep.ts +++ b/src/commands/registries/azure/DockerAssignAcrPullRoleStep.ts @@ -22,7 +22,7 @@ export class DockerAssignAcrPullRoleStep extends AzureWizardExecuteStep): Promise { const message: string = localize('vscode-docker.commands.registries.azure.deployImage.assigningPullRole', 'Granting permission for App Service to pull image from ACR...'); - ext.outputChannel.appendLine(message); + ext.outputChannel.info(message); progress.report({ message: message }); const azExtAzureUtils = await getAzExtAzureUtils(); diff --git a/src/commands/registries/azure/DockerSiteCreateStep.ts b/src/commands/registries/azure/DockerSiteCreateStep.ts index c6c4bb8fc9..5574029d8a 100644 --- a/src/commands/registries/azure/DockerSiteCreateStep.ts +++ b/src/commands/registries/azure/DockerSiteCreateStep.ts @@ -29,7 +29,7 @@ export class DockerSiteCreateStep extends AzureWizardExecuteStep): Promise { const creatingNewApp: string = localize('vscode-docker.commands.registries.azure.deployImage.creatingWebApp', 'Creating web app "{0}"...', context.newSiteName); - ext.outputChannel.appendLine(creatingNewApp); + ext.outputChannel.info(creatingNewApp); progress.report({ message: creatingNewApp }); const siteConfig = await this.getNewSiteConfig(context); diff --git a/src/commands/registries/azure/DockerWebhookCreateStep.ts b/src/commands/registries/azure/DockerWebhookCreateStep.ts index d76bb6f5eb..622e1db7ea 100644 --- a/src/commands/registries/azure/DockerWebhookCreateStep.ts +++ b/src/commands/registries/azure/DockerWebhookCreateStep.ts @@ -37,10 +37,10 @@ export class DockerWebhookCreateStep extends AzureWizardExecuteStep//webHooks diff --git a/src/commands/registries/azure/deployImageToAzure.ts b/src/commands/registries/azure/deployImageToAzure.ts index 9349743064..ece89017ce 100644 --- a/src/commands/registries/azure/deployImageToAzure.ts +++ b/src/commands/registries/azure/deployImageToAzure.ts @@ -67,7 +67,7 @@ export async function deployImageToAzure(context: IActionContext, node?: RemoteT const site: Site = nonNullProp(wizardContext, 'site'); const siteUri: string = `https://${site.defaultHostName}`; const createdNewWebApp: string = localize('vscode-docker.commands.registries.azure.deployImage.created', 'Successfully created web app "{0}": {1}', site.name, siteUri); - ext.outputChannel.appendLine(createdNewWebApp); + ext.outputChannel.info(createdNewWebApp); const openSite: string = localize('vscode-docker.commands.registries.azure.deployImage.openSite', 'Open Site'); // don't wait diff --git a/src/commands/registries/azure/tasks/scheduleRunRequest.ts b/src/commands/registries/azure/tasks/scheduleRunRequest.ts index 419823108e..1c3fe90d2c 100644 --- a/src/commands/registries/azure/tasks/scheduleRunRequest.ts +++ b/src/commands/registries/azure/tasks/scheduleRunRequest.ts @@ -65,7 +65,7 @@ export async function scheduleRunRequest(context: IActionContext, requestType: ' } const uploadedSourceLocation: string = await uploadSourceCode(await node.getClient(context), node.registryName, node.resourceGroup, rootUri, tarFilePath); - ext.outputChannel.appendLine(localize('vscode-docker.commands.registries.azure.tasks.uploaded', 'Uploaded source code from {0}', tarFilePath)); + ext.outputChannel.info(localize('vscode-docker.commands.registries.azure.tasks.uploaded', 'Uploaded source code from {0}', tarFilePath)); let runRequest: AcrDockerBuildRequest | AcrFileTaskRunRequest; if (requestType === 'DockerBuildRequest') { @@ -87,11 +87,11 @@ export async function scheduleRunRequest(context: IActionContext, requestType: ' } // Schedule the run and Clean up. - ext.outputChannel.appendLine(localize('vscode-docker.commands.registries.azure.tasks.setUp', 'Set up run request')); + ext.outputChannel.info(localize('vscode-docker.commands.registries.azure.tasks.setUp', 'Set up run request')); const client = await node.getClient(context); const run = await client.registries.beginScheduleRunAndWait(node.resourceGroup, node.registryName, runRequest); - ext.outputChannel.appendLine(localize('vscode-docker.commands.registries.azure.tasks.scheduledRun', 'Scheduled run {0}', run.runId)); + ext.outputChannel.info(localize('vscode-docker.commands.registries.azure.tasks.scheduledRun', 'Scheduled run {0}', run.runId)); void streamLogs(context, node, run); @@ -134,21 +134,21 @@ async function quickPickImageName(context: IActionContext, rootFolder: vscode.Wo } async function uploadSourceCode(client: ContainerRegistryManagementClient, registryName: string, resourceGroupName: string, rootFolder: vscode.Uri, tarFilePath: string): Promise { - ext.outputChannel.appendLine(localize('vscode-docker.commands.registries.azure.tasks.sendingSource', ' Sending source code to temp file')); + ext.outputChannel.info(localize('vscode-docker.commands.registries.azure.tasks.sendingSource', ' Sending source code to temp file')); const source: string = rootFolder.fsPath; let items = await fse.readdir(source); items = items.filter(i => !(i in vcsIgnoreList)); // tslint:disable-next-line:no-unsafe-any tar.c({ cwd: source }, items).pipe(fse.createWriteStream(tarFilePath)); - ext.outputChannel.appendLine(localize('vscode-docker.commands.registries.azure.tasks.gettingBuildSourceUploadUrl', ' Getting build source upload URL')); + ext.outputChannel.info(localize('vscode-docker.commands.registries.azure.tasks.gettingBuildSourceUploadUrl', ' Getting build source upload URL')); const sourceUploadLocation = await client.registries.getBuildSourceUploadUrl(resourceGroupName, registryName); const uploadUrl: string = sourceUploadLocation.uploadUrl; const relativePath: string = sourceUploadLocation.relativePath; const storageBlob = await getStorageBlob(); const blobClient = new storageBlob.BlockBlobClient(uploadUrl); - ext.outputChannel.appendLine(localize('vscode-docker.commands.registries.azure.tasks.creatingBlockBlob', ' Creating block blob')); + ext.outputChannel.info(localize('vscode-docker.commands.registries.azure.tasks.creatingBlockBlob', ' Creating block blob')); await blobClient.uploadFile(tarFilePath); return relativePath; @@ -185,7 +185,7 @@ async function streamLogs(context: IActionContext, node: AzureRegistryTreeItem, const content = bufferToString(contentBuffer); if (content) { - ext.outputChannel.appendLine(content); + ext.outputChannel.info(content); } if (properties?.metadata?.complete) { @@ -202,7 +202,7 @@ function getTempSourceArchivePath(): string { /* tslint:disable-next-line:insecure-random */ const id: number = Math.floor(Math.random() * Math.pow(10, idPrecision)); const archive = `sourceArchive${id}.tar.gz`; - ext.outputChannel.appendLine(localize('vscode-docker.commands.registries.azure.tasks.settingUpTempFile', 'Setting up temp file with \'{0}\'', archive)); + ext.outputChannel.info(localize('vscode-docker.commands.registries.azure.tasks.settingUpTempFile', 'Setting up temp file with \'{0}\'', archive)); const tarFilePath: string = path.join(os.tmpdir(), archive); return tarFilePath; } diff --git a/src/commands/registries/logInToDockerCli.ts b/src/commands/registries/logInToDockerCli.ts index e836b42c0f..74f1218074 100644 --- a/src/commands/registries/logInToDockerCli.ts +++ b/src/commands/registries/logInToDockerCli.ts @@ -30,7 +30,7 @@ export async function logInToDockerCli(context: IActionContext, node?: RegistryT } if (!username || !password) { - ext.outputChannel.appendLine(localize('vscode-docker.commands.registries.logIn.skipping', 'WARNING: Skipping login for "{0}" because it does not require authentication.', creds.registryPath)); + ext.outputChannel.warn(localize('vscode-docker.commands.registries.logIn.skipping', 'Skipping login for "{0}" because it does not require authentication.', creds.registryPath)); } else { const progressOptions: vscode.ProgressOptions = { location: vscode.ProgressLocation.Notification, @@ -49,7 +49,7 @@ export async function logInToDockerCli(context: IActionContext, node?: RegistryT stdInPipe: stream.Readable.from(password), } ); - ext.outputChannel.appendLine('Login succeeded.'); + ext.outputChannel.info('Login succeeded.'); } catch (err) { const error = parseError(err); diff --git a/src/debugging/DockerDebugConfigurationProvider.ts b/src/debugging/DockerDebugConfigurationProvider.ts index 41b4441e0c..3c94addb7a 100644 --- a/src/debugging/DockerDebugConfigurationProvider.ts +++ b/src/debugging/DockerDebugConfigurationProvider.ts @@ -163,8 +163,8 @@ export class DockerDebugConfigurationProvider implements DebugConfigurationProvi } if (portMappings.length > 0) { - ext.outputChannel.appendLine(localize('vscode-docker.debug.configProvider.portMappings', 'The application is listening on the following port(s) (Host => Container):')); - ext.outputChannel.appendLine(portMappings.join('\n')); + ext.outputChannel.info(localize('vscode-docker.debug.configProvider.portMappings', 'The application is listening on the following port(s) (Host => Container):')); + ext.outputChannel.info(portMappings.join('\n')); } } catch { // Best effort diff --git a/src/debugging/netcore/VsDbgHelper.ts b/src/debugging/netcore/VsDbgHelper.ts index a4b90d3e69..36bc9f65f2 100644 --- a/src/debugging/netcore/VsDbgHelper.ts +++ b/src/debugging/netcore/VsDbgHelper.ts @@ -65,11 +65,11 @@ async function getLatestAcquisitionScriptIfNecessary(): Promise { return false; } - ext.outputChannel.appendLine(localize('vscode-docker.debugging.netCore.vsDbgHelper.acquiringScript', 'Acquiring latest VsDbg install script...')); + ext.outputChannel.info(localize('vscode-docker.debugging.netCore.vsDbgHelper.acquiringScript', 'Acquiring latest VsDbg install script...')); await streamToFile(acquisition.url, acquisition.scriptPath); await ext.context.globalState.update(scriptAcquiredDateKey, Date.now()); - ext.outputChannel.appendLine(localize('vscode-docker.debugging.netCore.vsDbgHelper.scriptAcquired', 'Script acquired.')); + ext.outputChannel.info(localize('vscode-docker.debugging.netCore.vsDbgHelper.scriptAcquired', 'Script acquired.')); return true; } @@ -85,13 +85,13 @@ async function executeAcquisitionScriptIfNecessary(runtime: VsDbgRuntime, versio const command = acquisition.getShellCommand(runtime, version); - ext.outputChannel.appendLine(localize('vscode-docker.debugging.netCore.vsDbgHelper.installingDebugger', 'Installing VsDbg, Runtime = {0}, Version = {1}...', runtime, version)); - ext.outputChannel.appendLine(command); + ext.outputChannel.info(localize('vscode-docker.debugging.netCore.vsDbgHelper.installingDebugger', 'Installing VsDbg, Runtime = {0}, Version = {1}...', runtime, version)); + ext.outputChannel.info(command); await execAsync(command, {}, (output: string) => { - ext.outputChannel.append(output); + ext.outputChannel.info(output); }); await ext.context.globalState.update(scriptExecutedDateKey, Date.now()); - ext.outputChannel.appendLine(localize('vscode-docker.debugging.netCore.vsDbgHelper.debuggerInstalled', 'VsDbg installed, Runtime = {0}, Version = {1}...', runtime, version)); + ext.outputChannel.info(localize('vscode-docker.debugging.netCore.vsDbgHelper.debuggerInstalled', 'VsDbg installed, Runtime = {0}, Version = {1}...', runtime, version)); } diff --git a/src/extension.ts b/src/extension.ts index 17493cfb37..dc1b864a55 100644 --- a/src/extension.ts +++ b/src/extension.ts @@ -4,7 +4,7 @@ *--------------------------------------------------------------------------------------------*/ import { TelemetryEvent } from '@microsoft/compose-language-service/lib/client/TelemetryEvent'; -import { IActionContext, UserCancelledError, callWithTelemetryAndErrorHandling, createAzExtOutputChannel, createExperimentationService, registerErrorHandler, registerEvent, registerReportIssueCommand, registerUIExtensionVariables } from '@microsoft/vscode-azext-utils'; +import { IActionContext, UserCancelledError, callWithTelemetryAndErrorHandling, createExperimentationService, registerErrorHandler, registerEvent, registerReportIssueCommand, registerUIExtensionVariables } from '@microsoft/vscode-azext-utils'; import * as path from 'path'; import * as vscode from 'vscode'; import { ConfigurationParams, DidChangeConfigurationNotification, DocumentSelector, LanguageClient, LanguageClientOptions, Middleware, ServerOptions, TransportKind } from 'vscode-languageclient/node'; @@ -26,6 +26,8 @@ import { ContainerRuntimeManager } from './runtimes/ContainerRuntimeManager'; import { OrchestratorRuntimeManager } from './runtimes/OrchestratorRuntimeManager'; import { AutoConfigurableDockerClient } from './runtimes/clients/AutoConfigurableDockerClient'; import { AutoConfigurableDockerComposeClient } from './runtimes/clients/AutoConfigurableDockerComposeClient'; +import { AzExtLogOutputChannelWrapper } from './utils/AzExtLogOutputChannelWrapper'; +import { logDockerEnvironment, logSystemInfo } from './utils/diagnostics'; export type KeyInfo = { [keyName: string]: string }; @@ -42,10 +44,11 @@ const DOCUMENT_SELECTOR: DocumentSelector = [ { language: 'dockerfile', scheme: 'file' } ]; + function initializeExtensionVariables(ctx: vscode.ExtensionContext): void { ext.context = ctx; - ext.outputChannel = createAzExtOutputChannel('Docker', ext.prefix); + ext.outputChannel = new AzExtLogOutputChannelWrapper(vscode.window.createOutputChannel('Docker', { log: true }), ext.prefix); ctx.subscriptions.push(ext.outputChannel); registerUIExtensionVariables(ext); @@ -68,6 +71,8 @@ export async function activateInternal(ctx: vscode.ExtensionContext, perfStats: process.env.VSCODE_DOCKER_TEAM === '1' ? tas.TargetPopulation.Team : undefined // If VSCODE_DOCKER_TEAM isn't set, let @microsoft/vscode-azext-utils decide target population ); + logSystemInfo(ext.outputChannel); + // Disabled for now // (new SurveyManager()).activate(); @@ -151,6 +156,7 @@ function registerEnvironmentVariableContributions(): void { actionContext.errorHandling.suppressDisplay = true; if (e.affectsConfiguration('docker.environment')) { + logDockerEnvironment(ext.outputChannel); setEnvironmentVariableContributions(); } }); diff --git a/src/extensionVariables.ts b/src/extensionVariables.ts index 0a3df69290..4bd57dd7f1 100644 --- a/src/extensionVariables.ts +++ b/src/extensionVariables.ts @@ -3,7 +3,7 @@ * Licensed under the MIT License. See LICENSE.md in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { AzExtTreeDataProvider, AzExtTreeItem, IAzExtOutputChannel, IExperimentationServiceAdapter } from '@microsoft/vscode-azext-utils'; +import { AzExtTreeDataProvider, AzExtTreeItem, IExperimentationServiceAdapter } from '@microsoft/vscode-azext-utils'; import { ExtensionContext, TreeView } from 'vscode'; import { ContainerRuntimeManager } from './runtimes/ContainerRuntimeManager'; import { IActivityMeasurementService } from './telemetry/ActivityMeasurementService'; @@ -15,6 +15,7 @@ import { RegistriesTreeItem } from './tree/registries/RegistriesTreeItem'; import { VolumesTreeItem } from './tree/volumes/VolumesTreeItem'; import { OrchestratorRuntimeManager } from './runtimes/OrchestratorRuntimeManager'; import { runWithDefaults as runWithDefaultsImpl, streamWithDefaults as streamWithDefaultsImpl } from './runtimes/runners/runWithDefaults'; +import { AzExtLogOutputChannelWrapper } from './utils/AzExtLogOutputChannelWrapper'; /** * Namespace for common variables used throughout the extension. They must be initialized in the activate() method of extension.ts @@ -22,7 +23,7 @@ import { runWithDefaults as runWithDefaultsImpl, streamWithDefaults as streamWit // eslint-disable-next-line @typescript-eslint/no-namespace export namespace ext { export let context: ExtensionContext; - export let outputChannel: IAzExtOutputChannel; + export let outputChannel: AzExtLogOutputChannelWrapper; export let experimentationService: IExperimentationServiceAdapter; export let activityMeasurementService: IActivityMeasurementService; diff --git a/src/runtimes/clients/AutoConfigurableDockerClient.ts b/src/runtimes/clients/AutoConfigurableDockerClient.ts index a376be7af4..38a952992b 100644 --- a/src/runtimes/clients/AutoConfigurableDockerClient.ts +++ b/src/runtimes/clients/AutoConfigurableDockerClient.ts @@ -4,6 +4,8 @@ *--------------------------------------------------------------------------------------------*/ import * as vscode from 'vscode'; +import { ext } from '../../extensionVariables'; +import { logCommandPath } from '../../utils/diagnostics'; import { DockerClient } from '../docker'; import { AutoConfigurableClient } from './AutoConfigurableClient'; @@ -17,5 +19,9 @@ export class AutoConfigurableDockerClient extends DockerClient implements AutoCo const config = vscode.workspace.getConfiguration('docker'); const dockerCommand = config.get('dockerPath') || 'docker'; this.commandName = dockerCommand; + + ext.outputChannel.debug(`docker.dockerPath: ${this.commandName}`); + + logCommandPath(ext.outputChannel, this.commandName); } } diff --git a/src/runtimes/clients/AutoConfigurableDockerComposeClient.ts b/src/runtimes/clients/AutoConfigurableDockerComposeClient.ts index c82b1307b6..10070c0c67 100644 --- a/src/runtimes/clients/AutoConfigurableDockerComposeClient.ts +++ b/src/runtimes/clients/AutoConfigurableDockerComposeClient.ts @@ -57,7 +57,7 @@ export class AutoConfigurableDockerComposeClient extends DockerComposeClient imp // User has not set a compose command, so we will attempt to autodetect it try { - ext.outputChannel.appendLine('Attempting to autodetect Docker Compose command...'); + ext.outputChannel.info('Attempting to autodetect Docker Compose command...'); await execAsync('docker compose version'); // If successful, then assume we can use compose V2 diff --git a/src/runtimes/docker/commandRunners/shellStream.ts b/src/runtimes/docker/commandRunners/shellStream.ts index 2d426ec73c..9433c4f5cc 100644 --- a/src/runtimes/docker/commandRunners/shellStream.ts +++ b/src/runtimes/docker/commandRunners/shellStream.ts @@ -24,7 +24,7 @@ import { StreamSpawnOptions, } from '../utils/spawnStreamAsync'; -export type ShellStreamCommandRunnerOptions = Omit & { +export type ShellStreamCommandRunnerOptions = StreamSpawnOptions & { strict?: boolean; }; @@ -51,7 +51,19 @@ export class ShellStreamCommandRunnerFactory { + try { + ext.outputChannel.debug(chunk.toString()); + } catch { + // Do not throw on diagnostic errors + } + }); + } + + const stdErrPipe = new stream.PassThrough(); + stdErrPipe.on('data', (chunk: Buffer) => { + try { + ext.outputChannel.error(chunk.toString()); + } catch { + // Do not throw on diagnostic errors + } + }); + stdErrPipe.pipe(errAccumulator); + + const onCommand = (command) => { + ext.outputChannel.debug(command); + if (typeof options?.onCommand === 'function') { + options.onCommand(command); + } + }; + super({ ...options, env: withDockerEnvSettings(process.env), shell: false, shellProvider: new NoShell(), - stdErrPipe: errAccumulator, + onCommand, + stdOutPipe, + stdErrPipe, windowsVerbatimArguments: true, strict: true, }); diff --git a/src/tree/RefreshManager.ts b/src/tree/RefreshManager.ts index 520435a67f..0ce1c20b69 100644 --- a/src/tree/RefreshManager.ts +++ b/src/tree/RefreshManager.ts @@ -157,7 +157,7 @@ export class RefreshManager extends vscode.Disposable { continue; } else { // Emit a message and rethrow to get telemetry - ext.outputChannel.appendLine( + ext.outputChannel.error( localize('vscode-docker.tree.refreshManager.eventSetupFailure', 'Failed to set up event listener: {0}', error.message) ); diff --git a/src/tree/registries/azure/createWizard/AzureRegistryCreateStep.ts b/src/tree/registries/azure/createWizard/AzureRegistryCreateStep.ts index 7306ad075d..da8738c560 100644 --- a/src/tree/registries/azure/createWizard/AzureRegistryCreateStep.ts +++ b/src/tree/registries/azure/createWizard/AzureRegistryCreateStep.ts @@ -21,7 +21,7 @@ export class AzureRegistryCreateStep extends AzureWizardExecuteStep; + private _logOutputChannel: vscode.LogOutputChannel; + + constructor(logOutputChannel: vscode.LogOutputChannel, extensionPrefix: string) { + this._logOutputChannel = logOutputChannel; + this.name = this._logOutputChannel.name; + this.extensionPrefix = extensionPrefix; + this.onDidChangeLogLevel = this._logOutputChannel.onDidChangeLogLevel; + } + + public get logLevel() { + return this._logOutputChannel.logLevel; + } + + public get isDebugLoggingEnabled(): boolean { + return isLogLevelEnabled(this, vscode.LogLevel.Debug); + } + + appendLog(value: string, options?: { resourceName?: string; date?: Date; }): void { + const enableOutputTimestampsSetting: string = 'enableOutputTimestamps'; + const projectConfiguration: vscode.WorkspaceConfiguration = vscode.workspace.getConfiguration(this.extensionPrefix); + const result: boolean | undefined = projectConfiguration.get(enableOutputTimestampsSetting); + + if (!result) { + this.info(value); + } else { + options ||= {}; + this.info(`${options.resourceName ? ' '.concat(options.resourceName) : ''}: ${value}`); + } + } + + // eslint-disable-next-line @typescript-eslint/no-explicit-any + trace(message: string, ...args: any[]): void { + this._logOutputChannel.trace(message, ...args); + } + + // eslint-disable-next-line @typescript-eslint/no-explicit-any + debug(message: string, ...args: any[]): void { + this._logOutputChannel.debug(message, ...args); + } + + // eslint-disable-next-line @typescript-eslint/no-explicit-any + info(message: string, ...args: any[]): void { + this._logOutputChannel.info(message, ...args); + } + + // eslint-disable-next-line @typescript-eslint/no-explicit-any + warn(message: string, ...args: any[]): void { + this._logOutputChannel.warn(message, ...args); + } + + // eslint-disable-next-line @typescript-eslint/no-explicit-any + error(error: string | Error, ...args: any[]): void { + this._logOutputChannel.error(error, ...args); + } + + append(value: string): void { + this._logOutputChannel.append(value); + } + + appendLine(value: string): void { + this._logOutputChannel.appendLine(value); + } + + replace(value: string): void { + this._logOutputChannel.replace(value); + } + + clear(): void { + this._logOutputChannel.clear(); + } + + show(preserveFocus?: boolean): void; + show(column?: vscode.ViewColumn, preserveFocus?: boolean): void; + show(column?: boolean | vscode.ViewColumn, preserveFocus?: boolean): void { + this._logOutputChannel.show(column as vscode.ViewColumn, preserveFocus); + } + + hide(): void { + this._logOutputChannel.hide(); + } + + dispose(): void { + this._logOutputChannel.dispose(); + } +} diff --git a/src/utils/diagnostics.ts b/src/utils/diagnostics.ts new file mode 100644 index 0000000000..41fb9329ab --- /dev/null +++ b/src/utils/diagnostics.ts @@ -0,0 +1,164 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See LICENSE.md in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import * as stream from 'stream'; +import * as vscode from 'vscode'; +import { ext } from '../extensionVariables'; +import { CancellationError, CommandLineArgs, spawnStreamAsync, StreamSpawnOptions } from '../runtimes/docker'; +import { execAsync } from './execAsync'; +import { isLinux } from './osUtils'; + +export function isLogLevelEnabled(outputChannel: vscode.LogOutputChannel, logLevel: vscode.LogLevel): boolean { + return outputChannel && outputChannel.logLevel > 0 && outputChannel.logLevel <= logLevel; +} + +export function logProcessEnvironment(outputChannel: vscode.LogOutputChannel): void { + if (isLogLevelEnabled(outputChannel, vscode.LogLevel.Debug)) { + try { + outputChannel.debug(`--- Process Environment (${Object.getOwnPropertyNames(process.env).length}) ---`); + + for (const key of Object.keys(process.env)) { + outputChannel.debug(`${key}: ${process.env[key]}`); + } + } catch { + // Do not throw for diagnostic logging + } + } +} + +export function logDockerEnvironment(outputChannel: vscode.LogOutputChannel): void { + if (isLogLevelEnabled(outputChannel, vscode.LogLevel.Debug)) { + try { + const settingValue: NodeJS.ProcessEnv = vscode.workspace.getConfiguration('docker').get('environment', {}); + + outputChannel.debug(`--- Docker Environment (${Object.getOwnPropertyNames(settingValue).length}) ---`); + for (const key of Object.keys(settingValue)) { + outputChannel.debug(`${key}: ${settingValue[key]}`); + } + } catch { + // Do not throw for diagnostic logging + } + } +} + +let loggedSystemInfo: boolean = false; +let systemInfoDisposable: vscode.Disposable; +export function logSystemInfo(outputChannel: vscode.LogOutputChannel): void { + if (loggedSystemInfo) { + return; + } + + if (isLogLevelEnabled(outputChannel, vscode.LogLevel.Debug)) { + loggedSystemInfo = true; + + if (systemInfoDisposable) { + systemInfoDisposable.dispose(); + systemInfoDisposable = null; + } + + logProcessEnvironment(outputChannel); + + logDockerEnvironment(outputChannel); + + // Linux specific system diagnostic logging + if (isLinux()) { + outputChannel.debug('--- System Info ---'); + try { + execAsync('uname -a').catch(() => {/* Do not throw */ }); + } catch { + // Do not throw for diagnostic logging + } + + try { + execAsync('cat /etc/os-release').catch(() => {/* Do not throw */ }); + } catch { + // Do not throw for diagnostic logging + } + } + } else { + systemInfoDisposable = outputChannel.onDidChangeLogLevel((logLevel) => { + if (logLevel > vscode.LogLevel.Off && logLevel <= vscode.LogLevel.Debug) { + logSystemInfo(outputChannel); + } + }); + } +} + +export function logCommandPath(outputChannel: vscode.LogOutputChannel, command: string): void { + if (isLogLevelEnabled(outputChannel, vscode.LogLevel.Debug)) { + if (isLinux()) { + try { + execAsync(`which ${command}`).catch(() => {/* Do not throw errors */ }); + } catch { + // Do not throw for diagnostic logging + } + } + } +} + +export async function spawnStreamWithDiagnosticsAsync( + command: string, + args: CommandLineArgs, + options: StreamSpawnOptions, +): Promise { + let stdOutPipe: NodeJS.WritableStream | undefined = options.stdOutPipe; + if (ext.outputChannel.isDebugLoggingEnabled) { + const debugStdOutPipe = new stream.PassThrough(); + debugStdOutPipe.on('data', (chunk: Buffer) => { + try { + ext.outputChannel.debug(chunk.toString()); + } catch { + // Do not throw on diagnostic errors + } + }); + + if (stdOutPipe) { + debugStdOutPipe.pipe(stdOutPipe); + } + stdOutPipe = debugStdOutPipe; + } + + const stdErrPipe = new stream.PassThrough(); + stdErrPipe.on('data', (chunk: Buffer) => { + try { + ext.outputChannel.error(chunk.toString()); + } catch { + // Do not throw on diagnostic errors + } + }); + + if (options.stdErrPipe) { + stdErrPipe.pipe(options.stdErrPipe); + } + + const onCommand = (command) => { + ext.outputChannel.debug(command); + if (typeof options?.onCommand === 'function') { + options.onCommand(command); + } + }; + + options = { + ...options, + onCommand, + stdOutPipe, + stdErrPipe, + }; + + try { + return spawnStreamAsync(command, args, options); + } catch (err) { + if (err instanceof CancellationError || err instanceof vscode.CancellationError) { + ext.outputChannel.debug(err.message); + } else if (err instanceof Error) { + ext.outputChannel.error(err.message); + } else { + ext.outputChannel.error(`Unknown error: ${JSON.stringify(err)}`); + } + + // Rethrow any errors + throw err; + } +} diff --git a/src/utils/execAsync.ts b/src/utils/execAsync.ts index d78d3e132b..8af0cf4f11 100644 --- a/src/utils/execAsync.ts +++ b/src/utils/execAsync.ts @@ -5,8 +5,9 @@ import * as cp from 'child_process'; import * as stream from 'stream'; -import { AccumulatorStream, Shell, spawnStreamAsync, StreamSpawnOptions } from '../runtimes/docker'; +import { AccumulatorStream, Shell, StreamSpawnOptions } from '../runtimes/docker'; import { CancellationToken } from 'vscode'; +import { spawnStreamWithDiagnosticsAsync } from './diagnostics'; type Progress = (content: string, err: boolean) => void; @@ -57,7 +58,7 @@ export async function execAsync(command: string, options?: cp.ExecOptions & { st stdErrPipe: stderrIntermediate ?? stderrFinal, }; - await spawnStreamAsync(command, [], spawnOptions); + await spawnStreamWithDiagnosticsAsync(command, [], spawnOptions); return { stdout: await stdoutFinal.getString(),