Add diagnostic logging option to help with advanced troubleshooting (#3809)

* Add diagnostic logging option to help troubleshoot issues

* Use LogOutputChannel implementation

* More diagnostic logging tweaks

* Fix linting errors

* Remove redundant warning label from log output

* Respond to PR comments

* Additional diagnostics refactor

* Refactor to allow logging system info once and only once when debug logging is enabled

* Refactor to move diagnostics out of the runtime code

* More changes for PR

* Use similar pattern for onCommand callback
This commit is contained in:
David Negstad 2023-01-27 09:19:50 -08:00 коммит произвёл GitHub
Родитель 8e9e2495fc
Коммит de296ad220
Не найден ключ, соответствующий данной подписи
Идентификатор ключа GPG: 4AEE18F83AFDEB23
22 изменённых файлов: 367 добавлений и 47 удалений

16
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": {

Просмотреть файл

@ -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",

Просмотреть файл

@ -22,7 +22,7 @@ export class DockerAssignAcrPullRoleStep extends AzureWizardExecuteStep<IAppServ
public async execute(context: IAppServiceWizardContext, progress: Progress<{ message?: string; increment?: number }>): Promise<void> {
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();

Просмотреть файл

@ -29,7 +29,7 @@ export class DockerSiteCreateStep extends AzureWizardExecuteStep<IAppServiceCont
public async execute(context: IAppServiceContainerWizardContext, progress: Progress<{ message?: string; increment?: number }>): Promise<void> {
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);

Просмотреть файл

@ -37,10 +37,10 @@ export class DockerWebhookCreateStep extends AzureWizardExecuteStep<IAppServiceW
const appUri: string = (await siteClient.getWebAppPublishCredential()).scmUri;
if (this._treeItem.parent instanceof AzureRepositoryTreeItem) {
const creatingNewWebhook: string = localize('vscode-docker.commands.registries.azure.dockerWebhook.creatingWebhook', 'Creating webhook for web app "{0}"...', context.newSiteName);
ext.outputChannel.appendLine(creatingNewWebhook);
ext.outputChannel.info(creatingNewWebhook);
progress.report({ message: creatingNewWebhook });
const webhook = await this.createWebhookForApp(context, this._treeItem, context.site, appUri);
ext.outputChannel.appendLine(localize('vscode-docker.commands.registries.azure.dockerWebhook.createdWebhook', 'Created webhook "{0}" with scope "{1}", id: "{2}" and location: "{3}"', webhook.name, webhook.scope, webhook.id, webhook.location));
ext.outputChannel.info(localize('vscode-docker.commands.registries.azure.dockerWebhook.createdWebhook', 'Created webhook "{0}" with scope "{1}", id: "{2}" and location: "{3}"', webhook.name, webhook.scope, webhook.id, webhook.location));
} else if (this._treeItem.parent instanceof DockerHubRepositoryTreeItem) {
// point to dockerhub to create a webhook
// http://cloud.docker.com/repository/docker/<registryName>/<repoName>/webHooks

Просмотреть файл

@ -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

Просмотреть файл

@ -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<string> {
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;
}

Просмотреть файл

@ -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);

Просмотреть файл

@ -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

Просмотреть файл

@ -65,11 +65,11 @@ async function getLatestAcquisitionScriptIfNecessary(): Promise<boolean> {
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));
}

Просмотреть файл

@ -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();
}
});

Просмотреть файл

@ -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;

Просмотреть файл

@ -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<string | undefined>('dockerPath') || 'docker';
this.commandName = dockerCommand;
ext.outputChannel.debug(`docker.dockerPath: ${this.commandName}`);
logCommandPath(ext.outputChannel, this.commandName);
}
}

Просмотреть файл

@ -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

Просмотреть файл

