src/goDebugFactory: add GoDebugAdapterTrackerFactory

And create a `Go Debug` output channel that can be used
to output all logging and traces from debug extension
activity.

Logging level of individual debug session is controlled
by launch configuration's `trace` attribute.

Manually tested.

Update golang/vscode-go#1410

Change-Id: Ib87d014ec6114581af78bdcdd74b190b14552ce6
Reviewed-on: https://go-review.googlesource.com/c/vscode-go/+/310750
Trust: Hyang-Ah Hana Kim <hyangah@gmail.com>
Run-TryBot: Hyang-Ah Hana Kim <hyangah@gmail.com>
TryBot-Result: kokoro <noreply+kokoro@google.com>
Reviewed-by: Suzy Mueller <suzmue@golang.org>
diff --git a/src/goDebugFactory.ts b/src/goDebugFactory.ts
index 656de92..b7cb008 100644
--- a/src/goDebugFactory.ts
+++ b/src/goDebugFactory.ts
@@ -14,6 +14,7 @@
 import * as fs from 'fs';
 import * as net from 'net';
 import { getTool } from './goTools';
+import { TimestampedLogger } from './goLogging';
 
 export class GoDebugAdapterDescriptorFactory implements vscode.DebugAdapterDescriptorFactory {
 	public createDebugAdapterDescriptor(
@@ -41,8 +42,27 @@
 	}
 }
 
-// TODO(hyangah): Code below needs refactoring to avoid using vscode API
-// so we can use from a separate debug adapter executable in testing.
+export class GoDebugAdapterTrackerFactory implements vscode.DebugAdapterTrackerFactory {
+	constructor(private outputChannel: vscode.OutputChannel) {}
+
+	createDebugAdapterTracker(session: vscode.DebugSession) {
+		const level = session.configuration?.trace;
+		if (!level || level === 'off') {
+			return null;
+		}
+		const logger = new TimestampedLogger(session.configuration?.trace || 'off', this.outputChannel);
+		return {
+			onWillStartSession: () =>
+				logger.debug(`session ${session.id} will start with ${JSON.stringify(session.configuration)}\n`),
+			onWillReceiveMessage: (message: any) => logger.trace(`client -> ${JSON.stringify(message)}\n`),
+			onDidSendMessage: (message: any) => logger.trace(`client <- ${JSON.stringify(message)}\n`),
+			onError: (error: Error) => logger.error(`error: ${error}\n`),
+			onWillStopSession: () => logger.debug(`session ${session.id} will stop\n`),
+			onExit: (code: number | undefined, signal: string | undefined) =>
+				logger.info(`debug adapter exited: (code: ${code}, signal: ${signal})\n`)
+		};
+	}
+}
 
 const TWO_CRLF = '\r\n\r\n';
 
diff --git a/src/goMain.ts b/src/goMain.ts
index 104ba62..20c2927 100644
--- a/src/goMain.ts
+++ b/src/goMain.ts
@@ -23,7 +23,7 @@
 	updateCodeCoverageDecorators
 } from './goCover';
 import { GoDebugConfigurationProvider } from './goDebugConfiguration';
-import { GoDebugAdapterDescriptorFactory } from './goDebugFactory';
+import { GoDebugAdapterDescriptorFactory, GoDebugAdapterTrackerFactory } from './goDebugFactory';
 import { extractFunction, extractVariable } from './goDoctor';
 import { toolExecutionEnvironment } from './goEnv';
 import {
@@ -240,6 +240,14 @@
 		ctx.subscriptions.push(factory);
 	}
 
+	const debugOutputChannel = vscode.window.createOutputChannel('Go Debug');
+	ctx.subscriptions.push(debugOutputChannel);
+	const tracker = new GoDebugAdapterTrackerFactory(debugOutputChannel);
+	ctx.subscriptions.push(vscode.debug.registerDebugAdapterTrackerFactory('go', tracker));
+	if ('dispose' in tracker) {
+		ctx.subscriptions.push(tracker);
+	}
+
 	buildDiagnosticCollection = vscode.languages.createDiagnosticCollection('go');
 	ctx.subscriptions.push(buildDiagnosticCollection);
 	lintDiagnosticCollection = vscode.languages.createDiagnosticCollection(