all: convert the "Go" output channel to 'log' type

The Go outputChannel was used as a place to record
various commands' progress, error, and output. Go vet, lint, build and
tool installation commands used this channel to record their progress.
They are now more suitable in log format. Use the 'log' format
output channel API (that was added a while ago).

"Go: Configured Go Tools" printed the result to the output channel
and this timestamped log format doesn't work well. Change the command
to created an unnamed: file and output the result there. This change
also helps users to audit/edit the result before sharing in their
bug reports.

Change-Id: I990f5bffd9ce22ec804170898e900be0de7717d4
Reviewed-on: https://go-review.googlesource.com/c/vscode-go/+/559738
TryBot-Result: kokoro <noreply+kokoro@google.com>
Reviewed-by: Suzy Mueller <suzmue@golang.org>
Commit-Queue: Hyang-Ah Hana Kim <hyangah@gmail.com>
diff --git a/extension/src/commands/getConfiguredGoTools.ts b/extension/src/commands/getConfiguredGoTools.ts
index cc8296c..88a412b 100644
--- a/extension/src/commands/getConfiguredGoTools.ts
+++ b/extension/src/commands/getConfiguredGoTools.ts
@@ -9,16 +9,14 @@
 import { CommandFactory } from '.';
 import { getGoConfig, getGoplsConfig } from '../config';
 import { inspectGoToolVersion } from '../goInstallTools';
