From ba1717af5b31fa58b3393024a43da2c1072e7718 Mon Sep 17 00:00:00 2001 From: Kyle Carberry <kyle@carberry.com> Date: Sat, 4 Nov 2023 13:59:24 -0500 Subject: [PATCH 1/2] Add log file --- package.json | 2 +- src/remote.ts | 32 ++++++++++++++++---------------- 2 files changed, 17 insertions(+), 17 deletions(-) diff --git a/package.json b/package.json index 48f9dbd8..16e5e5db 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "coder-remote", "publisher": "coder", - "displayName": "Coder Remote", + "displayName": "Coder", "description": "Open any workspace with a single click.", "repository": "https://github.com/coder/vscode-coder", "version": "0.1.25", diff --git a/src/remote.ts b/src/remote.ts index 3e072dd9..8b52773c 100644 --- a/src/remote.ts +++ b/src/remote.ts @@ -76,6 +76,9 @@ export class Remote { await this.closeRemote() return } + const hasCoderLogs = (parsedVersion?.compare("2.3.3") || 0) >= 0 || parsedVersion?.prerelease[0] === "devel" + + console.log("hasCoderLogs", hasCoderLogs) // Find the workspace from the URI scheme provided! try { @@ -484,24 +487,21 @@ export class Remote { // Now override with the user's config. const userConfigSSH = vscode.workspace.getConfiguration("coder").get<string[]>("sshConfig") || [] // Parse the user's config into a Record<string, string>. - const userConfig = userConfigSSH.reduce( - (acc, line) => { - let i = line.indexOf("=") + const userConfig = userConfigSSH.reduce((acc, line) => { + let i = line.indexOf("=") + if (i === -1) { + i = line.indexOf(" ") if (i === -1) { - i = line.indexOf(" ") - if (i === -1) { - // This line is malformed. The setting is incorrect, and does not match - // the pattern regex in the settings schema. - return acc - } + // This line is malformed. The setting is incorrect, and does not match + // the pattern regex in the settings schema. + return acc } - const key = line.slice(0, i) - const value = line.slice(i + 1) - acc[key] = value - return acc - }, - {} as Record<string, string>, - ) + } + const key = line.slice(0, i) + const value = line.slice(i + 1) + acc[key] = value + return acc + }, {} as Record<string, string>) const sshConfigOverrides = mergeSSHConfigValues(deploymentSSHConfig, userConfig) let sshConfigFile = vscode.workspace.getConfiguration().get<string>("remote.SSH.configFile") From 5ad161b7a764d0d25619e7e6f5a4f81e750a72d3 Mon Sep 17 00:00:00 2001 From: Kyle Carberry <kyle@carberry.com> Date: Mon, 13 Nov 2023 12:42:52 -0600 Subject: [PATCH 2/2] feat: add "Show Logs" command to help with network debugging --- package.json | 6 ++++++ src/commands.ts | 11 +++++++++++ src/extension.ts | 1 + src/remote.ts | 47 ++++++++++++++++++++++++++++------------------- src/storage.ts | 7 +++++++ 5 files changed, 53 insertions(+), 19 deletions(-) diff --git a/package.json b/package.json index 16e5e5db..8ffdb57c 100644 --- a/package.json +++ b/package.json @@ -168,6 +168,12 @@ "title": "Coder: Refresh Workspace", "icon": "$(refresh)", "when": "coder.authenticated" + }, + { + "command": "coder.viewLogs", + "title": "Coder: View Logs", + "icon": "$(list-unordered)", + "when": "coder.authenticated" } ], "menus": { diff --git a/src/commands.ts b/src/commands.ts index 774768c5..c099a34f 100644 --- a/src/commands.ts +++ b/src/commands.ts @@ -115,6 +115,17 @@ export class Commands { } } + // viewLogs opens the workspace logs. + public async viewLogs(): Promise<void> { + if (!this.storage.workspaceLogPath) { + vscode.window.showInformationMessage("No logs available.", this.storage.workspaceLogPath || "<unset>") + return + } + const uri = vscode.Uri.file(this.storage.workspaceLogPath) + const doc = await vscode.workspace.openTextDocument(uri) + await vscode.window.showTextDocument(doc) + } + public async logout(): Promise<void> { await this.storage.setURL(undefined) await this.storage.setSessionToken(undefined) diff --git a/src/extension.ts b/src/extension.ts index 34188082..1bb82b84 100644 --- a/src/extension.ts +++ b/src/extension.ts @@ -157,6 +157,7 @@ export async function activate(ctx: vscode.ExtensionContext): Promise<void> { myWorkspacesProvider.fetchAndRefresh() allWorkspacesProvider.fetchAndRefresh() }) + vscode.commands.registerCommand("coder.viewLogs", commands.viewLogs.bind(commands)) // Since the "onResolveRemoteAuthority:ssh-remote" activation event exists // in package.json we're able to perform actions before the authority is diff --git a/src/remote.ts b/src/remote.ts index 8b52773c..fb0a4e7b 100644 --- a/src/remote.ts +++ b/src/remote.ts @@ -76,10 +76,11 @@ export class Remote { await this.closeRemote() return } + // CLI versions before 2.3.3 don't support the --log-dir flag! + // If this check didn't exist, VS Code connections would fail on + // older versions because of an unknown CLI argument. const hasCoderLogs = (parsedVersion?.compare("2.3.3") || 0) >= 0 || parsedVersion?.prerelease[0] === "devel" - console.log("hasCoderLogs", hasCoderLogs) - // Find the workspace from the URI scheme provided! try { this.storage.workspace = await getWorkspaceByOwnerAndName(parts[0], parts[1]) @@ -429,7 +430,7 @@ export class Remote { // // If we didn't write to the SSH config file, connecting would fail with // "Host not found". - await this.updateSSHConfig(authorityParts[1]) + await this.updateSSHConfig(authorityParts[1], hasCoderLogs) this.findSSHProcessID().then((pid) => { if (!pid) { @@ -437,6 +438,7 @@ export class Remote { return } disposables.push(this.showNetworkUpdates(pid)) + this.storage.workspaceLogPath = path.join(this.storage.getLogPath(), `${pid}.log`) }) // Register the label formatter again because SSH overrides it! @@ -459,7 +461,7 @@ export class Remote { // updateSSHConfig updates the SSH configuration with a wildcard that handles // all Coder entries. - private async updateSSHConfig(hostName: string) { + private async updateSSHConfig(hostName: string, hasCoderLogs = false) { let deploymentSSHConfig = defaultSSHConfigResponse try { const deploymentConfig = await getDeploymentSSHConfig() @@ -487,21 +489,24 @@ export class Remote { // Now override with the user's config. const userConfigSSH = vscode.workspace.getConfiguration("coder").get<string[]>("sshConfig") || [] // Parse the user's config into a Record<string, string>. - const userConfig = userConfigSSH.reduce((acc, line) => { - let i = line.indexOf("=") - if (i === -1) { - i = line.indexOf(" ") + const userConfig = userConfigSSH.reduce( + (acc, line) => { + let i = line.indexOf("=") if (i === -1) { - // This line is malformed. The setting is incorrect, and does not match - // the pattern regex in the settings schema. - return acc + i = line.indexOf(" ") + if (i === -1) { + // This line is malformed. The setting is incorrect, and does not match + // the pattern regex in the settings schema. + return acc + } } - } - const key = line.slice(0, i) - const value = line.slice(i + 1) - acc[key] = value - return acc - }, {} as Record<string, string>) + const key = line.slice(0, i) + const value = line.slice(i + 1) + acc[key] = value + return acc + }, + {} as Record<string, string>, + ) const sshConfigOverrides = mergeSSHConfigValues(deploymentSSHConfig, userConfig) let sshConfigFile = vscode.workspace.getConfiguration().get<string>("remote.SSH.configFile") @@ -542,12 +547,16 @@ export class Remote { if (typeof headerCommand === "string" && headerCommand.trim().length > 0) { headerArg = ` --header-command ${escape(headerCommand)}` } - + let logArg = "" + if (hasCoderLogs) { + await fs.mkdir(this.storage.getLogPath(), { recursive: true }) + logArg = ` --log-dir ${escape(this.storage.getLogPath())}` + } const sshValues: SSHValues = { Host: `${Remote.Prefix}*`, ProxyCommand: `${escape(binaryPath)}${headerArg} vscodessh --network-info-dir ${escape( this.storage.getNetworkInfoPath(), - )} --session-token-file ${escape(this.storage.getSessionTokenPath())} --url-file ${escape( + )}${logArg} --session-token-file ${escape(this.storage.getSessionTokenPath())} --url-file ${escape( this.storage.getURLPath(), )} %h`, ConnectTimeout: "0", diff --git a/src/storage.ts b/src/storage.ts index 8506464f..fa173cdd 100644 --- a/src/storage.ts +++ b/src/storage.ts @@ -15,6 +15,7 @@ import { getHeaderCommand, getHeaders } from "./headers" export class Storage { public workspace?: Workspace + public workspaceLogPath?: string constructor( private readonly output: vscode.OutputChannel, @@ -279,6 +280,12 @@ export class Storage { return path.join(this.globalStorageUri.fsPath, "net") } + // getLogPath returns the path where log data from the Coder + // agent is stored. + public getLogPath(): string { + return path.join(this.globalStorageUri.fsPath, "log") + } + public getUserSettingsPath(): string { return path.join(this.globalStorageUri.fsPath, "..", "..", "..", "User", "settings.json") }