Skip to content

Commit 214b1a1

Browse files
committed
Improve request logs and error handling
1 parent bf5eec5 commit 214b1a1

File tree

2 files changed

+58
-31
lines changed

2 files changed

+58
-31
lines changed

src/extension.ts

Lines changed: 23 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import { getErrorMessage } from "coder/site/src/api/errors"
44
import * as module from "module"
55
import * as vscode from "vscode"
66
import { makeCoderSdk } from "./api"
7+
import { errToStr } from "./api-helper"
78
import { Commands } from "./commands"
89
import { CertificateError, getErrorDetail } from "./error"
910
import { Remote } from "./remote"
@@ -143,40 +144,45 @@ export async function activate(ctx: vscode.ExtensionContext): Promise<void> {
143144
}
144145
} catch (ex) {
145146
if (ex instanceof CertificateError) {
147+
storage.writeToCoderOutputChannel(ex.x509Err || ex.message)
146148
await ex.showModal("Failed to open workspace")
147149
} else if (isAxiosError(ex)) {
148-
const msg = getErrorMessage(ex, "")
149-
const detail = getErrorDetail(ex)
150-
const urlString = axios.getUri(ex.response?.config)
151-
let path = urlString
152-
try {
153-
path = new URL(urlString).pathname
154-
} catch (e) {
155-
// ignore, default to full url
156-
}
150+
const msg = getErrorMessage(ex, "None")
151+
const detail = getErrorDetail(ex) || "None"
152+
const urlString = axios.getUri(ex.config)
153+
const method = ex.config?.method?.toUpperCase() || "request"
154+
const status = ex.response?.status || "None"
155+
const message = `API ${method} to '${urlString}' failed.\nStatus code: ${status}\nMessage: ${msg}\nDetail: ${detail}`
156+
storage.writeToCoderOutputChannel(message)
157157
await vscodeProposed.window.showErrorMessage("Failed to open workspace", {
158-
detail: `API ${ex.response?.config.method?.toUpperCase()} to '${path}' failed with code ${ex.response?.status}.\nMessage: ${msg}\nDetail: ${detail}`,
158+
detail: message,
159159
modal: true,
160160
useCustom: true,
161161
})
162162
} else {
163+
const message = errToStr(ex, "No error message was provided")
164+
storage.writeToCoderOutputChannel(message)
163165
await vscodeProposed.window.showErrorMessage("Failed to open workspace", {
164-
detail: (ex as string).toString(),
166+
detail: message,
165167
modal: true,
166168
useCustom: true,
167169
})
168170
}
169171
// Always close remote session when we fail to open a workspace.
170172
await remote.closeRemote()
173+
return
171174
}
172175
}
173176