-import { outputChannel } from '../goStatus';
 import { getConfiguredTools } from '../goTools';
 import { getBinPath, getCurrentGoPath, getGoEnv, getGoVersion, getToolsGopath } from '../util';
 import { getEnvPath, initialEnvPath, getCurrentGoRoot } from '../utils/pathUtils';
 
 export const getConfiguredGoTools: CommandFactory = () => {
 	return async () => {
-		outputChannel.show();
-		outputChannel.clear();
-		outputChannel.appendLine('Checking configured tools....');
+		// create an untitled markdown document.
+		const buf = [];
 		// Tool's path search is done by getBinPathWithPreferredGopath
 		// which searches places in the following order
 		// 1) absolute path for the alternateTool
@@ -27,45 +25,51 @@
 		// 4) gopath
 		// 5) GOROOT
 		// 6) PATH
-		outputChannel.appendLine('GOBIN: ' + process.env['GOBIN']);
-		outputChannel.appendLine('toolsGopath: ' + getToolsGopath());
-		outputChannel.appendLine('gopath: ' + getCurrentGoPath());
-		outputChannel.appendLine('GOROOT: ' + getCurrentGoRoot());
+		buf.push('# Tools Configuration\n');
+		buf.push('\n## Environment\n');
+		buf.push('GOBIN: ' + process.env['GOBIN']);
+		buf.push('toolsGopath: ' + getToolsGopath());
+		buf.push('gopath: ' + getCurrentGoPath());
+		buf.push('GOROOT: ' + getCurrentGoRoot());
 		const currentEnvPath = getEnvPath();
-		outputChannel.appendLine('PATH: ' + currentEnvPath);
+		buf.push('PATH: ' + currentEnvPath);
 		if (currentEnvPath !== initialEnvPath) {
-			outputChannel.appendLine(`PATH (vscode launched with): ${initialEnvPath}`);
+			buf.push(`PATH (vscode launched with): ${initialEnvPath}`);
 		}
-		outputChannel.appendLine('');
 
-		const goVersion = await getGoVersion();
-		const allTools = getConfiguredTools(getGoConfig(), getGoplsConfig());
-		const goVersionTooOld = goVersion?.lt('1.12') || false;
+		buf.push('\n## Tools\n');
+		try {
+			const goVersion = await getGoVersion();
+			const allTools = getConfiguredTools(getGoConfig(), getGoplsConfig());
+			const goVersionTooOld = goVersion?.lt('1.18') || false;
 
-		outputChannel.appendLine(`\tgo:\t${goVersion?.binaryPath}: ${goVersion?.version}`);
-		const toolsInfo = await Promise.all(
-			allTools.map(async (tool) => {
-				const toolPath = getBinPath(tool.name);
-				// TODO(hyangah): print alternate tool info if set.
-				if (!path.isAbsolute(toolPath)) {
-					// getBinPath returns the absolute path is the tool exists.
-					// (See getBinPathWithPreferredGopath which is called underneath)
-					return `\t${tool.name}:\tnot installed`;
-				}
-				if (goVersionTooOld) {
-					return `\t${tool.name}:\t${toolPath}: unknown version`;
-				}
-				const { goVersion, moduleVersion, debugInfo } = await inspectGoToolVersion(toolPath);
-				if (goVersion || moduleVersion) {
-					return `\t${tool.name}:\t${toolPath}\t(version: ${moduleVersion} built with go: ${goVersion})`;
-				} else {
-					return `\t${tool.name}:\t${toolPath}\t(version: unknown - ${debugInfo})`;
-				}
-			})
-		);
-		toolsInfo.forEach((info) => {
-			outputChannel.appendLine(info);
-		});
+			buf.push(`\tgo:\t${goVersion?.binaryPath}: ${goVersion?.version}`);
+			const toolsInfo = await Promise.all(
+				allTools.map(async (tool) => {
+					const toolPath = getBinPath(tool.name);
+					// TODO(hyangah): print alternate tool info if set.
+					if (!path.isAbsolute(toolPath)) {
+						// getBinPath returns the absolute path is the tool exists.
+						// (See getBinPathWithPreferredGopath which is called underneath)
+						return `\t${tool.name}:\tnot installed`;
+					}
+					if (goVersionTooOld) {
+						return `\t${tool.name}:\t${toolPath}: unknown version`;
+					}
+					const { goVersion, moduleVersion, debugInfo } = await inspectGoToolVersion(toolPath);
+					if (goVersion || moduleVersion) {
+						return `\t${tool.name}:\t${toolPath}\t(version: ${moduleVersion} built with go: ${goVersion})`;
+					} else {
+						return `\t${tool.name}:\t${toolPath}\t(version: unknown - ${debugInfo})`;
+					}
+				})
+			);
+			toolsInfo.forEach((info) => {
+				buf.push(info);
+			});
+		} catch (e) {
+			buf.push(`failed to get tools info: ${e}`);
+		}
 
 		let folders = vscode.workspace.workspaceFolders?.map<{ name: string; path?: string }>((folder) => {
 			return { name: folder.name, path: folder.uri.fsPath };
@@ -74,18 +78,24 @@
 			folders = [{ name: 'no folder', path: undefined }];
 		}
 
-		outputChannel.appendLine('');
-		outputChannel.appendLine('go env');
+		buf.push('\n## Go env\n');
 		for (const folder of folders) {
-			outputChannel.appendLine(`Workspace Folder (${folder.name}): ${folder.path}`);
+			buf.push(`Workspace Folder (${folder.name}): ${folder.path}\n`);
 			try {
 				const out = await getGoEnv(folder.path);
 				// Append '\t' to the beginning of every line (^) of 'out'.
 				// 'g' = 'global matching', and 'm' = 'multi-line matching'
-				outputChannel.appendLine(out.replace(/^/gm, '\t'));
+				buf.push(out.replace(/^/gm, '\t'));
 			} catch (e) {
-				outputChannel.appendLine(`failed to run 'go env': ${e}`);
+				buf.push(`failed to run 'go env': ${e}`);
 			}
 		}
+
+		// create a new untitled document
+		const doc = await vscode.workspace.openTextDocument({
+			content: buf.join('\n'),
+			language: 'markdown'
+		});
+		await vscode.window.showTextDocument(doc);
 	};
 };
diff --git a/extension/src/commands/startLanguageServer.ts b/extension/src/commands/startLanguageServer.ts
index f01d1f9..3c13e31 100644
--- a/extension/src/commands/startLanguageServer.ts
+++ b/extension/src/commands/startLanguageServer.ts
@@ -110,19 +110,19 @@
 	for (const folder of vscode.workspace.workspaceFolders || []) {
 		switch (folder.uri.scheme) {
 			case 'vsls':
-				outputChannel.appendLine(
+				outputChannel.error(
 					'Language service on the guest side is disabled. ' +
 						'The server-side language service will provide the language features.'
 				);
 				return;
 			case 'ssh':
-				outputChannel.appendLine('The language server is not supported for SSH. Disabling it.');
+				outputChannel.error('The language server is not supported for SSH. Disabling it.');
 				return;
 		}
 	}
 	const schemes = vscode.workspace.workspaceFolders?.map((folder) => folder.uri.scheme);
 	if (schemes && schemes.length > 0 && !schemes.includes('file') && !schemes.includes('untitled')) {
-		outputChannel.appendLine(
+		outputChannel.error(
 			`None of the folders in this workspace ${schemes.join(
 				','
 			)} are the types the language server recognizes. Disabling the language features.`
diff --git a/extension/src/goBuild.ts b/extension/src/goBuild.ts
index 5f0462e..5df6d00 100644
--- a/extension/src/goBuild.ts
+++ b/extension/src/goBuild.ts
@@ -45,7 +45,6 @@
 		const documentUri = editor?.document.uri;
 		const goConfig = getGoConfig(documentUri);
 
-		outputChannel.clear(); // Ensures stale output from build on save is cleared
 		diagnosticsStatusBarItem.show();
 		diagnosticsStatusBarItem.text = 'Building...';
 
@@ -156,7 +155,7 @@
 		if (currentGoWorkspace && !isMod) {
 			importPath = cwd.substr(currentGoWorkspace.length + 1);
 		} else {
-			outputChannel.appendLine(
+			outputChannel.error(
 				`Not able to determine import path of current package by using cwd: ${cwd} and Go workspace: ${currentGoWorkspace}`
 			);
 		}
diff --git a/extension/src/goCheck.ts b/extension/src/goCheck.ts
index c71574b..3231649 100644
--- a/extension/src/goCheck.ts
+++ b/extension/src/goCheck.ts
@@ -61,7 +61,7 @@
 	goConfig: vscode.WorkspaceConfiguration
 ): Promise<IToolCheckResults[]> {
 	diagnosticsStatusBarItem.hide();
-	outputChannel.clear();
+	outputChannel.appendLine('Running checks...');
 	const runningToolsPromises = [];
 	const cwd = path.dirname(fileUri.fsPath);
 
diff --git a/extension/src/goEnvironmentStatus.ts b/extension/src/goEnvironmentStatus.ts
index 0088cd7..1db7758 100644
--- a/extension/src/goEnvironmentStatus.ts
+++ b/extension/src/goEnvironmentStatus.ts
@@ -218,8 +218,6 @@
 			location: vscode.ProgressLocation.Notification
 		},
 		async () => {
-			outputChannel.clear();
-			outputChannel.show();
 			outputChannel.appendLine(`go install ${goOption.binpath}@latest`);
 			const result = await installTool({
 				name: newExecutableName,
@@ -229,7 +227,7 @@
 				isImportant: false
 			});
 			if (result) {
-				outputChannel.appendLine(`Error installing ${goOption.binpath}: ${result}`);
+				outputChannel.error(`Error installing ${goOption.binpath}: ${result}`);
 				throw new Error('Could not install ${goOption.binpath}');
 			}
 			// run `goX.X download`
@@ -238,7 +236,7 @@
 			try {
 				await execFile(goXExecutable, ['download']);
 			} catch (downloadErr) {
-				outputChannel.appendLine(`Error finishing installation: ${downloadErr}`);
+				outputChannel.error(`Error finishing installation: ${downloadErr}`);
 				throw new Error('Could not download Go version.');
 			}
 
@@ -253,11 +251,11 @@
 					sdkPath = stdout.trim();
 				}
 			} catch (downloadErr) {
-				outputChannel.appendLine(`Error finishing installation: ${downloadErr}`);
+				outputChannel.error(`Error finishing installation: ${downloadErr}`);
 				throw new Error('Could not download Go version.');
 			}
 			if (!sdkPath || !(await dirExists(sdkPath))) {
-				outputChannel.appendLine(`SDK path does not exist: ${sdkPath}`);
+				outputChannel.error(`SDK path does not exist: ${sdkPath}`);
 				throw new Error(`SDK path does not exist: ${sdkPath}`);
 			}
 
diff --git a/extension/src/goGenerateTests.ts b/extension/src/goGenerateTests.ts
index d2ee0ad..00cb913 100644
--- a/extension/src/goGenerateTests.ts
+++ b/extension/src/goGenerateTests.ts
@@ -202,8 +202,7 @@
 					return resolve(false);
 				}
 				if (err) {
-					console.log(err);
-					outputChannel.appendLine(err.message);
+					outputChannel.error(err.message);
 					return reject('Cannot generate test due to errors');
 				}
 
diff --git a/extension/src/goGetPackage.ts b/extension/src/goGetPackage.ts
index 46bb542..0d27eec 100644
--- a/extension/src/goGetPackage.ts
+++ b/extension/src/goGetPackage.ts
@@ -35,9 +35,7 @@
 	cp.execFile(goRuntimePath, ['get', '-v', importPath], { env }, (err, stdout, stderr) => {
 		// go get -v uses stderr to write output regardless of success or failure
 		if (stderr !== '') {
-			outputChannel.show();
-			outputChannel.clear();
-			outputChannel.appendLine(stderr);
+			outputChannel.error(stderr);
 			buildCode(false)(ctx, goCtx)();
 			return;
 		}
diff --git a/extension/src/goInstall.ts b/extension/src/goInstall.ts
index 2e006c8..52aab35 100644
--- a/extension/src/goInstall.ts
+++ b/extension/src/goInstall.ts
@@ -58,11 +58,13 @@
 	const importPath = currentGoWorkspace && !isMod ? cwd.substr(currentGoWorkspace.length + 1) : '.';
 	args.push(importPath);
 
-	outputChannel.clear();
-	outputChannel.show();
 	outputChannel.appendLine(`Installing ${importPath === '.' ? 'current package' : importPath}`);
 
 	cp.execFile(goRuntimePath, args, { env, cwd }, (err, stdout, stderr) => {
-		outputChannel.appendLine(err ? `Installation failed: ${stderr}` : 'Installation successful');
+		if (err) {
+			outputChannel.error(`Installation failed: ${stderr}`);
+		} else {
+			outputChannel.appendLine('Installation successful');
+		}
 	});
 };
diff --git a/extension/src/goInstallTools.ts b/extension/src/goInstallTools.ts
index 07552a7..edbb526 100644
--- a/extension/src/goInstallTools.ts
+++ b/extension/src/goInstallTools.ts
@@ -119,7 +119,7 @@
 		logError(`getGoForInstall failed to run 'go version' with the configured go for tool install: ${e}`);
 	} finally {
 		if (!silent) {
-			outputChannel.appendLine(
+			outputChannel.error(
 				`Ignoring misconfigured 'go.toolsManagement.go' (${configured}). Provide a valid path to the Go command.`
 			);
 		}
@@ -152,10 +152,7 @@
 		return [];
 	}
 	const { silent, skipRestartGopls } = options || {};
-	if (!silent) {
-		outputChannel.show();
-	}
-	outputChannel.clear();
+	outputChannel.appendLine('Installing tools...');
 
 	const goForInstall = await getGoForInstall(goVersion);
 
@@ -222,9 +219,9 @@
 		if (silent) {
 			outputChannel.show();
 		}
-		outputChannel.appendLine(failures.length + ' tools failed to install.\n');
+		outputChannel.error(failures.length + ' tools failed to install.\n');
 		for (const failure of failures) {
-			outputChannel.appendLine(`${failure.tool.name}: ${failure.reason} `);
+			outputChannel.error(`${failure.tool.name}: ${failure.reason} `);
 		}
 	}
 	if (missing.some((tool) => tool.isImportant)) {
@@ -280,8 +277,8 @@
 		const toolInstallPath = getBinPath(tool.name);
 		outputChannel.appendLine(`Installing ${importPath} (${toolInstallPath}) SUCCEEDED`);
 	} catch (e) {
-		outputChannel.appendLine(`Installing ${importPath} FAILED`);
-		outputChannel.appendLine(`${JSON.stringify(e, null, 1)}`);
+		outputChannel.error(`Installing ${importPath} FAILED`);
+		outputChannel.error(`${JSON.stringify(e, null, 1)}`);
 		return `failed to install ${tool.name}(${importPath}): ${e}`;
 	}
 }
@@ -484,7 +481,7 @@
 				if (stderr) {
 					// 'go env' may output warnings about potential misconfiguration.
 					// Show the messages to users but keep processing the stdout.
-					outputChannel.append(`'${goRuntimePath} env': ${stderr}`);
+					outputChannel.error(`'${goRuntimePath} env': ${stderr}`);
 					outputChannel.show();
 				}
 				logVerbose(`${goRuntimePath} env ...:\n${stdout}`);
diff --git a/extension/src/goLint.ts b/extension/src/goLint.ts
index 4f2c5c5..b9d6450 100644
--- a/extension/src/goLint.ts
+++ b/extension/src/goLint.ts
@@ -35,7 +35,7 @@
 		const goConfig = getGoConfig(documentUri);
 		const goplsConfig = getGoplsConfig(documentUri);
 
-		outputChannel.clear(); // Ensures stale output from lint on save is cleared
+		outputChannel.appendLine('Linting...');
 		diagnosticsStatusBarItem.show();
 		diagnosticsStatusBarItem.text = 'Linting...';
 
diff --git a/extension/src/goModules.ts b/extension/src/goModules.ts
index 822846c..313f7e1 100644
--- a/extension/src/goModules.ts
+++ b/extension/src/goModules.ts
@@ -127,8 +127,6 @@
 }
 
 export const goModInit: CommandFactory = () => async () => {
-	outputChannel.clear();
-
 	const moduleName = await vscode.window.showInputBox({
 		prompt: 'Enter module name',
 		value: '',
@@ -149,7 +147,7 @@
 		outputChannel.appendLine('Module successfully initialized. You are ready to Go :)');
 		vscode.commands.executeCommand('vscode.open', vscode.Uri.file(path.join(cwd, 'go.mod')));
 	} catch (e) {
-		outputChannel.appendLine((e as Error).message);
+		outputChannel.error((e as Error).message);
 		outputChannel.show();
 		vscode.window.showErrorMessage(
 			`Error running "${goRuntimePath} mod init ${moduleName}": See Go output channel for details`
diff --git a/extension/src/goStatus.ts b/extension/src/goStatus.ts
index 9fd0da7..58caa30 100644
--- a/extension/src/goStatus.ts
+++ b/extension/src/goStatus.ts
@@ -18,7 +18,7 @@
 import { GoExtensionContext } from './context';
 import { CommandFactory } from './commands';
 
-export const outputChannel = vscode.window.createOutputChannel('Go');
+export const outputChannel = vscode.window.createOutputChannel('Go', { log: true });
 
 const STATUS_BAR_ITEM_NAME = 'Go Diagnostics';
 export const diagnosticsStatusBarItem = vscode.window.createStatusBarItem(
diff --git a/extension/src/goTest/explore.ts b/extension/src/goTest/explore.ts
index bb5e009..e6901b2 100644
--- a/extension/src/goTest/explore.ts
+++ b/extension/src/goTest/explore.ts
@@ -141,7 +141,7 @@
 					await inst.didChangeConfiguration(x);
 				} catch (error) {
 					if (isInTest()) throw error;
-					else outputChannel.appendLine(`Failed while handling 'onDidChangeConfiguration': ${error}`);
+					else outputChannel.error(`Failed while handling 'onDidChangeConfiguration': ${error}`);
 				}
 			})
 		);
@@ -152,7 +152,7 @@
 					await inst.didOpenTextDocument(x);
 				} catch (error) {
 					if (isInTest()) throw error;
-					else outputChannel.appendLine(`Failed while handling 'onDidOpenTextDocument': ${error}`);
+					else outputChannel.error(`Failed while handling 'onDidOpenTextDocument': ${error}`);
 				}
 			})
 		);