@ -24,7 +24,7 @@ import {
StreamSpawnOptions,
} from '../utils/spawnStreamAsync';
export type ShellStreamCommandRunnerOptions = Omit<StreamSpawnOptions, 'stdOutPipe'> & {
export type ShellStreamCommandRunnerOptions = StreamSpawnOptions & {
strict?: boolean;
};
@ -51,7 +51,19 @@ export class ShellStreamCommandRunnerFactory<TOptions extends ShellStreamCommand
accumulator = new AccumulatorStream();
}
await spawnStreamAsync(command, args, { ...this.options, stdOutPipe: accumulator });
// Determine the appropriate combination of streams that need to read from stdout
let stdOutPipe: NodeJS.WritableStream | undefined = accumulator;
if (accumulator && this.options.stdOutPipe) {
const stdOutPassThrough = new stream.PassThrough();
stdOutPassThrough.pipe(this.options.stdOutPipe);
stdOutPassThrough.pipe(accumulator);
stdOutPipe = stdOutPassThrough;
} else if (this.options.stdOutPipe) {
stdOutPipe = this.options.stdOutPipe;
}
await spawnStreamAsync(command, args, { ...this.options, stdOutPipe: stdOutPipe });
throwIfCancellationRequested(this.options.cancellationToken);

Просмотреть файл

@ -229,7 +229,7 @@ export async function spawnStreamAsync(
}
if (options.onCommand) {
options.onCommand([command, ...args].join(' '));
options.onCommand([command, ...normalizedArgs].join(' '));
}
const childProcess = spawn(

Просмотреть файл

@ -3,6 +3,7 @@
* Licensed under the MIT License. See LICENSE in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import * as stream from 'stream';
import * as vscode from 'vscode';
import { AccumulatorStream, ClientIdentity, GeneratorCommandResponse, IContainersClient, isChildProcessError, Like, normalizeCommandResponseLike, NoShell, PromiseCommandResponse, ShellStreamCommandRunnerFactory, ShellStreamCommandRunnerOptions, VoidCommandResponse } from '../docker';
import { ext } from '../../extensionVariables';
@ -104,12 +105,43 @@ class DefaultEnvStreamCommandRunnerFactory<TOptions extends DefaultEnvStreamComm
public constructor(options: TOptions) {
const errAccumulator = new AccumulatorStream();
let stdOutPipe: NodeJS.WritableStream | undefined;
if (ext.outputChannel.isDebugLoggingEnabled) {
stdOutPipe = new stream.PassThrough();
stdOutPipe.on('data', (chunk: Buffer) => {
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,
});

Просмотреть файл

@ -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)
);

Просмотреть файл

@ -21,7 +21,7 @@ export class AzureRegistryCreateStep extends AzureWizardExecuteStep<IAzureRegist
const armContainerRegistry = await getArmContainerRegistry();
const client = azExtAzureUtils.createAzureClient(context, armContainerRegistry.ContainerRegistryManagementClient);
const creating: string = localize('vscode-docker.tree.registries.azure.createWizard.creating', 'Creating registry "{0}"...', newRegistryName);
ext.outputChannel.appendLine(creating);
ext.outputChannel.info(creating);
progress.report({ message: creating });
const location: AzExtLocation = await azExtAzureUtils.LocationListStep.getLocation(context);
@ -49,7 +49,7 @@ export class AzureRegistryCreateStep extends AzureWizardExecuteStep<IAzureRegist
}
const created = localize('vscode-docker.tree.registries.azure.createWizard.created', 'Successfully created registry "{0}".', newRegistryName);
ext.outputChannel.appendLine(created);
ext.outputChannel.info(created);
}
public shouldExecute(context: IAzureRegistryWizardContext): boolean {

Просмотреть файл

@ -0,0 +1,98 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See LICENSE.md in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { IAzExtOutputChannel } from '@microsoft/vscode-azext-utils';
import * as vscode from 'vscode';
import { isLogLevelEnabled } from './diagnostics';
export class AzExtLogOutputChannelWrapper implements vscode.LogOutputChannel, IAzExtOutputChannel {
public readonly name: string;
public readonly extensionPrefix: string;
public readonly onDidChangeLogLevel: vscode.Event<vscode.LogLevel>;
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<boolean>(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();
}
}

164
src/utils/diagnostics.ts Normal file
Просмотреть файл

@ -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<NodeJS.ProcessEnv>('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<void> {
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;
}
}

Просмотреть файл

@ -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(),