174177
// See if the plugin client is authenticated.
175-
if (restClient.getAxiosInstance().defaults.baseURL) {
178+
const baseUrl = restClient.getAxiosInstance().defaults.baseURL
179+
if (baseUrl) {
180+
storage.writeToCoderOutputChannel(`Logged in to ${baseUrl}; checking credentials`)
176181
restClient
177182
.getAuthenticatedUser()
178183
.then(async (user) => {
179184
if (user && user.roles) {
185+
storage.writeToCoderOutputChannel("Credentials are valid")
180186
vscode.commands.executeCommand("setContext", "coder.authenticated", true)
181187
if (user.roles.find((role) => role.name === "owner")) {
182188
await vscode.commands.executeCommand("setContext", "coder.isOwner", true)
@@ -185,17 +191,21 @@ export async function activate(ctx: vscode.ExtensionContext): Promise<void> {
185191
// Fetch and monitor workspaces, now that we know the client is good.
186192
myWorkspacesProvider.fetchAndRefresh()
187193
allWorkspacesProvider.fetchAndRefresh()
194+
} else {
195+
storage.writeToCoderOutputChannel(`No error, but got unexpected response: ${user}`)
188196
}
189197
})
190198
.catch((error) => {
191199
// This should be a failure to make the request, like the header command
192200
// errored.
193-
vscode.window.showErrorMessage("Failed to check user authentication: " + error.message)
201+
storage.writeToCoderOutputChannel(`Failed to check user authentication: ${error.message}`)
202+
vscode.window.showErrorMessage(`Failed to check user authentication: ${error.message}`)
194203
})
195204
.finally(() => {
196205
vscode.commands.executeCommand("setContext", "coder.loaded", true)
197206
})
198207
} else {
208+
storage.writeToCoderOutputChannel("Not currently logged in")
199209
vscode.commands.executeCommand("setContext", "coder.loaded", true)
200210
}
201211
}

src/remote.ts

Lines changed: 35 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -109,7 +109,11 @@ export class Remote {
109109
// Next is to find the workspace from the URI scheme provided.
110110
let workspace: Workspace
111111
try {
112+
this.storage.writeToCoderOutputChannel(`Looking for workspace ${workspaceName}...`)
112113
workspace = await workspaceRestClient.getWorkspaceByOwnerAndName(parts.username, parts.workspace)
114+
this.storage.writeToCoderOutputChannel(
115+
`Found workspace ${workspaceName} with status ${workspace.latest_build.status}`,
116+
)
113117
this.commands.workspace = workspace
114118
} catch (error) {
115119
if (!isAxiosError(error)) {
@@ -186,11 +190,13 @@ export class Remote {
186190
}),
187191
)
188192

193+
this.storage.writeToCoderOutputChannel(`Trying to start ${workspaceName}...`)
189194
const latestBuild = await workspaceRestClient.startWorkspace(workspace.id, versionID)
190195
workspace = {
191196
...workspace,
192197
latest_build: latestBuild,
193198
}
199+
this.storage.writeToCoderOutputChannel(`${workspaceName} is now ${workspace.latest_build.status}`)
194200
this.commands.workspace = workspace
195201
}
196202

@@ -201,6 +207,7 @@ export class Remote {
201207
workspace.latest_build.status === "starting" ||
202208
workspace.latest_build.status === "stopping"
203209
) {
210+
this.storage.writeToCoderOutputChannel(`Waiting for ${workspaceName}...`)
204211
const writeEmitter = new vscode.EventEmitter<string>()
205212
// We use a terminal instead of an output channel because it feels more
206213
// familiar to a user!
@@ -257,30 +264,31 @@ export class Remote {
257264
workspace = await workspaceRestClient.getWorkspace(workspace.id)
258265
this.commands.workspace = workspace
259266
terminal.dispose()
267+
}
260268

261-
if (buildComplete) {
262-
buildComplete()
263-
}
269+
if (buildComplete) {
270+
buildComplete()
271+
}
264272

265-
if (workspace.latest_build.status === "stopped") {
266-
const result = await this.vscodeProposed.window.showInformationMessage(
267-
`This workspace is stopped!`,
268-
{
269-
modal: true,
270-
detail: `Click below to start and open ${workspaceName}.`,
271-
useCustom: true,
272-
},
273-
"Start Workspace",
274-
)
275-
if (!result) {
276-
await this.closeRemote()
277-
}
278-
await this.reloadWindow()
279-
return
273+
if (workspace.latest_build.status === "stopped") {
274+
const result = await this.vscodeProposed.window.showInformationMessage(
275+
`This workspace is stopped!`,
276+
{
277+
modal: true,
278+
detail: `Click below to start and open ${workspaceName}.`,
279+
useCustom: true,
280+
},
281+
"Start Workspace",
282+
)
283+
if (!result) {
284+
await this.closeRemote()
280285
}
286+
await this.reloadWindow()
287+
return
281288
}
282289

283290
// Pick an agent.
291+
this.storage.writeToCoderOutputChannel(`Finding agent for ${workspaceName}...`)
284292
const agents = workspace.latest_build.resources.reduce((acc, resource) => {
285293
return acc.concat(resource.agents || [])
286294
}, [] as WorkspaceAgent[])
@@ -303,8 +311,10 @@ export class Remote {
303311
}
304312
agent = matchingAgents[0]
305313
}
314+
this.storage.writeToCoderOutputChannel(`Found agent ${agent.name} with status ${agent.status}`)
306315

307316
// Do some janky setting manipulation.
317+
this.storage.writeToCoderOutputChannel("Modifying settings...")
308318
const remotePlatforms = this.vscodeProposed.workspace
309319
.getConfiguration()
310320
.get<Record<string, string>>("remote.SSH.remotePlatform", {})
@@ -365,6 +375,7 @@ export class Remote {
365375
}
366376

367377
// Watch for workspace updates.
378+
this.storage.writeToCoderOutputChannel(`Establishing watcher for ${workspaceName}...`)
368379
const workspaceUpdate = new vscode.EventEmitter<Workspace>()
369380
const watchURL = new URL(`${baseUrlRaw}/api/v2/workspaces/${workspace.id}/watch`)
370381
const eventSource = new EventSource(watchURL.toString(), {
@@ -450,6 +461,7 @@ export class Remote {
450461

451462
// Wait for the agent to connect.
452463
if (agent.status === "connecting") {
464+
this.storage.writeToCoderOutputChannel(`Waiting for ${workspaceName}/${agent.name}...`)
453465
await vscode.window.withProgress(
454466
{
455467
title: "Waiting for the agent to connect...",
@@ -484,12 +496,14 @@ export class Remote {
484496
})
485497
},
486498
)
499+
this.storage.writeToCoderOutputChannel(`Agent ${agent.name} status is now ${agent.status}`)
487500
}
488501

489502
// Make sure agent did not time out.
490503
// TODO: Seems like maybe we should check for all the good states rather
491504
// than one bad state? Agents can error in many ways.
492505
if (agent.status === "timeout") {
506+
this.storage.writeToCoderOutputChannel(`${workspaceName}/${agent.name} timed out`)
493507
const result = await this.vscodeProposed.window.showErrorMessage("Connection timed out...", {
494508
useCustom: true,
495509
modal: true,
@@ -509,6 +523,7 @@ export class Remote {
509523
// If we didn't write to the SSH config file, connecting would fail with
510524
// "Host not found".
511525
try {
526+
this.storage.writeToCoderOutputChannel("Updating SSH config...")
512527
await this.updateSSHConfig(workspaceRestClient, parts.label, parts.host, hasCoderLogs)
513528
} catch (error) {
514529
this.storage.writeToCoderOutputChannel(`Failed to configure SSH: ${error}`)
@@ -533,6 +548,8 @@ export class Remote {
533548
}),
534549
)
535550

551+
this.storage.writeToCoderOutputChannel("Remote setup complete")
552+
536553
// Returning the URL and token allows the plugin to authenticate its own
537554
// client, for example to display the list of workspaces belonging to this
538555
// deployment in the sidebar. We use our own client in here for reasons

0 commit comments

Comments
 (0)