@@ -163,7 +163,7 @@
 					await inst.didChangeTextDocument(x);
 				} catch (error) {
 					if (isInTest()) throw error;
-					else outputChannel.appendLine(`Failed while handling 'onDidChangeTextDocument': ${error}`);
+					else outputChannel.error(`Failed while handling 'onDidChangeTextDocument': ${error}`);
 				}
 			})
 		);
diff --git a/extension/src/goTest/resolve.ts b/extension/src/goTest/resolve.ts
index 5bcc91c..46362d6 100644
--- a/extension/src/goTest/resolve.ts
+++ b/extension/src/goTest/resolve.ts
@@ -57,8 +57,7 @@
 				if (isInTest()) throw error;
 
 				const m = 'Failed to resolve tests';
-				outputChannel.appendLine(`${m}: ${error}`);
-				await vscode.window.showErrorMessage(m);
+				outputChannel.error(`${m}: ${error}`);
 			}
 		};
 	}
diff --git a/extension/src/goTest/run.ts b/extension/src/goTest/run.ts
index 697a481..eb0f03e 100644
--- a/extension/src/goTest/run.ts
+++ b/extension/src/goTest/run.ts
@@ -92,7 +92,7 @@
 					await this.run(request, token);
 				} catch (error) {
 					const m = 'Failed to execute tests';
-					outputChannel.appendLine(`${m}: ${error}`);
+					outputChannel.error(`${m}: ${error}`);
 					await vscode.window.showErrorMessage(m);
 				}
 			},
@@ -107,7 +107,7 @@
 					await this.debug(request, token);
 				} catch (error) {
 					const m = 'Failed to debug tests';
-					outputChannel.appendLine(`${m}: ${error}`);
+					outputChannel.error(`${m}: ${error}`);
 					await vscode.window.showErrorMessage(m);
 				}
 			},
@@ -122,7 +122,7 @@
 					await this.run(request, token, this.profiler.options);
 				} catch (error) {
 					const m = 'Failed to execute tests';
-					outputChannel.appendLine(`${m}: ${error}`);
+					outputChannel.error(`${m}: ${error}`);
 					await vscode.window.showErrorMessage(m);
 				}
 			},
@@ -333,7 +333,7 @@
 
 			// https://github.com/golang/go/issues/39904
 			if (subItems.length > 0 && Object.keys(tests).length + Object.keys(benchmarks).length > 1) {
-				outputChannel.appendLine(
+				outputChannel.error(
 					`The following tests in ${pkg.uri} failed to run, as go test will only run a sub-test or sub-benchmark if it is by itself:`
 				);
 				Object.keys(tests)
diff --git a/extension/src/goVet.ts b/extension/src/goVet.ts
index c7efb70..b7965f4 100644
--- a/extension/src/goVet.ts
+++ b/extension/src/goVet.ts
@@ -38,7 +38,7 @@
 		const documentUri = editor?.document.uri;
 		const goConfig = getGoConfig(documentUri);
 
-		outputChannel.clear(); // Ensures stale output from vet on save is cleared
+		outputChannel.appendLine('Vetting...');
 		diagnosticsStatusBarItem.show();
 		diagnosticsStatusBarItem.text = 'Vetting...';
 
diff --git a/extension/src/util.ts b/extension/src/util.ts
index 7c4318c..dfc4c3f 100644
--- a/extension/src/util.ts
+++ b/extension/src/util.ts
@@ -533,8 +533,8 @@
 					return resolve([]);
 				}
 				if (err && stderr && !useStdErr) {
-					outputChannel.appendLine(['Error while running tool:', cmd, ...args].join(' '));
-					outputChannel.appendLine(stderr);
+					outputChannel.error(['Error while running tool:', cmd, ...args].join(' '));
+					outputChannel.error(stderr);
 					return resolve([]);
 				}
 				const lines = (useStdErr ? stderr : stdout).toString().split('\n');
@@ -575,7 +575,7 @@
 					outputChannel.appendLine(`${filePath}:${line}:${col ?? ''} ${msg}`);
 				}
 				if (!atLeastSingleMatch && unexpectedOutput && vscode.window.activeTextEditor) {
-					outputChannel.appendLine(stderr);
+					outputChannel.error(stderr);
 					if (err) {
 						ret.push({
 							file: vscode.window.activeTextEditor.document.fileName,